Skip to content

Commit

Permalink
Introduce use_future_with_deps (#2615)
Browse files Browse the repository at this point in the history
* introduce use_future_with_deps

* add documentation to use_future

* use_future_with_deps now uses Rc<D> instead of &D

* fix expansion and simplify use_future
  • Loading branch information
WorldSEnder committed Apr 17, 2022
1 parent c5b7790 commit 504693f
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 38 deletions.
1 change: 1 addition & 0 deletions packages/yew/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ tokio = { version = "1.15.0", features = ["rt"], optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3"
gloo = { version = "0.7", features = ["futures"] }
gloo-net = { version = "0.2", features = ["http"] }
wasm-bindgen-futures = "0.4"
rustversion = "1"
trybuild = "1"
Expand Down
2 changes: 1 addition & 1 deletion packages/yew/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ args = [
"test",
"--doc",
"--features",
"doc_test,wasm_test",
"tokio,doc_test,wasm_test",
]

[tasks.ssr-test]
Expand Down
63 changes: 39 additions & 24 deletions packages/yew/src/functional/hooks/use_memo.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
use std::cell::RefCell;
use std::rc::Rc;
use super::use_mut_ref;
use std::{borrow::Borrow, rc::Rc};

use crate::functional::{hook, use_state};
use crate::functional::hook;

/// Get a immutable reference to a memoized value.
///
/// This version allows for a key cache key derivation that only borrows
/// like the original argument. For example, using ` K = Rc<D>`, we only
/// create a shared reference to dependencies *after* they change.
#[hook]
pub(crate) fn use_memo_base<T, F, D, K>(f: F, deps: D) -> Rc<T>
where
T: 'static,
F: FnOnce(D) -> (T, K),
K: 'static + Borrow<D>,
D: PartialEq,
{
struct MemoState<T, K> {
memo_key: K,
result: Rc<T>,
}
let state = use_mut_ref(|| -> Option<MemoState<T, K>> { None });

let mut state = state.borrow_mut();
match &*state {
Some(existing) if existing.memo_key.borrow() != &deps => {
// Drop old state if it's outdated
*state = None;
}
_ => {}
};
let state = state.get_or_insert_with(|| {
let (result, memo_key) = f(deps);
let result = Rc::new(result);
MemoState { result, memo_key }
});
state.result.clone()
}

/// Get a immutable reference to a memoized value.
///
Expand Down Expand Up @@ -39,25 +74,5 @@ where
F: FnOnce(&D) -> T,
D: 'static + PartialEq,
{
let val = use_state(|| -> RefCell<Option<Rc<T>>> { RefCell::new(None) });
let last_deps = use_state(|| -> RefCell<Option<D>> { RefCell::new(None) });

let mut val = val.borrow_mut();
let mut last_deps = last_deps.borrow_mut();

match (
val.as_ref(),
last_deps.as_ref().and_then(|m| (m != &deps).then(|| ())),
) {
// Previous value exists and last_deps == deps
(Some(m), None) => m.clone(),
_ => {
let new_val = Rc::new(f(&deps));
*last_deps = Some(deps);

*val = Some(new_val.clone());

new_val
}
}
use_memo_base(|d| (f(&d), d), deps)
}
19 changes: 15 additions & 4 deletions packages/yew/src/functional/hooks/use_ref.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
use std::cell::RefCell;
use std::rc::Rc;

use crate::functional::{hook, use_memo, use_state};
use crate::functional::{hook, use_state, Hook, HookContext};
use crate::NodeRef;

struct UseMutRef<F> {
init_fn: F,
}

impl<T: 'static, F: FnOnce() -> T> Hook for UseMutRef<F> {
type Output = Rc<RefCell<T>>;

fn run(self, ctx: &mut HookContext) -> Self::Output {
ctx.next_state(|_| RefCell::new((self.init_fn)()))
}
}

/// This hook is used for obtaining a mutable reference to a stateful value.
/// Its state persists across renders.
///
Expand Down Expand Up @@ -50,12 +62,11 @@ use crate::NodeRef;
/// }
/// }
/// ```
#[hook]
pub fn use_mut_ref<T: 'static, F>(init_fn: F) -> Rc<RefCell<T>>
pub fn use_mut_ref<T: 'static, F>(init_fn: F) -> impl Hook<Output = Rc<RefCell<T>>>
where
F: FnOnce() -> T,
{
use_memo(|_| RefCell::new(init_fn()), ())
UseMutRef { init_fn }
}

/// This hook is used for obtaining a [`NodeRef`].
Expand Down
83 changes: 75 additions & 8 deletions packages/yew/src/suspense/hooks.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#[cfg_attr(documenting, doc(cfg(any(target_arch = "wasm32", feature = "tokio"))))]
#[cfg(any(target_arch = "wasm32", feature = "tokio"))]
mod feat_futures {
use std::cell::Cell;
use std::fmt;
use std::future::Future;
use std::ops::Deref;
use std::rc::Rc;

use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};
Expand Down Expand Up @@ -32,25 +34,90 @@ mod feat_futures {
}
}

