From 7e6cea3a1389c439dcc93f3b1d4336fb9bb15c2b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 22 Nov 2022 10:05:13 -0800 Subject: [PATCH] wip: continue to tweak suspense --- examples/dog_app.rs | 5 +- packages/core/src/any_props.rs | 8 -- packages/core/src/create.rs | 103 +++++++++--------------- packages/core/src/factory.rs | 1 - packages/core/src/nodes.rs | 13 ++- packages/core/src/scheduler/suspense.rs | 3 + packages/core/src/virtual_dom.rs | 33 +++++++- packages/core/tests/suspend.rs | 6 +- packages/interpreter/src/interpreter.js | 2 + 9 files changed, 90 insertions(+), 84 deletions(-) diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 7f868c235..363d85f4b 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -63,7 +63,10 @@ async fn breed_pic(cx: Scope, breed: String) -> Element { Ok(resp) => render! { div { button { - onclick: move |_| fut.restart(), + onclick: move |_| { + println!("clicked"); + fut.restart() + }, "Click to fetch another doggo" } img { diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index 3ff59e04d..532fe02ca 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -22,14 +22,6 @@ pub(crate) struct VProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> { _marker: PhantomData, } -enum PropsAllocation

{ - /// If it's memoized, then the heap owns the props - Memoized(*mut P), - - /// If it's borrowed, then the parent owns the props - Borrowed(P), -} - impl<'a, P, A, F> VProps<'a, P, A, F> where F: ComponentReturn<'a, A>, diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index c5b6c97c1..4e6611235 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -19,18 +19,10 @@ impl VirtualDom { mutations: &mut Mutations<'a>, template: &'a VNode<'a>, ) -> usize { - let mutations_to_this_point = mutations.len(); - self.scope_stack.push(scope); let out = self.create(mutations, template); self.scope_stack.pop(); - if !self.collected_leaves.is_empty() { - if let Some(boundary) = self.scopes[scope.0].has_context::() { - println!("Boundary detected and pending leaves!"); - } - } - out } @@ -338,98 +330,79 @@ impl VirtualDom { use RenderReturn::*; match return_nodes { - Sync(Some(t)) => self.create_component(mutations, scope, t, idx, component), + Sync(Some(t)) => self.mount_component(mutations, scope, t, idx), Sync(None) | Async(_) => { - self.create_component_placeholder(template, idx, component, scope, mutations) + self.mount_component_placeholder(template, idx, scope, mutations) } } } - fn create_component<'a>( + fn mount_component<'a>( &mut self, mutations: &mut Mutations<'a>, scope: ScopeId, template: &'a VNode<'a>, idx: usize, - component: &'a VComponent<'a>, ) -> usize { - // // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary - // // is encountered - // let mutations_to_this_point = mutations.len(); + // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary + // is encountered + let mutations_to_this_point = mutations.len(); // Create the component's root element let created = self.create_scope(scope, mutations, template); - if !self.collected_leaves.is_empty() { - println!("collected leaves: {:?}", self.collected_leaves); + // If there are no suspense leaves below us, then just don't bother checking anything suspense related + if self.collected_leaves.is_empty() { + return created; } - created + // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense + let boundary = match self.scopes[scope.0].has_context::() { + Some(boundary) => boundary, + _ => return created, + }; - // // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense - // let boundary = match self.scopes[scope.0].has_context::() { - // Some(boundary) if !self.collected_leaves.is_empty() => boundary, - // _ => return created, - // }; + // Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree + let new_id = self.next_element(template, template.template.node_paths[idx]); - // // Since this is a boundary, use it as a placeholder - // let new_id = self.next_element(template, template.template.node_paths[idx]); - // component.placeholder.set(Some(new_id)); - // self.scopes[scope.0].placeholder.set(Some(new_id)); - // mutations.push(AssignId { - // id: new_id, - // path: &template.template.node_paths[idx][1..], - // }); + // Now connect everything to the boundary + self.scopes[scope.0].placeholder.set(Some(new_id)); - // // Now connect everything to the boundary - // let boundary_mut = boundary; - // let split_off = mutations.split_off(mutations_to_this_point); - // let split_off: Vec = unsafe { std::mem::transmute(split_off) }; + // This involves breaking off the mutations to this point, and then creating a new placeholder for the boundary + // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately + let split_off = unsafe { + std::mem::transmute::, Vec>( + mutations.split_off(mutations_to_this_point), + ) + }; + boundary.mutations.borrow_mut().edits.extend(split_off); + boundary.created_on_stack.set(created); + boundary + .waiting_on + .borrow_mut() + .extend(self.collected_leaves.drain(..)); - // if boundary_mut.placeholder.get().is_none() { - // boundary_mut.placeholder.set(Some(new_id)); - // } + // Now assign the placeholder in the DOM + mutations.push(AssignId { + id: new_id, + path: &template.template.node_paths[idx][1..], + }); - // // In the generated edits, we want to pick off from where we left off. - // boundary_mut.mutations.borrow_mut().edits.extend(split_off); - - // boundary_mut - // .waiting_on - // .borrow_mut() - // .extend(self.collected_leaves.drain(..)); - - // 0 - - // let boudary = self.scopes[scope.0] - // .consume_context::() - // .unwrap(); - - // boudary - // .waiting_on - // .borrow_mut() - // .extend(self.collected_leaves.drain(..)); - - // if boudary.placeholder.get().is_none() { - // boudary.placeholder.set(Some(new_id)); - // } + 0 } /// Take the rendered nodes from a component and handle them if they were async /// /// IE simply assign an ID to the placeholder - fn create_component_placeholder( + fn mount_component_placeholder( &mut self, template: &VNode, idx: usize, - component: &VComponent, scope: ScopeId, mutations: &mut Mutations, ) -> usize { let new_id = self.next_element(template, template.template.node_paths[idx]); - // Set the placeholder of the component - component.placeholder.set(Some(new_id)); - // Set the placeholder of the scope self.scopes[scope.0].placeholder.set(Some(new_id)); diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 773fc5c29..3869d1053 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -92,7 +92,6 @@ impl ScopeState { render_fn: component as *const (), static_props: P::IS_STATIC, props: Cell::new(Some(extended)), - placeholder: Cell::new(None), scope: Cell::new(None), }) } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 61356e6a5..1c98b9d1f 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -39,7 +39,16 @@ pub struct VNode<'a> { } impl<'a> VNode<'a> { - pub fn single_component( + pub fn placeholder_template(cx: &'a ScopeState) -> Self { + Self::template_from_dynamic_node( + cx, + DynamicNode::Placeholder(Cell::new(ElementId(0))), + "dioxus-placeholder", + ) + .unwrap() + } + + pub fn template_from_dynamic_node( cx: &'a ScopeState, node: DynamicNode<'a>, id: &'static str, @@ -134,7 +143,6 @@ impl<'a> DynamicNode<'a> { pub struct VComponent<'a> { pub name: &'static str, pub static_props: bool, - pub placeholder: Cell>, pub scope: Cell>, pub props: Cell + 'a>>>, pub render_fn: *const (), @@ -145,7 +153,6 @@ impl<'a> std::fmt::Debug for VComponent<'a> { f.debug_struct("VComponent") .field("name", &self.name) .field("static_props", &self.static_props) - .field("placeholder", &self.placeholder) .field("scope", &self.scope) .finish() } diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index 8e252364f..2fd879258 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/packages/core/src/scheduler/suspense.rs @@ -20,6 +20,8 @@ pub struct SuspenseBoundary { pub mutations: RefCell>, pub placeholder: Cell>, + pub created_on_stack: Cell, + // whenever the suspense resolves, we call this onresolve function // this lets us do things like putting up a loading spinner // @@ -41,6 +43,7 @@ impl SuspenseBoundary { waiting_on: Default::default(), mutations: RefCell::new(Mutations::new(0)), placeholder: Cell::new(None), + created_on_stack: Cell::new(0), onresolve: None, onstart: None, }) diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 07a7e6c23..f0c29ca51 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -19,6 +19,7 @@ use futures_util::{pin_mut, StreamExt}; use slab::Slab; use std::{ any::Any, + borrow::BorrowMut, cell::Cell, collections::{BTreeSet, HashMap}, future::Future, @@ -541,9 +542,35 @@ impl VirtualDom { self.dirty_scopes.remove(&dirty); // if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update - if !self.is_scope_suspended(dirty.id) { - self.run_scope(dirty.id); - self.diff_scope(&mut mutations, dirty.id); + if self.is_scope_suspended(dirty.id) { + continue; + } + + // Save the current mutations length so we can split them into boundary + let mutations_to_this_point = mutations.len(); + + self.diff_scope(&mut mutations, dirty.id); + + // If suspended leaves are present, then we should find the boundary for this scope and attach things + // No placeholder necessary since this is a diff + if !self.collected_leaves.is_empty() { + let mut boundary = self.scopes[dirty.id.0] + .consume_context::() + .unwrap(); + + let boundary_mut = boundary.borrow_mut(); + + // Attach mutations + boundary_mut + .mutations + .borrow_mut() + .extend(mutations.split_off(mutations_to_this_point)); + + // Attach suspended leaves + boundary + .waiting_on + .borrow_mut() + .extend(self.collected_leaves.drain(..)); } } diff --git a/packages/core/tests/suspend.rs b/packages/core/tests/suspend.rs index df6dcb413..18743c26a 100644 --- a/packages/core/tests/suspend.rs +++ b/packages/core/tests/suspend.rs @@ -15,7 +15,7 @@ async fn it_works() { fn app(cx: Scope) -> Element { println!("running root app"); - VNode::single_component( + VNode::template_from_dynamic_node( cx, cx.component(suspense_boundary, (), "suspense_boundary"), "app", @@ -29,7 +29,7 @@ fn suspense_boundary(cx: Scope) -> Element { cx.provide_context(Rc::new(RefCell::new(SuspenseBoundary::new(cx.scope_id())))) }); - VNode::single_component(cx, cx.component(async_child, (), "async_child"), "app") + VNode::template_from_dynamic_node(cx, cx.component(async_child, (), "async_child"), "app") } async fn async_child(cx: Scope<'_>) -> Element { @@ -47,7 +47,7 @@ async fn async_child(cx: Scope<'_>) -> Element { println!("Future awaited and complete"); - VNode::single_component(cx, cx.component(async_text, (), "async_text"), "app") + VNode::template_from_dynamic_node(cx, cx.component(async_text, (), "async_text"), "app") } async fn async_text(cx: Scope<'_>) -> Element { diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index a663f9278..df9bed8db 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -323,6 +323,8 @@ export class Interpreter { this.RemoveEventListener(edit.id, edit.event_name); break; case "NewEventListener": + // console.log("creating listener! ", edit); + // this handler is only provided on desktop implementations since this // method is not used by the web implementation let handler = (event) => {