From f0128a3150faede0ca6f9f610ff45995a6bca4f9 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 14 Jul 2023 17:12:07 -0700 Subject: [PATCH] implement suspense using simpler approach --- packages/core/Cargo.toml | 1 + packages/core/src/scope_arena.rs | 2 + packages/core/src/scopes.rs | 2 +- packages/core/src/virtual_dom.rs | 4 +- packages/core/tests/suspense.rs | 101 ++++++------------------------- 5 files changed, 27 insertions(+), 83 deletions(-) diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 0b668f43d..a077a7379 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -40,6 +40,7 @@ tokio = { workspace = true, features = ["full"] } dioxus = { workspace = true } pretty_assertions = "1.3.0" rand = "0.8.5" +dioxus-ssr = { workspace = true } [features] default = [] diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 306afe16b..39b39f8c4 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -85,6 +85,8 @@ impl VirtualDom { if scope.suspended.get() { self.suspended_scopes.insert(scope.id); + } else if !self.suspended_scopes.is_empty() { + _ = self.suspended_scopes.remove(&scope.id); } // rebind the lifetime now that its stored internally diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 7bc26fb02..8d980222c 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -655,7 +655,7 @@ impl<'src> ScopeState { } /// Mark this component as suspended and then return None - pub fn suspend(&self) -> Option<()> { + pub fn suspend(&self) -> Option { self.suspended.set(true); None } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1f056cfae..31a1b5912 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -572,10 +572,12 @@ impl VirtualDom { pub async fn wait_for_suspense(&mut self) { loop { if self.suspended_scopes.is_empty() { - break; + return; } self.wait_for_work().await; + + _ = self.render_immediate(); } } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 2b90a37da..2a64dc53c 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -1,103 +1,42 @@ -use dioxus::core::ElementId; use dioxus::core::Mutation::*; use dioxus::prelude::*; use std::future::IntoFuture; use std::rc::Rc; use std::time::Duration; -#[test] -fn it_works() { +#[tokio::test] +async fn it_works() { // wait just a moment, not enough time for the boundary to resolve - tokio::runtime::Builder::new_current_thread() - .enable_time() - .build() - .unwrap() - .block_on(async { - let mut dom = VirtualDom::new(app); + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + dom.wait_for_suspense().await; + let out = dioxus_ssr::pre_render(&dom); - { - let mutations = dom.rebuild().santize(); + assert_eq!(out, "
Waiting for... child
"); - // We should at least get the top-level template in before pausing for the children - // note: we dont test template edits anymore - // assert_eq!( - // mutations.templates, - // [ - // CreateElement { name: "div" }, - // CreateStaticText { value: "Waiting for child..." }, - // CreateStaticPlaceholder, - // AppendChildren { m: 2 }, - // SaveTemplate { name: "template", m: 1 } - // ] - // ); - - // And we should load it in and assign the placeholder properly - assert_eq!( - mutations.edits, - [ - LoadTemplate { name: "template", index: 0, id: ElementId(1) }, - // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly? - // can we even? - AssignId { path: &[1], id: ElementId(3) }, - AppendChildren { m: 1, id: ElementId(0) }, - ] - ); - } - - dom.wait_for_work().await; - }); + dbg!(out); } fn app(cx: Scope) -> Element { cx.render(rsx!( div { - "Waiting for child..." - suspense_boundary {} + "Waiting for... " + suspended_child {} } )) } -fn suspense_boundary(cx: Scope) -> Element { - todo!() - // cx.use_hook(|| { - // cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))); - // }); +fn suspended_child(cx: Scope) -> Element { + let mut val = use_state(cx, || 0); - // // Ensure the right types are found - // cx.has_context::>().unwrap(); + if **val < 3 { + let mut val = val.clone(); + cx.spawn(async move { + val += 1; + }); + return cx.suspend()?; + } - // cx.render(rsx!(async_child {})) -} - -fn async_child(cx: Scope<'_>) -> Element { - todo!() - // use_future!(cx, || tokio::time::sleep(Duration::from_millis(10))).await; - // cx.render(rsx!(async_text {})) -} - -fn async_text(cx: Scope<'_>) -> Element { - todo!() - // let username = use_future!(cx, || async { - // tokio::time::sleep(std::time::Duration::from_secs(1)).await; - // "async child 1" - // }); - - // let age = use_future!(cx, || async { - // tokio::time::sleep(std::time::Duration::from_secs(2)).await; - // 1234 - // }); - - // let (_user, _age) = use_future!(cx, || async { - // tokio::join!( - // tokio::time::sleep(std::time::Duration::from_secs(1)), - // tokio::time::sleep(std::time::Duration::from_secs(2)) - // ); - // ("async child 1", 1234) - // }) - // .await; - - // let (username, age) = tokio::join!(username.into_future(), age.into_future()); - - // cx.render(rsx!( div { "Hello! {username}, you are {age}, {_user} {_age}" } )) + render!("child") }