Implement sleep and interval for Yew Platform (#2784)

* Rename runtime.

* Implement sleep and interval.

* Replace sleep usage with one provided by the platform.

* Fix imports.

* Fix tests.

* Enable futures on gloo.

* Always inline sleep.

* Fix tests.

* Implement sleep in house.

* Remove futures feature.

* Oops.

* Prevent Excessive Polling.
This commit is contained in:
Kaede Hoshikawa 2022-07-31 23:17:34 +09:00 committed by GitHub
parent 4eda9432bb
commit de613fa832
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 224 additions and 95 deletions

View File

@ -1,9 +1,9 @@
use std::time::Duration;
use gloo::timers::future::sleep;
use serde::{Deserialize, Serialize};
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use yew::functional::function_component;
use yew::platform::time::sleep;
use yew::prelude::*;
use yew_router::prelude::*;

View File

@ -1,9 +1,9 @@
use std::time::Duration;
use gloo::timers::future::sleep;
use serde::{Deserialize, Serialize};
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use yew::functional::function_component;
use yew::platform::time::sleep;
use yew::prelude::*;
use yew_router::prelude::*;

View File

@ -1,9 +1,9 @@
use std::time::Duration;
use gloo::timers::future::sleep;
use serde::{Deserialize, Serialize};
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use yew::functional::function_component;
use yew::platform::time::sleep;
use yew::prelude::*;
use yew_router::prelude::*;

View File

@ -1,9 +1,9 @@
use std::time::Duration;
use gloo::timers::future::sleep;
use serde::{Deserialize, Serialize};
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use yew::functional::function_component;
use yew::platform::time::sleep;
use yew::prelude::*;
use yew_router::prelude::*;

View File

@ -25,7 +25,7 @@ slab = "0.4"
wasm-bindgen = "0.2"
yew-macro = { version = "^0.19.0", path = "../yew-macro" }
thiserror = "1.0"
futures = { version = "0.3", optional = true }
futures = { version = "0.3", default-features = false, features = ["std"] }
html-escape = { version = "0.2.9", optional = true }
implicit-clone = { version = "0.3", features = ["map"] }
base64ct = { version = "1.5.0", features = ["std"], optional = true }
@ -93,8 +93,8 @@ features = [
]
[features]
tokio = ["tokio/rt", "dep:num_cpus", "dep:tokio-util"]
ssr = ["dep:futures", "dep:html-escape", "dep:base64ct", "dep:bincode"]
tokio = ["tokio/rt", "tokio/time", "dep:num_cpus", "dep:tokio-util"]
ssr = ["dep:html-escape", "dep:base64ct", "dep:bincode"]
csr = []
hydration = ["csr", "dep:bincode"]
nightly = ["yew-macro/nightly"]

View File

@ -165,14 +165,12 @@ mod feat_hydration {
#[cfg(target_arch = "wasm32")]
#[cfg(test)]
mod tests {
use std::ops::Deref;
use gloo::utils::document;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use web_sys::{Element, Node};
use web_sys::Element;
use super::*;
use crate::dom_bundle::{Bundle, Reconcilable, ReconcileTarget};
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::virtual_dom::{Key, VChild, VNode};
use crate::{html, scheduler, Children, Component, Context, Html, NodeRef, Properties};

View File

@ -46,15 +46,16 @@ use std::future::Future;
pub(crate) mod io;
pub mod sync;
pub mod time;
#[cfg(target_arch = "wasm32")]
#[path = "rt_wasm_bindgen.rs"]
#[path = "rt_wasm_bindgen/mod.rs"]
mod imp;
#[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
#[path = "rt_tokio.rs"]
#[path = "rt_tokio/mod.rs"]
mod imp;
#[cfg(all(not(target_arch = "wasm32"), not(feature = "tokio")))]
#[path = "rt_none.rs"]
#[path = "rt_none/mod.rs"]
mod imp;
/// Spawns a task on current thread.

View File

@ -1,26 +0,0 @@
use std::future::Future;
#[inline(always)]
pub(super) fn spawn_local<F>(_f: F)
where
F: Future<Output = ()> + 'static,
{
panic!(
r#"No runtime configured for this platform, features that requires task spawning can't be used.
Either compile with `target_arch = "wasm32", or enable the `tokio` feature."#
);
}
#[cfg(feature = "ssr")]
pub(crate) async fn run_pinned<F, Fut>(_create_task: F) -> Fut::Output
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
panic!(
r#"No runtime configured for this platform, features that requires task spawning can't be used.
Either compile with `target_arch = "wasm32", or enable the `tokio` feature."#
)
}

View File

@ -0,0 +1,30 @@
use std::future::Future;
pub(crate) mod time;
static NO_RUNTIME_NOTICE: &str = r#"No runtime configured for this platform, \
features that requires a runtime can't be used. \
Either compile with `target_arch = "wasm32", or enable the `tokio` feature."#;
fn panic_no_runtime() -> ! {
panic!("{}", NO_RUNTIME_NOTICE);
}
#[inline(always)]
pub(super) fn spawn_local<F>(_f: F)
where
F: Future<Output = ()> + 'static,
{
panic_no_runtime();
}
#[cfg(feature = "ssr")]
pub(crate) async fn run_pinned<F, Fut>(_create_task: F) -> Fut::Output
where
F: FnOnce() -> Fut,
F: Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
panic_no_runtime();
}

View File

@ -0,0 +1,13 @@
use std::time::Duration;
use futures::stream::LocalBoxStream;
use super::panic_no_runtime;
pub(crate) async fn sleep(_dur: Duration) {
panic_no_runtime();
}
pub(crate) fn interval(_dur: Duration) -> LocalBoxStream<'static, ()> {
panic_no_runtime();
}

View File

@ -1,5 +1,7 @@
use std::future::Future;
pub(crate) mod time;
#[cfg(feature = "ssr")]
pub(super) async fn run_pinned<F, Fut>(create_task: F) -> Fut::Output
where

View File

@ -0,0 +1,14 @@
use std::future::Future;
use std::time::Duration;
use futures::stream::{Stream, StreamExt};
use tokio_stream::wrappers::IntervalStream;
#[inline(always)]
pub(crate) fn sleep(dur: Duration) -> impl Future<Output = ()> {
tokio::time::sleep(dur)
}
pub(crate) fn interval(dur: Duration) -> impl Stream<Item = ()> {
IntervalStream::new(tokio::time::interval(dur)).then(|_| async {})
}

View File

@ -1,6 +1,8 @@
#[cfg(feature = "ssr")]
use std::future::Future;
pub(crate) mod time;
pub(super) use wasm_bindgen_futures::spawn_local;
#[cfg(feature = "ssr")]

View File

@ -0,0 +1,75 @@
use std::cell::Cell;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use std::time::Duration;
use futures::stream;
use futures::stream::Stream;
use gloo::timers::callback::Timeout;
#[inline(always)]
pub(crate) fn sleep(dur: Duration) -> impl Future<Output = ()> {
pub struct Sleep {
inner: Option<Timeout>,
dur_left: Option<u128>,
timeout_registered: Rc<Cell<bool>>,
}
impl Future for Sleep {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
static I32_MAX_U128: u128 = 2_147_483_647;
static I32_MAX_U32: u32 = 2_147_483_647;
// If polling before the registered timeout is reached, return Pending.
if self.timeout_registered.get() {
return Poll::Pending;
}
// set_timeout can only accept maximum of i32, so we wrap around if it gets longer.
let next_timeout = match self.dur_left.map(|m| (m, u32::try_from(m))) {
Some((m_u128, Err(_))) => {
self.dur_left = Some(m_u128 - I32_MAX_U128);
I32_MAX_U32
}
Some((m_u128, _)) if m_u128 > I32_MAX_U128 => {
self.dur_left = Some(m_u128 - I32_MAX_U128);
I32_MAX_U32
}
Some((_, Ok(m_u32))) => {
self.dur_left = None;
m_u32
}
None => return Poll::Ready(()),
};
let waker = cx.waker().clone();
self.timeout_registered.set(true);
let timeout_registered = self.timeout_registered.clone();
self.inner = Some(Timeout::new(next_timeout, move || {
timeout_registered.set(false);
waker.wake();
}));
Poll::Pending
}
}
Sleep {
inner: None,
dur_left: Some(dur.as_millis()),
timeout_registered: Cell::new(false).into(),
}
}
pub(crate) fn interval(dur: Duration) -> impl Stream<Item = ()> {
stream::unfold((), move |_: ()| async move {
sleep(dur).await;
Some(((), ()))
})
}