/// Use the result of an async computation, suspending while waiting.
///
/// Awaits the future returned from the first call to `init_f`, and returns
/// its result in a [`UseFutureHandle`]. Always suspends initially, even if
/// the future is immediately [ready].
///
/// [ready]: std::task::Poll::Ready
///
/// # Example
///
/// ```
/// # use yew::prelude::*;
/// # use yew::suspense::use_future;
/// use gloo_net::http::Request;
///
/// const URL: &str = "https://en.wikipedia.org/w/api.php?\
/// action=query&origin=*&format=json&generator=search&\
/// gsrnamespace=0&gsrlimit=5&gsrsearch='New_England_Patriots'";
///
/// #[function_component]
/// fn WikipediaSearch() -> HtmlResult {
/// let res = use_future(|| async { Request::new(URL).send().await?.text().await })?;
/// let result_html = match *res {
/// Ok(ref res) => html! { res },
/// Err(ref failure) => failure.to_string().into(),
/// };
/// Ok(html! {
/// <p>
/// {"Wikipedia search result: "}
/// {result_html}
/// </p>
/// })
/// }
/// ```
#[hook]
pub fn use_future<F, T, O>(init_f: F) -> SuspensionResult<UseFutureHandle<O>>
where
F: FnOnce() -> T,
T: Future<Output = O> + 'static,
O: 'static,
{
use_future_with_deps(move |_| init_f(), ())
}

/// Use the result of an async computation with dependencies, suspending while waiting.
///
/// Awaits the future returned from `f` for the latest `deps`. Even if the future is immediately
/// [ready], the hook suspends at least once. If the dependencies
/// change while a future is still pending, the result is never used. This guarantees that your
/// component always sees up-to-date values while it is not suspended.
///
/// [ready]: std::task::Poll::Ready
#[hook]
pub fn use_future<F, T, O>(f: F) -> SuspensionResult<UseFutureHandle<O>>
pub fn use_future_with_deps<F, D, T, O>(f: F, deps: D) -> SuspensionResult<UseFutureHandle<O>>
where
F: FnOnce() -> T + 'static,
F: FnOnce(Rc<D>) -> T,
T: Future<Output = O> + 'static,
O: 'static,
D: PartialEq + 'static,
{
let output = use_state(|| None);
// We only commit a result if it comes from the latest spawned future. Otherwise, this
// might trigger pointless updates or even override newer state.
let latest_id = use_state(|| Cell::new(0u32));

let suspension = {
let output = output.clone();

use_memo(
move |_| {
Suspension::from_future(async move {
output.set(Some(f().await));
})
use_memo_base(
move |deps| {
let self_id = latest_id.get().wrapping_add(1);
// As long as less than 2**32 futures are in flight wrapping_add is fine
(*latest_id).set(self_id);
let deps = Rc::new(deps);
let task = f(deps.clone());
let suspension = Suspension::from_future(async move {
let result = task.await;
if latest_id.get() == self_id {
output.set(Some(result));
}
});
(suspension, deps)
},
(),
deps,
)
};

Expand Down
52 changes: 51 additions & 1 deletion packages/yew/tests/suspense.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use gloo::timers::future::TimeoutFuture;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use web_sys::{HtmlElement, HtmlTextAreaElement};
use yew::suspense::{use_future, Suspension, SuspensionResult};
use yew::suspense::{use_future, use_future_with_deps, Suspension, SuspensionResult};

#[wasm_bindgen_test]
async fn suspense_works() {
Expand Down Expand Up @@ -634,3 +634,53 @@ async fn use_suspending_future_works() {
let result = obtain_result();
assert_eq!(result.as_str(), r#"<div>Content</div>"#);
}

#[wasm_bindgen_test]
async fn use_suspending_future_with_deps_works() {
#[derive(PartialEq, Properties)]
struct ContentProps {
delay_millis: u32,
}

#[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;
42
},
*delay_millis,
)?;

Ok(html! {
<div>
{*delayed_result}
</div>
})
}

#[function_component(App)]
fn app() -> Html {
let fallback = html! {<div>{"wait..."}</div>};

html! {
<div id="result">
<Suspense {fallback}>
<Content delay_millis={50} />
</Suspense>
</div>
}
}

yew::Renderer::<App>::with_root(gloo_utils::document().get_element_by_id("output").unwrap())
.render();

TimeoutFuture::new(10).await;
let result = obtain_result();
assert_eq!(result.as_str(), "<div>wait...</div>");

TimeoutFuture::new(50).await;

let result = obtain_result();
assert_eq!(result.as_str(), r#"<div>42</div>"#);
}

1 comment on commit 504693f

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yew master branch benchmarks (Lower is better)

Benchmark suite Current: 504693f Previous: c5b7790 Ratio
yew-struct-keyed 01_run1k 171.6885 161.78449999999998 1.06
yew-struct-keyed 02_replace1k 196.066 187.0295 1.05
yew-struct-keyed 03_update10th1k_x16 404.02 334.1155 1.21
yew-struct-keyed 04_select1k 74.086 49.329 1.50
yew-struct-keyed 05_swap1k 89.103 71.294 1.25
yew-struct-keyed 06_remove-one-1k 28.2605 25.76 1.10
yew-struct-keyed 07_create10k 3002.531 2860.7435 1.05
yew-struct-keyed 08_create1k-after1k_x2 428.198 395.7595 1.08
yew-struct-keyed 09_clear1k_x8 189.66000000000005 185.153 1.02
yew-struct-keyed 21_ready-memory 1.457233428955078 1.457233428955078 1
yew-struct-keyed 22_run-memory 1.6610145568847656 1.6945228576660156 0.98
yew-struct-keyed 23_update5-memory 1.6644821166992188 1.6934852600097656 0.98
yew-struct-keyed 24_run5-memory 1.944618225097656 1.9446907043457031 1.00
yew-struct-keyed 25_run-clear-memory 1.3275680541992188 1.425739288330078 0.93
yew-struct-keyed 31_startup-ci 1881.015 1731.796 1.09
yew-struct-keyed 32_startup-bt 31.936000000000007 27.288000000000004 1.17
yew-struct-keyed 33_startup-mainthreadcost 250.924 222.696 1.13
yew-struct-keyed 34_startup-totalbytes 328.7392578125 328.7392578125 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.