Implement requestAnimationFrame for web (#1519)

* Use requestAnimationFrame for polling wasm

* Implement `requestAnimationFrame` for stdweb

Co-authored-by: Ryan G <ryanisaacg@users.noreply.github.com>
This commit is contained in:
Jurgis 2020-04-11 22:49:07 +03:00 committed by GitHub
parent a8e777a5df
commit 1f24a09570
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 93 additions and 6 deletions

View file

@ -1,6 +1,7 @@
# Unreleased # Unreleased
- On X11, fix `ResumeTimeReached` being fired too early. - On X11, fix `ResumeTimeReached` being fired too early.
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
- On Web, fix a possible panic during event handling - On Web, fix a possible panic during event handling
# 0.22.0 (2020-03-09) # 0.22.0 (2020-03-09)

View file

@ -72,7 +72,8 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControlFlow { pub enum ControlFlow {
/// When the current loop iteration finishes, immediately begin a new iteration regardless of /// When the current loop iteration finishes, immediately begin a new iteration regardless of
/// whether or not new events are available to process. /// whether or not new events are available to process. For web, events are sent when
/// `requestAnimationFrame` fires.
Poll, Poll,
/// When the current loop iteration finishes, suspend the thread until another event arrives. /// When the current loop iteration finishes, suspend the thread until another event arrives.
Wait, Wait,

View file

@ -241,7 +241,7 @@ impl<T: 'static> Shared<T> {
root::ControlFlow::Poll => { root::ControlFlow::Poll => {
let cloned = self.clone(); let cloned = self.clone();
State::Poll { State::Poll {
timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)), request: backend::AnimationFrameRequest::new(move || cloned.poll()),
} }
} }
root::ControlFlow::Wait => State::Wait { root::ControlFlow::Wait => State::Wait {

View file

@ -15,7 +15,7 @@ pub enum State {
start: Instant, start: Instant,
}, },
Poll { Poll {
timeout: backend::Timeout, request: backend::AnimationFrameRequest,
}, },
Exit, Exit,
} }

View file

@ -3,7 +3,7 @@ mod event;
mod timeout; mod timeout;
pub use self::canvas::Canvas; pub use self::canvas::Canvas;
pub use self::timeout::Timeout; pub use self::timeout::{AnimationFrameRequest, Timeout};
use crate::dpi::{LogicalSize, Size}; use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtStdweb; use crate::platform::web::WindowExtStdweb;

View file

@ -1,5 +1,7 @@
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use stdweb::web::{window, IWindowOrWorker, TimeoutHandle}; use stdweb::web::{window, IWindowOrWorker, RequestAnimationFrameHandle, TimeoutHandle};
#[derive(Debug)] #[derive(Debug)]
pub struct Timeout { pub struct Timeout {
@ -23,3 +25,39 @@ impl Drop for Timeout {
handle.clear(); handle.clear();
} }
} }
#[derive(Debug)]
pub struct AnimationFrameRequest {
handle: Option<RequestAnimationFrameHandle>,
// track callback state, because `cancelAnimationFrame` is slow
fired: Rc<Cell<bool>>,
}
impl AnimationFrameRequest {
pub fn new<F>(mut f: F) -> AnimationFrameRequest
where
F: 'static + FnMut(),
{
let fired = Rc::new(Cell::new(false));
let c_fired = fired.clone();
let handle = window().request_animation_frame(move |_| {
(*c_fired).set(true);
f();
});
AnimationFrameRequest {
handle: Some(handle),
fired,
}
}
}
impl Drop for AnimationFrameRequest {
fn drop(&mut self) {
if !(*self.fired).get() {
if let Some(handle) = self.handle.take() {
handle.cancel();
}
}
}
}

View file

@ -3,7 +3,7 @@ mod event;
mod timeout; mod timeout;
pub use self::canvas::Canvas; pub use self::canvas::Canvas;
pub use self::timeout::Timeout; pub use self::timeout::{AnimationFrameRequest, Timeout};
use crate::dpi::{LogicalSize, Size}; use crate::dpi::{LogicalSize, Size};
use crate::platform::web::WindowExtWebSys; use crate::platform::web::WindowExtWebSys;

View file

@ -1,3 +1,5 @@
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration; use std::time::Duration;
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@ -38,3 +40,48 @@ impl Drop for Timeout {
window.clear_timeout_with_handle(self.handle); window.clear_timeout_with_handle(self.handle);
} }
} }
#[derive(Debug)]
pub struct AnimationFrameRequest {
handle: i32,
// track callback state, because `cancelAnimationFrame` is slow
fired: Rc<Cell<bool>>,
_closure: Closure<dyn FnMut()>,
}
impl AnimationFrameRequest {
pub fn new<F>(mut f: F) -> AnimationFrameRequest
where
F: 'static + FnMut(),
{
let window = web_sys::window().expect("Failed to obtain window");
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 = window
.request_animation_frame(&closure.as_ref().unchecked_ref())
.expect("Failed to request animation frame");
AnimationFrameRequest {
handle,
fired,
_closure: closure,
}
}
}
impl Drop for AnimationFrameRequest {
fn drop(&mut self) {
if !(*self.fired).get() {
let window = web_sys::window().expect("Failed to obtain window");
window
.cancel_animation_frame(self.handle)
.expect("Failed to cancel animation frame");
}
}
}