View File

@ -0,0 +1,20 @@
//! Utilities for bridging time and tasks.
use std::future::Future;
use std::time::Duration;
use futures::stream::Stream;
use crate::platform::imp::time as imp;
/// Waits until duration has elapsed.
#[inline(always)]
pub fn sleep(dur: Duration) -> impl Future<Output = ()> {
imp::sleep(dur)
}
/// Creates a Stream that yields an item after every period has elapsed.
#[inline(always)]
pub fn interval(period: Duration) -> impl Stream<Item = ()> {
imp::interval(period)
}

View File

@ -65,8 +65,8 @@ mod ssr_tests {
use tokio::task::{spawn_local, LocalSet};
use tokio::test;
use tokio::time::sleep;
use crate::platform::time::sleep;
use crate::prelude::*;
use crate::suspense::{Suspension, SuspensionResult};
use crate::ServerRenderer;

View File

@ -15,5 +15,5 @@ pub fn obtain_result_by_id(id: &str) -> String {
}
pub fn output_element() -> web_sys::Element {
gloo_utils::document().get_element_by_id("output").unwrap()
gloo::utils::document().get_element_by_id("output").unwrap()
}

View File

@ -8,11 +8,11 @@ use std::time::Duration;
mod common;
use common::{obtain_result, obtain_result_by_id};
use gloo::timers::future::sleep;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use wasm_bindgen_test::*;
use web_sys::{HtmlElement, HtmlTextAreaElement};
use yew::platform::time::sleep;
use yew::prelude::*;
use yew::suspense::{use_future, Suspension, SuspensionResult};
use yew::{Renderer, ServerRenderer};
@ -769,7 +769,7 @@ async fn hydration_suspense_no_flickering() {
#[hook]
pub fn use_suspend() -> SuspensionResult<()> {
use_future(|| async {
gloo::timers::future::sleep(std::time::Duration::from_millis(200)).await;
yew::platform::time::sleep(std::time::Duration::from_millis(200)).await;
})?;
Ok(())
}

View File

@ -7,9 +7,9 @@ wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
use std::time::Duration;
use common::obtain_result;
use gloo::timers::future::sleep;
use wasm_bindgen_futures::spawn_local;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
#[wasm_bindgen_test]

View File

@ -5,8 +5,8 @@ mod common;
use std::time::Duration;
use common::obtain_result;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

View File

@ -2,21 +2,21 @@
mod common;
use common::obtain_result;
use wasm_bindgen_test::*;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use gloo::timers::future::TimeoutFuture;
use common::obtain_result;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use wasm_bindgen_test::*;
use web_sys::{HtmlElement, HtmlTextAreaElement};
use yew::platform::time::sleep;
use yew::prelude::*;
use yew::suspense::{use_future, use_future_with_deps, Suspension, SuspensionResult};
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
async fn suspense_works() {
#[derive(PartialEq)]
@ -29,7 +29,7 @@ async fn suspense_works() {
let (s, handle) = Suspension::new();
spawn_local(async move {
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
handle.resume();
});
@ -100,11 +100,11 @@ async fn suspense_works() {
yew::Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
.render();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -112,7 +112,7 @@ async fn suspense_works() {
r#"<div class="content-area"><div class="actual-result">0</div><button class="increase">increase</button><div class="action-area"><button class="take-a-break">Take a break!</button></div></div>"#
);
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
gloo::utils::document()
.query_selector(".increase")
@ -122,7 +122,7 @@ async fn suspense_works() {
.unwrap()
.click();
TimeoutFuture::new(0).await;
sleep(Duration::ZERO).await;
gloo::utils::document()
.query_selector(".increase")
@ -132,7 +132,7 @@ async fn suspense_works() {
.unwrap()
.click();
TimeoutFuture::new(1).await;
sleep(Duration::from_millis(1)).await;
let result = obtain_result();
assert_eq!(
@ -148,11 +148,11 @@ async fn suspense_works() {
.unwrap()
.click();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -181,7 +181,7 @@ async fn suspense_not_suspended_at_start() {
let (s, handle) = Suspension::new();
spawn_local(async move {
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
handle.resume();
});
@ -250,7 +250,7 @@ async fn suspense_not_suspended_at_start() {
yew::Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
.render();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(
@ -265,11 +265,11 @@ async fn suspense_not_suspended_at_start() {
.unwrap()
.click();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -290,7 +290,7 @@ async fn suspense_nested_suspense_works() {
let (s, handle) = Suspension::new();
spawn_local(async move {
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
handle.resume();
});
@ -369,11 +369,11 @@ async fn suspense_nested_suspense_works() {
yew::Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
.render();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...(outer)</div>");
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -381,7 +381,7 @@ async fn suspense_nested_suspense_works() {
r#"<div class="content-area"><div class="action-area"><button class="take-a-break">Take a break!</button></div><div>wait...(inner)</div></div>"#
);
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -397,14 +397,14 @@ async fn suspense_nested_suspense_works() {
.unwrap()
.click();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(
result.as_str(),
r#"<div class="content-area"><div class="action-area"><button class="take-a-break">Take a break!</button></div><div>wait...(inner)</div></div>"#
);
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -425,7 +425,7 @@ async fn effects_not_run_when_suspended() {
let (s, handle) = Suspension::new();
spawn_local(async move {
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
handle.resume();
});
@ -528,12 +528,12 @@ async fn effects_not_run_when_suspended() {
)
.render();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
assert_eq!(*counter.borrow(), 0); // effects not called.
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -542,7 +542,7 @@ async fn effects_not_run_when_suspended() {
);
assert_eq!(*counter.borrow(), 1); // effects ran 1 time.
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
gloo::utils::document()
.query_selector(".increase")
@ -552,7 +552,7 @@ async fn effects_not_run_when_suspended() {
.unwrap()
.click();
TimeoutFuture::new(0).await;
sleep(Duration::ZERO).await;
gloo::utils::document()
.query_selector(".increase")
@ -562,7 +562,7 @@ async fn effects_not_run_when_suspended() {
.unwrap()
.click();
TimeoutFuture::new(0).await;
sleep(Duration::from_millis(0)).await;
let result = obtain_result();
assert_eq!(
@ -579,12 +579,12 @@ async fn effects_not_run_when_suspended() {
.unwrap()
.click();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
assert_eq!(*counter.borrow(), 3); // effects ran 3 times.
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(
@ -599,7 +599,7 @@ async fn use_suspending_future_works() {
#[function_component(Content)]
fn content() -> HtmlResult {
let _sleep_handle = use_future(|| async move {
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
})?;
Ok(html! {
@ -625,11 +625,11 @@ async fn use_suspending_future_works() {
yew::Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
.render();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(result.as_str(), r#"<div>Content</div>"#);
@ -639,14 +639,14 @@ async fn use_suspending_future_works() {
async fn use_suspending_future_with_deps_works() {
#[derive(PartialEq, Properties)]
struct ContentProps {
delay_millis: u32,
delay_millis: u64,
}
#[function_component(Content)]
fn content(ContentProps { delay_millis }: &ContentProps) -> HtmlResult {
let delayed_result = use_future_with_deps(
|delay_millis| async move {
TimeoutFuture::new(*delay_millis).await;
sleep(Duration::from_millis(*delay_millis)).await;
42
},
*delay_millis,
@ -675,11 +675,11 @@ async fn use_suspending_future_with_deps_works() {
yew::Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
.render();
TimeoutFuture::new(10).await;
sleep(Duration::from_millis(10)).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");
TimeoutFuture::new(50).await;
sleep(Duration::from_millis(50)).await;
let result = obtain_result();
assert_eq!(result.as_str(), r#"<div>42</div>"#);

View File

@ -7,8 +7,8 @@ mod common;
use std::time::Duration;
use common::obtain_result;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

View File

@ -6,8 +6,8 @@ use std::rc::Rc;
use std::time::Duration;
use common::obtain_result_by_id;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

View File

@ -7,8 +7,8 @@ use std::rc::Rc;
use std::time::Duration;
use common::obtain_result;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

View File

@ -7,8 +7,8 @@ mod common;
use std::time::Duration;
use common::obtain_result;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

View File

@ -7,8 +7,8 @@ use std::time::Duration;
mod common;
use common::obtain_result_by_id;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
use yew::{Renderer, ServerRenderer};

View File

@ -4,11 +4,11 @@ use std::collections::HashSet;
use std::rc::Rc;
use std::time::Duration;
use gloo::timers::future::sleep;
use gloo::utils::document;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
use web_sys::HtmlElement;
use yew::platform::time::sleep;
use yew::prelude::*;
mod common;

View File

@ -6,8 +6,8 @@ use std::ops::DerefMut;
use std::time::Duration;
use common::obtain_result;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

View File

@ -5,8 +5,8 @@ mod common;
use std::time::Duration;
use common::obtain_result;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

View File

@ -6,8 +6,8 @@ use std::time::Duration;
mod common;
use common::obtain_result_by_id;
use gloo::timers::future::sleep;
use wasm_bindgen_test::*;
use yew::platform::time::sleep;
use yew::prelude::*;
use yew::{Renderer, ServerRenderer};