ソースを参照

wip: continue to tweak suspense

Jonathan Kelley 2 年 前
コミット
7e6cea3a13

+ 4 - 1
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 {

+ 0 - 8
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<A>,
 }
 
-enum PropsAllocation<P> {
-    /// 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>,

+ 43 - 70
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::<SuspenseContext>() {
-                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::<SuspenseContext>() {
-        //     Some(boundary) if !self.collected_leaves.is_empty() => boundary,
-        //     _ => return created,
-        // };
-
-        // // 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
-        // let boundary_mut = boundary;
-        // let split_off = mutations.split_off(mutations_to_this_point);
-        // let split_off: Vec<Mutation> = unsafe { std::mem::transmute(split_off) };
-
-        // if boundary_mut.placeholder.get().is_none() {
-        //     boundary_mut.placeholder.set(Some(new_id));
-        // }
-
-        // // 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::<SuspenseContext>()
-        //     .unwrap();
-
-        // boudary
-        //     .waiting_on
-        //     .borrow_mut()
-        //     .extend(self.collected_leaves.drain(..));
-
-        // if boudary.placeholder.get().is_none() {
-        //     boudary.placeholder.set(Some(new_id));
-        // }
+        // 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::<SuspenseContext>() {
+            Some(boundary) => 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]);
+
+        // Now connect everything to the boundary
+        self.scopes[scope.0].placeholder.set(Some(new_id));
+
+        // 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<Mutation>, Vec<Mutation>>(
+                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(..));
+
+        // Now assign the placeholder in the DOM
+        mutations.push(AssignId {
+            id: new_id,
+            path: &template.template.node_paths[idx][1..],
+        });
+
+        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));
 

+ 0 - 1
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),
         })
     }

+ 10 - 3
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<Option<ElementId>>,
     pub scope: Cell<Option<ScopeId>>,
     pub props: Cell<Option<Box<dyn AnyProps<'a> + '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()
     }

+ 3 - 0
packages/core/src/scheduler/suspense.rs

@@ -20,6 +20,8 @@ pub struct SuspenseBoundary {
     pub mutations: RefCell<Mutations<'static>>,
     pub placeholder: Cell<Option<ElementId>>,
 
+    pub created_on_stack: Cell<usize>,
+
     // 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,
         })

+ 30 - 3
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::<SuspenseContext>()
+                        .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(..));
                 }
             }
 

+ 3 - 3
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 {

+ 2 - 0
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) => {