mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 13:51:30 +11:00
Use setTimeout()
trick instead of Window.requestIdleCallback()
(#3044)
This commit is contained in:
parent
68ef9f707e
commit
48abf52aac
|
@ -9,6 +9,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
|||
# Unreleased
|
||||
|
||||
- Fix window size sometimes being invalid when resizing on macOS.
|
||||
- On Web, `ControlFlow::Poll` and `ControlFlow::WaitUntil` are now using the Prioritized Task Scheduling API. `setTimeout()` with a trick to circumvent throttling to 4ms is used as a fallback.
|
||||
|
||||
# 0.29.1-beta
|
||||
|
||||
|
|
|
@ -164,6 +164,8 @@ redox_syscall = "0.3"
|
|||
package = "web-sys"
|
||||
version = "0.3.64"
|
||||
features = [
|
||||
'AbortController',
|
||||
'AbortSignal',
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
|
@ -179,6 +181,8 @@ features = [
|
|||
'IntersectionObserverEntry',
|
||||
'KeyboardEvent',
|
||||
'MediaQueryList',
|
||||
'MessageChannel',
|
||||
'MessagePort',
|
||||
'Node',
|
||||
'PageTransitionEvent',
|
||||
'PointerEvent',
|
||||
|
|
|
@ -627,9 +627,11 @@ impl<T: 'static> Shared<T> {
|
|||
ControlFlow::Poll => {
|
||||
let cloned = self.clone();
|
||||
State::Poll {
|
||||
request: backend::IdleCallback::new(self.window().clone(), move || {
|
||||
cloned.poll()
|
||||
}),
|
||||
request: backend::Schedule::new(
|
||||
self.window().clone(),
|
||||
move || cloned.poll(),
|
||||
None,
|
||||
),
|
||||
}
|
||||
}
|
||||
ControlFlow::Wait => State::Wait {
|
||||
|
@ -649,10 +651,10 @@ impl<T: 'static> Shared<T> {
|
|||
State::WaitUntil {
|
||||
start,
|
||||
end,
|
||||
timeout: backend::Timeout::new(
|
||||
timeout: backend::Schedule::new(
|
||||
self.window().clone(),
|
||||
move || cloned.resume_time_reached(start, end),
|
||||
delay,
|
||||
Some(delay),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use web_time::Instant;
|
|||
pub enum State {
|
||||
Init,
|
||||
WaitUntil {
|
||||
timeout: backend::Timeout,
|
||||
timeout: backend::Schedule,
|
||||
start: Instant,
|
||||
end: Instant,
|
||||
},
|
||||
|
@ -15,7 +15,7 @@ pub enum State {
|
|||
start: Instant,
|
||||
},
|
||||
Poll {
|
||||
request: backend::IdleCallback,
|
||||
request: backend::Schedule,
|
||||
},
|
||||
Exit,
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ mod intersection_handle;
|
|||
mod media_query_handle;
|
||||
mod pointer;
|
||||
mod resize_scaling;
|
||||
mod timeout;
|
||||
mod schedule;
|
||||
|
||||
pub use self::canvas::Canvas;
|
||||
pub use self::event::ButtonsState;
|
||||
pub use self::event_handle::EventListenerHandle;
|
||||
pub use self::resize_scaling::ResizeScaleHandle;
|
||||
pub use self::timeout::{IdleCallback, Timeout};
|
||||
pub use self::schedule::Schedule;
|
||||
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use wasm_bindgen::closure::Closure;
|
||||
|
|
182
src/platform_impl/web/web_sys/schedule.rs
Normal file
182
src/platform_impl/web/web_sys/schedule.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
use js_sys::{Function, Object, Promise, Reflect};
|
||||
use once_cell::unsync::{Lazy, OnceCell};
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{AbortController, AbortSignal, MessageChannel, MessagePort};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Schedule(Inner);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Inner {
|
||||
Scheduler {
|
||||
controller: AbortController,
|
||||
_closure: Closure<dyn FnMut()>,
|
||||
},
|
||||
Timeout {
|
||||
window: web_sys::Window,
|
||||
handle: i32,
|
||||
port: MessagePort,
|
||||
_message_closure: Closure<dyn FnMut()>,
|
||||
_timeout_closure: Closure<dyn FnMut()>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
pub fn new<F>(window: web_sys::Window, f: F, duration: Option<Duration>) -> Schedule
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
if has_scheduler_support(&window) {
|
||||
Self::new_scheduler(window, f, duration)
|
||||
} else {
|
||||
Self::new_timeout(window, f, duration)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_scheduler<F>(window: web_sys::Window, f: F, duration: Option<Duration>) -> Schedule
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
let window: WindowSupportExt = window.unchecked_into();
|
||||
let scheduler = window.scheduler();
|
||||
|
||||
let closure = Closure::new(f);
|
||||
let mut options = SchedulerPostTaskOptions::new();
|
||||
let controller = AbortController::new().expect("Failed to create `AbortController`");
|
||||
options.signal(&controller.signal());
|
||||
|
||||
if let Some(duration) = duration {
|
||||
options.delay(duration.as_millis() as f64);
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static REJECT_HANDLER: Lazy<Closure<dyn FnMut(JsValue)>> = Lazy::new(|| Closure::new(|_| ()));
|
||||
}
|
||||
REJECT_HANDLER.with(|handler| {
|
||||
let _ = scheduler
|
||||
.post_task_with_options(closure.as_ref().unchecked_ref(), &options)
|
||||
.catch(handler);
|
||||
});
|
||||
|
||||
Schedule(Inner::Scheduler {
|
||||
controller,
|
||||
_closure: closure,
|
||||
})
|
||||
}
|
||||
|
||||
fn new_timeout<F>(window: web_sys::Window, f: F, duration: Option<Duration>) -> Schedule
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
let channel = MessageChannel::new().unwrap();
|
||||
let message_closure = Closure::new(f);
|
||||
let port_1 = channel.port1();
|
||||
port_1
|
||||
.add_event_listener_with_callback("message", message_closure.as_ref().unchecked_ref())
|
||||
.expect("Failed to set message handler");
|
||||
port_1.start();
|
||||
|
||||
let port_2 = channel.port2();
|
||||
let timeout_closure = Closure::new(move || {
|
||||
port_2
|
||||
.post_message(&JsValue::UNDEFINED)
|
||||
.expect("Failed to send message")
|
||||
});
|
||||
let handle = if let Some(duration) = duration {
|
||||
window.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
timeout_closure.as_ref().unchecked_ref(),
|
||||
duration.as_millis() as i32,
|
||||
)
|
||||
} else {
|
||||
window.set_timeout_with_callback(timeout_closure.as_ref().unchecked_ref())
|
||||
}
|
||||
.expect("Failed to set timeout");
|
||||
|
||||
Schedule(Inner::Timeout {
|
||||
window,
|
||||
handle,
|
||||
port: port_1,
|
||||
_message_closure: message_closure,
|
||||
_timeout_closure: timeout_closure,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Schedule {
|
||||
fn drop(&mut self) {
|
||||
match &self.0 {
|
||||
Inner::Scheduler { controller, .. } => controller.abort(),
|
||||
Inner::Timeout {
|
||||
window,
|
||||
handle,
|
||||
port,
|
||||
..
|
||||
} => {
|
||||
window.clear_timeout_with_handle(*handle);
|
||||
port.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_scheduler_support(window: &web_sys::Window) -> bool {
|
||||
thread_local! {
|
||||
static SCHEDULER_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
SCHEDULER_SUPPORT.with(|support| {
|
||||
*support.get_or_init(|| {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type SchedulerSupport;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = scheduler)]
|
||||
fn has_scheduler(this: &SchedulerSupport) -> JsValue;
|
||||
}
|
||||
|
||||
let support: &SchedulerSupport = window.unchecked_ref();
|
||||
|
||||
!support.has_scheduler().is_undefined()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type WindowSupportExt;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn scheduler(this: &WindowSupportExt) -> Scheduler;
|
||||
|
||||
type Scheduler;
|
||||
|
||||
#[wasm_bindgen(method, js_name = postTask)]
|
||||
fn post_task_with_options(
|
||||
this: &Scheduler,
|
||||
callback: &Function,
|
||||
options: &SchedulerPostTaskOptions,
|
||||
) -> Promise;
|
||||
|
||||
type SchedulerPostTaskOptions;
|
||||
}
|
||||
|
||||
impl SchedulerPostTaskOptions {
|
||||
fn new() -> Self {
|
||||
Object::new().unchecked_into()
|
||||
}
|
||||
|
||||
fn delay(&mut self, val: f64) -> &mut Self {
|
||||
let r = Reflect::set(self, &JsValue::from("delay"), &val.into());
|
||||
debug_assert!(r.is_ok(), "Failed to set `delay` property");
|
||||
self
|
||||
}
|
||||
|
||||
fn signal(&mut self, val: &AbortSignal) -> &mut Self {
|
||||
let r = Reflect::set(self, &JsValue::from("signal"), &val.into());
|
||||
debug_assert!(r.is_ok(), "Failed to set `signal` property");
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
use once_cell::unsync::OnceCell;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Timeout {
|
||||
window: web_sys::Window,
|
||||
handle: i32,
|
||||
_closure: Closure<dyn FnMut()>,
|
||||
}
|
||||
|
||||
impl Timeout {
|
||||
pub fn new<F>(window: web_sys::Window, f: F, duration: Duration) -> Timeout
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
let closure = Closure::wrap(Box::new(f) as Box<dyn FnMut()>);
|
||||
|
||||
let handle = window
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
closure.as_ref().unchecked_ref(),
|
||||
duration.as_millis() as i32,
|
||||
)
|
||||
.expect("Failed to set timeout");
|
||||
|
||||
Timeout {
|
||||
window,
|
||||
handle,
|
||||
_closure: closure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Timeout {
|
||||
fn drop(&mut self) {
|
||||
self.window.clear_timeout_with_handle(self.handle);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IdleCallback {
|
||||
window: web_sys::Window,
|
||||
handle: Handle,
|
||||
fired: Rc<Cell<bool>>,
|
||||
_closure: Closure<dyn FnMut()>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Handle {
|
||||
IdleCallback(u32),
|
||||
Timeout(i32),
|
||||
}
|
||||
|
||||
impl IdleCallback {
|
||||
pub fn new<F>(window: web_sys::Window, mut f: F) -> IdleCallback
|
||||
where
|
||||
F: 'static + FnMut(),
|
||||
{
|
||||
let fired = Rc::new(Cell::new(false));
|
||||
let c_fired = fired.clone();
|
||||
let closure = Closure::wrap(Box::new(move || {
|
||||
(*c_fired).set(true);
|
||||
f();
|
||||
}) as Box<dyn FnMut()>);
|
||||
|
||||
let handle = if has_idle_callback_support(&window) {
|
||||
Handle::IdleCallback(
|
||||
window
|
||||
.request_idle_callback(closure.as_ref().unchecked_ref())
|
||||
.expect("Failed to request idle callback"),
|
||||
)
|
||||
} else {
|
||||
Handle::Timeout(
|
||||
window
|
||||
.set_timeout_with_callback(closure.as_ref().unchecked_ref())
|
||||
.expect("Failed to set timeout"),
|
||||
)
|
||||
};
|
||||
|
||||
IdleCallback {
|
||||
window,
|
||||
handle,
|
||||
fired,
|
||||
_closure: closure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IdleCallback {
|
||||
fn drop(&mut self) {
|
||||
if !(*self.fired).get() {
|
||||
match self.handle {
|
||||
Handle::IdleCallback(handle) => self.window.cancel_idle_callback(handle),
|
||||
Handle::Timeout(handle) => self.window.clear_timeout_with_handle(handle),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_idle_callback_support(window: &web_sys::Window) -> bool {
|
||||
thread_local! {
|
||||
static IDLE_CALLBACK_SUPPORT: OnceCell<bool> = OnceCell::new();
|
||||
}
|
||||
|
||||
IDLE_CALLBACK_SUPPORT.with(|support| {
|
||||
*support.get_or_init(|| {
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type IdleCallbackSupport;
|
||||
|
||||
#[wasm_bindgen(method, getter, js_name = requestIdleCallback)]
|
||||
fn has_request_idle_callback(this: &IdleCallbackSupport) -> JsValue;
|
||||
}
|
||||
|
||||
let support: &IdleCallbackSupport = window.unchecked_ref();
|
||||
!support.has_request_idle_callback().is_undefined()
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue