1
0
Эх сурвалжийг харах

compiling, tests not passing

Jonathan Kelley 1 долоо хоног өмнө
parent
commit
50864df5d5

+ 421 - 70
packages/core/src/diff.rs

@@ -11,6 +11,7 @@ use std::{
     any::TypeId,
     iter::Peekable,
     ops::{Deref, DerefMut},
+    rc::Rc,
 };
 
 use crate::{
@@ -20,7 +21,7 @@ use crate::{
         ElementRef, MountId, ScopeOrder, SuspenseBoundaryProps, SuspenseBoundaryPropsWithOwner,
     },
     nodes::{AsVNode, VNode, VNodeMount},
-    prelude::{SuspenseBoundary, SuspenseContext},
+    prelude::{RuntimeGuard, SuspenseBoundary, SuspenseContext},
     scopes::ScopeId,
     Attribute, AttributeValue, DynamicNode, Element, ElementId, Runtime, ScopeState, TemplateNode,
     VComponent, VText, VirtualDom, WriteMutations,
@@ -28,38 +29,39 @@ use crate::{
 use rustc_hash::{FxHashMap, FxHashSet};
 use slab::Slab;
 
-// use crate::innerlude::MountId;
-// use crate::{Attribute, AttributeValue, DynamicNode::*};
-// use crate::{VNode, VirtualDomMutations};
-// use core::iter::Peekable;
-
-// use crate::{
-//     arena::ElementId,
-//     innerlude::{ElementPath, ElementRef, VNodeMount, VText},
-//     nodes::DynamicNode,
-//     scopes::ScopeId,
-//     TemplateNode,
-// };
-
 /// A fiber progresses a given work tree by running scopes and diffing nodes.
 /// It queues work internally such that suspended scopes can be paused and resumed.
 pub(crate) struct Fiber<'a, 'b, M: WriteMutations> {
-    runtime: &'a Runtime,
+    runtime: &'a Rc<Runtime>,
     dom: &'a mut VirtualDom,
     to: &'b mut M,
     write: bool,
 }
 
 impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
+    pub(crate) fn new(
+        runtime: &'a Rc<Runtime>,
+        dom: &'a mut VirtualDom,
+        to: &'b mut M,
+        write: bool,
+    ) -> Self {
+        Self {
+            runtime,
+            dom,
+            to,
+            write,
+        }
+    }
+
     pub(crate) fn run_and_diff_scope(&mut self, scope_id: ScopeId) {
-        todo!()
-        // let scope = &mut self.dom.scopes[scope_id.0];
-        // if SuspenseBoundaryProps::downcast_from_props(&mut *scope.props).is_some() {
-        //     SuspenseBoundaryProps::diff(scope_id)
-        // } else {
-        //     let new_nodes = self.run_scope(scope_id);
-        //     self.diff_scope(scope_id, new_nodes);
-        // }
+        let scope = &mut self.dom.scopes[scope_id.0];
+        if SuspenseBoundaryProps::downcast_from_props(&mut *scope.props).is_some() {
+            todo!()
+            // SuspenseBoundaryProps::diff(scope_id)
+        } else {
+            let new_nodes = self.dom.run_scope(scope_id);
+            self.diff_scope(scope_id, new_nodes);
+        }
     }
 
     fn diff_scope(&mut self, scope: ScopeId, new_nodes: Element) {
@@ -90,7 +92,7 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
     /// Create a new [`Scope`](crate::scope_context::Scope) for a component.
     ///
     /// Returns the number of nodes created on the stack
-    fn create_scope(
+    pub(crate) fn create_scope(
         &mut self,
         scope: ScopeId,
         new_nodes: Element,
@@ -100,7 +102,6 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
             // If there are suspended scopes, we need to check if the scope is suspended before we diff it
             // If it is suspended, we need to diff it but write the mutations nothing
             // Note: It is important that we still diff the scope even if it is suspended, because the scope may render other child components which may change between renders
-            let mut render_to = to.filter(|_| self.runtime.scope_should_render(scope));
 
             // Create the node
             let nodes = self.create(new_nodes.as_vnode(), parent);
@@ -108,7 +109,7 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
             // Then set the new node as the last rendered node
             self.dom.scopes[scope.0].last_rendered_node = Some(new_nodes);
 
-            if render_to.is_some() {
+            if self.write && self.runtime.scope_should_render(scope) {
                 self.dom
                     .runtime
                     .get_state(scope)
@@ -686,8 +687,7 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
     ) -> usize {
         // If this is a suspense boundary, run our suspense creation logic instead of running the component
         if component.props.props().type_id() == TypeId::of::<SuspenseBoundaryPropsWithOwner>() {
-            todo!()
-            // return SuspenseBoundaryProps::create(mount, idx, component, parent);
+            return self.create_suspense_boundary(mount, idx, component, parent);
         }
 
         let mut scope_id = ScopeId(self.get_mounted_dyn_node(mount, idx));
@@ -838,21 +838,11 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
         };
     }
 
-    /// Try to get the dynamic node and its index for a root node
-    fn get_dynamic_root_node_and_id<'n>(
-        node: &'n VNode,
-        root_idx: usize,
-    ) -> Option<(usize, &'n DynamicNode)> {
-        node.template.roots[root_idx]
-            .dynamic_id()
-            .map(|id| (id, &node.dynamic_nodes[id]))
-    }
-
     fn find_first_element(&self, node: &VNode) -> ElementId {
         use DynamicNode::*;
 
         let mount_id = node.mount.get();
-        let first = match Self::get_dynamic_root_node_and_id(node, 0) {
+        let first = match get_dynamic_root_node_and_id(node, 0) {
             // This node is static, just get the root id
             None => self.get_mounted_root_node(mount_id, 0),
 
@@ -891,7 +881,7 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
 
         let mount_id = node.mount.get();
         let last_root_index = node.template.roots.len() - 1;
-        let last = match Self::get_dynamic_root_node_and_id(node, last_root_index) {
+        let last = match get_dynamic_root_node_and_id(node, last_root_index) {
             // This node is static, just get the root id
             None => self.get_mounted_root_node(mount_id, last_root_index),
 
@@ -1531,37 +1521,41 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
 
     /// Push all the root nodes on the stack
     fn push_all_root_nodes(&mut self, node: &VNode) -> usize {
-        let template = node.template;
-
-        let mounts = self.runtime.mounts.borrow();
-        let mount = mounts.get(node.mount.get().0).unwrap();
-
-        template
-            .roots
-            .iter()
-            .enumerate()
-            .map(
-                |(root_idx, _)| match Self::get_dynamic_root_node_and_id(node, root_idx) {
-                    Some((_, DynamicNode::Fragment(nodes))) => {
-                        let mut accumulated = 0;
-                        for node in nodes {
-                            accumulated += self.push_all_root_nodes(&node);
+        fn push_all_inner(node: &VNode, dom: &VirtualDom, to: &mut impl WriteMutations) -> usize {
+            let template = node.template;
+
+            let mounts = dom.runtime.mounts.borrow();
+            let mount = mounts.get(node.mount.get().0).unwrap();
+
+            template
+                .roots
+                .iter()
+                .enumerate()
+                .map(
+                    |(root_idx, _)| match get_dynamic_root_node_and_id(node, root_idx) {
+                        Some((_, DynamicNode::Fragment(nodes))) => {
+                            let mut accumulated = 0;
+                            for node in nodes {
+                                accumulated += push_all_inner(&node, dom, to);
+                            }
+                            accumulated
                         }
-                        accumulated
-                    }
-                    Some((idx, DynamicNode::Component(_))) => {
-                        let scope = ScopeId(mount.mounted_dynamic_nodes[idx]);
-                        let node = self.dom.get_scope(scope).unwrap().root_node();
-                        self.push_all_root_nodes(&node)
-                    }
-                    // This is a static root node or a single dynamic node, just push it
-                    None | Some((_, DynamicNode::Placeholder(_) | DynamicNode::Text(_))) => {
-                        self.to.push_root(mount.root_ids[root_idx]);
-                        1
-                    }
-                },
-            )
-            .sum()
+                        Some((idx, DynamicNode::Component(_))) => {
+                            let scope = ScopeId(mount.mounted_dynamic_nodes[idx]);
+                            let node = dom.get_scope(scope).unwrap().root_node();
+                            push_all_inner(&node, dom, to)
+                        }
+                        // This is a static root node or a single dynamic node, just push it
+                        None | Some((_, DynamicNode::Placeholder(_) | DynamicNode::Text(_))) => {
+                            to.push_root(mount.root_ids[root_idx]);
+                            1
+                        }
+                    },
+                )
+                .sum()
+        }
+
+        push_all_inner(node, &self.dom, self.to)
     }
 
     fn remove_suspended_nodes(&mut self, scope_id: ScopeId, destroy_component_state: bool) {
@@ -1618,6 +1612,337 @@ impl<'a, 'b, M: WriteMutations> Fiber<'a, 'b, M> {
         // If this scope was a suspense boundary, remove it from the resolved scopes
         self.dom.resolved_scopes.retain(|s| s != &id);
     }
+
+    pub(crate) fn create_suspense_boundary(
+        &mut self,
+        mount: MountId,
+        idx: usize,
+        component: &VComponent,
+        parent: Option<ElementRef>,
+    ) -> usize {
+        let mut scope_id = ScopeId(self.get_mounted_dyn_node(mount, idx));
+        // If the ScopeId is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that
+        if scope_id.is_placeholder() {
+            {
+                let suspense_context = SuspenseContext::new();
+
+                let suspense_boundary_location =
+                    crate::scope_context::SuspenseLocation::SuspenseBoundary(
+                        suspense_context.clone(),
+                    );
+                self.runtime
+                    .clone()
+                    .with_suspense_location(suspense_boundary_location, || {
+                        let scope_state = self
+                            .dom
+                            .new_scope(component.props.duplicate(), component.name)
+                            .state();
+                        suspense_context.mount(scope_state.id);
+                        scope_id = scope_state.id;
+                    });
+            }
+
+            // Store the scope id for the next render
+            self.set_mounted_dyn_node(mount, idx, scope_id.0);
+        }
+        self.runtime.clone().with_scope_on_stack(scope_id, || {
+            let scope_state = &mut self.dom.scopes[scope_id.0];
+            let props =
+                SuspenseBoundaryProps::downcast_from_props(&mut *scope_state.props).unwrap();
+            let suspense_context =
+                SuspenseContext::downcast_suspense_boundary_from_scope(self.runtime, scope_id)
+                    .unwrap();
+
+            let children = props.children.clone();
+
+            // First always render the children in the background. Rendering the children may cause this boundary to suspend
+            suspense_context.under_suspense_boundary(&self.dom.runtime(), || {
+                let write = self.write;
+                self.write = false;
+                self.create(children.as_vnode(), parent);
+                self.write = write;
+            });
+
+            // Store the (now mounted) children back into the scope state
+            let scope_state = &mut self.dom.scopes[scope_id.0];
+            let props =
+                SuspenseBoundaryProps::downcast_from_props(&mut *scope_state.props).unwrap();
+            props.children.clone_from(&children);
+
+            let scope_state = &mut self.dom.scopes[scope_id.0];
+            let suspense_context = scope_state
+                .state()
+                .suspense_location()
+                .suspense_context()
+                .unwrap()
+                .clone();
+            // If there are suspended futures, render the fallback
+            let nodes_created = if !suspense_context.suspended_futures().is_empty() {
+                let (node, nodes_created) =
+                    suspense_context.in_suspense_placeholder(&self.dom.runtime(), || {
+                        let scope_state = &mut self.dom.scopes[scope_id.0];
+                        let props =
+                            SuspenseBoundaryProps::downcast_from_props(&mut *scope_state.props)
+                                .unwrap();
+                        let suspense_context =
+                            SuspenseContext::downcast_suspense_boundary_from_scope(
+                                self.runtime,
+                                scope_id,
+                            )
+                            .unwrap();
+                        suspense_context.set_suspended_nodes(children.into());
+                        let suspense_placeholder = props.fallback.call(suspense_context);
+                        self.write = false;
+                        let nodes_created = self.create(suspense_placeholder.as_vnode(), parent);
+                        self.write = true;
+                        (suspense_placeholder, nodes_created)
+                    });
+
+                let scope_state = &mut self.dom.scopes[scope_id.0];
+                scope_state.last_rendered_node = Some(node);
+
+                nodes_created
+            } else {
+                // Otherwise just render the children in the real dom
+                debug_assert!(children.as_vnode().mount.get().mounted());
+                let nodes_created = suspense_context
+                    .under_suspense_boundary(&self.dom.runtime(), || {
+                        self.create(children.as_vnode(), parent)
+                    });
+                let scope_state = &mut self.dom.scopes[scope_id.0];
+                scope_state.last_rendered_node = Some(children);
+                let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope(
+                    &self.dom.runtime(),
+                    scope_id,
+                )
+                .unwrap();
+                suspense_context.take_suspended_nodes();
+                mark_suspense_resolved(&suspense_context, self.dom, scope_id);
+
+                nodes_created
+            };
+            nodes_created
+        })
+    }
+
+    #[doc(hidden)]
+    /// Manually rerun the children of this suspense boundary without diffing against the old nodes.
+    ///
+    /// This should only be called by dioxus-web after the suspense boundary has been streamed in from the server.
+    pub fn resolve_suspense(
+        &mut self,
+        scope_id: ScopeId,
+        only_write_templates: impl FnOnce(&mut M),
+        replace_with: usize,
+    ) {
+        self.runtime.clone().with_scope_on_stack(scope_id, || {
+            let _runtime = RuntimeGuard::new(self.runtime.clone());
+            let Some(scope_state) = self.dom.scopes.get_mut(scope_id.0) else {
+                return;
+            };
+
+            // Reset the suspense context
+            let suspense_context = scope_state
+                .state()
+                .suspense_location()
+                .suspense_context()
+                .unwrap()
+                .clone();
+            suspense_context.inner.suspended_tasks.borrow_mut().clear();
+
+            // Get the parent of the suspense boundary to later create children with the right parent
+            let currently_rendered = scope_state.last_rendered_node.as_ref().unwrap().clone();
+            let mount = currently_rendered.as_vnode().mount.get();
+            let parent = {
+                let mounts = self.dom.runtime.mounts.borrow();
+                mounts
+                    .get(mount.0)
+                    .expect("suspense placeholder is not mounted")
+                    .parent
+            };
+
+            let props =
+                SuspenseBoundaryProps::downcast_from_props(&mut *scope_state.props).unwrap();
+
+            // Unmount any children to reset any scopes under this suspense boundary
+            let children = props.children.clone();
+            let suspense_context =
+                SuspenseContext::downcast_suspense_boundary_from_scope(&self.runtime, scope_id)
+                    .unwrap();
+
+            // Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing
+            let suspended = suspense_context.take_suspended_nodes();
+            if let Some(node) = suspended {
+                self.write = false;
+                self.remove_node(&node, None);
+                self.write = true;
+            }
+
+            // Replace the rendered nodes with resolved nodes
+            self.write = true;
+            self.remove_node(currently_rendered.as_vnode(), Some(replace_with));
+            self.write = false;
+
+            // Switch to only writing templates
+            only_write_templates(self.to);
+
+            children.as_vnode().mount.take();
+
+            // First always render the children in the background. Rendering the children may cause this boundary to suspend
+            suspense_context.under_suspense_boundary(&self.runtime, || {
+                self.write = true;
+                self.create(children.as_vnode(), parent);
+                self.write = false;
+            });
+
+            // Store the (now mounted) children back into the scope state
+            let scope_state = &mut self.dom.scopes[scope_id.0];
+            let props =
+                SuspenseBoundaryProps::downcast_from_props(&mut *scope_state.props).unwrap();
+            props.children.clone_from(&children);
+            scope_state.last_rendered_node = Some(children);
+
+            // Run any closures that were waiting for the suspense to resolve
+            suspense_context.run_resolved_closures(&self.runtime);
+        })
+    }
+
+    pub(crate) fn diff_suspense(&mut self, scope_id: ScopeId) {
+        todo!()
+        // self.runtime.clone().with_scope_on_stack(scope_id, || {
+        //     let scope = &mut dom.scopes[scope_id.0];
+        //     let myself = SuspenseBoundaryProps::downcast_from_props(&mut *scope.props)
+        //         .unwrap()
+        //         .clone();
+
+        //     let last_rendered_node = scope.last_rendered_node.as_ref().unwrap().clone();
+
+        //     let SuspenseBoundaryProps {
+        //         fallback, children, ..
+        //     } = myself;
+
+        //     let suspense_context = scope.state().suspense_boundary().unwrap().clone();
+        //     let suspended_nodes = suspense_context.suspended_nodes();
+        //     let suspended = !suspense_context.suspended_futures().is_empty();
+        //     match (suspended_nodes, suspended) {
+        //         // We already have suspended nodes that still need to be suspended
+        //         // Just diff the normal and suspended nodes
+        //         (Some(suspended_nodes), true) => {
+        //             let new_suspended_nodes: VNode = children.into();
+
+        //             // Diff the placeholder nodes in the dom
+        //             let new_placeholder =
+        //                 suspense_context.in_suspense_placeholder(&dom.runtime(), || {
+        //                     let old_placeholder = last_rendered_node;
+        //                     let new_placeholder = fallback.call(suspense_context.clone());
+
+        //                     old_placeholder.as_vnode().diff_node(
+        //                         new_placeholder.as_vnode(),
+        //                         dom,
+        //                         to,
+        //                         true,
+        //                     );
+        //                     new_placeholder
+        //                 });
+
+        //             // Set the last rendered node to the placeholder
+        //             dom.scopes[scope_id.0].last_rendered_node = Some(new_placeholder);
+
+        //             // Diff the suspended nodes in the background
+        //             suspense_context.under_suspense_boundary(&dom.runtime(), || {
+        //                 suspended_nodes.diff_node(&new_suspended_nodes, dom, to, false);
+        //             });
+
+        //             let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope(
+        //                 &dom.runtime,
+        //                 scope_id,
+        //             )
+        //             .unwrap();
+        //             suspense_context.set_suspended_nodes(new_suspended_nodes);
+        //         }
+        //         // We have no suspended nodes, and we are not suspended. Just diff the children like normal
+        //         (None, false) => {
+        //             let old_children = last_rendered_node;
+        //             let new_children = children;
+
+        //             suspense_context.under_suspense_boundary(&dom.runtime(), || {
+        //                 old_children
+        //                     .as_vnode()
+        //                     .diff_node(new_children.as_vnode(), dom, to, true);
+        //             });
+
+        //             // Set the last rendered node to the new children
+        //             dom.scopes[scope_id.0].last_rendered_node = Some(new_children);
+        //         }
+        //         // We have no suspended nodes, but we just became suspended. Move the children to the background
+        //         (None, true) => {
+        //             let old_children = last_rendered_node.as_vnode();
+        //             let new_children: VNode = children.into();
+
+        //             let new_placeholder = fallback.call(suspense_context.clone());
+
+        //             // Move the children to the background
+        //             let mount = old_children.mount.get();
+        //             let parent = dom.get_mounted_parent(mount);
+
+        //             suspense_context.in_suspense_placeholder(&dom.runtime(), || {
+        //                 old_children.move_node_to_background(
+        //                     std::slice::from_ref(new_placeholder.as_vnode()),
+        //                     parent,
+        //                     dom,
+        //                     to,
+        //                     false,
+        //                 );
+        //             });
+
+        //             // Then diff the new children in the background
+        //             suspense_context.under_suspense_boundary(&dom.runtime(), || {
+        //                 old_children.diff_node(&new_children, dom, to, false);
+        //             });
+
+        //             // Set the last rendered node to the new suspense placeholder
+        //             dom.scopes[scope_id.0].last_rendered_node = Some(new_placeholder);
+
+        //             let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope(
+        //                 &dom.runtime,
+        //                 scope_id,
+        //             )
+        //             .unwrap();
+        //             suspense_context.set_suspended_nodes(new_children);
+
+        //             un_resolve_suspense(dom, scope_id);
+        //         }
+        //         // We have suspended nodes, but we just got out of suspense. Move the suspended nodes to the foreground
+        //         (Some(_), false) => {
+        //             // Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing
+        //             let old_suspended_nodes = suspense_context.take_suspended_nodes().unwrap();
+        //             let old_placeholder = last_rendered_node;
+        //             let new_children = children;
+
+        //             // First diff the two children nodes in the background
+        //             suspense_context.under_suspense_boundary(&dom.runtime(), || {
+        //                 old_suspended_nodes.diff_node(new_children.as_vnode(), dom, to, false);
+
+        //                 // Then replace the placeholder with the new children
+        //                 let mount = old_placeholder.as_vnode().mount.get();
+        //                 let parent = dom.get_mounted_parent(mount);
+        //                 old_placeholder.as_vnode().replace(
+        //                     std::slice::from_ref(new_children.as_vnode()),
+        //                     parent,
+        //                     dom,
+        //                     to,
+        //                     true,
+        //                 );
+        //             });
+
+        //             // Set the last rendered node to the new children
+        //             dom.scopes[scope_id.0].last_rendered_node = Some(new_children);
+
+        //             mark_suspense_resolved(&suspense_context, dom, scope_id);
+        //         }
+        //     }
+        // })
+    }
 }
 
 /// We can apply various optimizations to dynamic nodes that are the single child of their parent.
@@ -1647,3 +1972,29 @@ fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
         _ => false,
     }
 }
+
+/// Try to get the dynamic node and its index for a root node
+fn get_dynamic_root_node_and_id<'n>(
+    node: &'n VNode,
+    root_idx: usize,
+) -> Option<(usize, &'n DynamicNode)> {
+    node.template.roots[root_idx]
+        .dynamic_id()
+        .map(|id| (id, &node.dynamic_nodes[id]))
+}
+
+/// Move to a resolved suspense state
+pub(crate) fn mark_suspense_resolved(
+    suspense_context: &SuspenseContext,
+    dom: &mut VirtualDom,
+    scope_id: ScopeId,
+) {
+    dom.resolved_scopes.push(scope_id);
+    // Run any closures that were waiting for the suspense to resolve
+    suspense_context.run_resolved_closures(&dom.runtime);
+}
+
+/// Move from a resolved suspense state to an suspended state
+fn un_resolve_suspense(dom: &mut VirtualDom, scope_id: ScopeId) {
+    dom.resolved_scopes.retain(|&id| id != scope_id);
+}

+ 2 - 333
packages/core/src/suspense/component.rs

@@ -3,9 +3,9 @@ use crate::{innerlude::*, scope_context::SuspenseLocation};
 /// Properties for the [`SuspenseBoundary()`] component.
 #[allow(non_camel_case_types)]
 pub struct SuspenseBoundaryProps {
-    fallback: Callback<SuspenseContext, Element>,
+    pub(crate) fallback: Callback<SuspenseContext, Element>,
     /// The children of the suspense boundary
-    children: Element,
+    pub(crate) children: Element,
 }
 
 impl Clone for SuspenseBoundaryProps {
@@ -266,337 +266,6 @@ impl SuspenseBoundaryProps {
         let inner: Option<&mut SuspenseBoundaryPropsWithOwner> = props.props_mut().downcast_mut();
         inner.map(|inner| &mut inner.inner)
     }
-
-    pub(crate) fn create(
-        mount: MountId,
-        idx: usize,
-        component: &VComponent,
-        parent: Option<ElementRef>,
-        dom: &mut VirtualDom,
-        to: &mut impl WriteMutations,
-        write: bool,
-    ) -> usize {
-        let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
-        // If the ScopeId is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that
-        if scope_id.is_placeholder() {
-            {
-                let suspense_context = SuspenseContext::new();
-
-                let suspense_boundary_location =
-                    crate::scope_context::SuspenseLocation::SuspenseBoundary(
-                        suspense_context.clone(),
-                    );
-                dom.runtime
-                    .clone()
-                    .with_suspense_location(suspense_boundary_location, || {
-                        let scope_state = dom
-                            .new_scope(component.props.duplicate(), component.name)
-                            .state();
-                        suspense_context.mount(scope_state.id);
-                        scope_id = scope_state.id;
-                    });
-            }
-
-            // Store the scope id for the next render
-            dom.set_mounted_dyn_node(mount, idx, scope_id.0);
-        }
-        dom.runtime.clone().with_scope_on_stack(scope_id, || {
-            let scope_state = &mut dom.scopes[scope_id.0];
-            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();
-            let suspense_context =
-                SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)
-                    .unwrap();
-
-            let children = props.children.clone();
-
-            // First always render the children in the background. Rendering the children may cause this boundary to suspend
-            suspense_context.under_suspense_boundary(&dom.runtime(), || {
-                children.as_vnode().create(dom, parent, to, false);
-            });
-
-            // Store the (now mounted) children back into the scope state
-            let scope_state = &mut dom.scopes[scope_id.0];
-            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();
-            props.children.clone_from(&children);
-
-            let scope_state = &mut dom.scopes[scope_id.0];
-            let suspense_context = scope_state
-                .state()
-                .suspense_location()
-                .suspense_context()
-                .unwrap()
-                .clone();
-            // If there are suspended futures, render the fallback
-            let nodes_created = if !suspense_context.suspended_futures().is_empty() {
-                let (node, nodes_created) =
-                    suspense_context.in_suspense_placeholder(&dom.runtime(), || {
-                        let scope_state = &mut dom.scopes[scope_id.0];
-                        let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();
-                        let suspense_context =
-                            SuspenseContext::downcast_suspense_boundary_from_scope(
-                                &dom.runtime,
-                                scope_id,
-                            )
-                            .unwrap();
-                        suspense_context.set_suspended_nodes(children.into());
-                        let suspense_placeholder = props.fallback.call(suspense_context);
-                        let nodes_created = suspense_placeholder
-                            .as_vnode()
-                            .create(dom, parent, to, false);
-                        (suspense_placeholder, nodes_created)
-                    });
-
-                let scope_state = &mut dom.scopes[scope_id.0];
-                scope_state.last_rendered_node = Some(node);
-
-                nodes_created
-            } else {
-                // Otherwise just render the children in the real dom
-                debug_assert!(children.as_vnode().mount.get().mounted());
-                let nodes_created = suspense_context
-                    .under_suspense_boundary(&dom.runtime(), || {
-                        children.as_vnode().create(dom, parent, to, write)
-                    });
-                let scope_state = &mut dom.scopes[scope_id.0];
-                scope_state.last_rendered_node = Some(children);
-                let suspense_context =
-                    SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)
-                        .unwrap();
-                suspense_context.take_suspended_nodes();
-                mark_suspense_resolved(&suspense_context, dom, scope_id);
-
-                nodes_created
-            };
-            nodes_created
-        })
-    }
-
-    #[doc(hidden)]
-    /// Manually rerun the children of this suspense boundary without diffing against the old nodes.
-    ///
-    /// This should only be called by dioxus-web after the suspense boundary has been streamed in from the server.
-    pub fn resolve_suspense<M: WriteMutations>(
-        scope_id: ScopeId,
-        dom: &mut VirtualDom,
-        to: &mut M,
-        only_write_templates: impl FnOnce(&mut M),
-        replace_with: usize,
-    ) {
-        dom.runtime.clone().with_scope_on_stack(scope_id, || {
-            let _runtime = RuntimeGuard::new(dom.runtime());
-            let Some(scope_state) = dom.scopes.get_mut(scope_id.0) else {
-                return;
-            };
-
-            // Reset the suspense context
-            let suspense_context = scope_state
-                .state()
-                .suspense_location()
-                .suspense_context()
-                .unwrap()
-                .clone();
-            suspense_context.inner.suspended_tasks.borrow_mut().clear();
-
-            // Get the parent of the suspense boundary to later create children with the right parent
-            let currently_rendered = scope_state.last_rendered_node.as_ref().unwrap().clone();
-            let mount = currently_rendered.as_vnode().mount.get();
-            let parent = {
-                let mounts = dom.runtime.mounts.borrow();
-                mounts
-                    .get(mount.0)
-                    .expect("suspense placeholder is not mounted")
-                    .parent
-            };
-
-            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();
-
-            // Unmount any children to reset any scopes under this suspense boundary
-            let children = props.children.clone();
-            let suspense_context =
-                SuspenseContext::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)
-                    .unwrap();
-            // Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing
-            let suspended = suspense_context.take_suspended_nodes();
-            if let Some(node) = suspended {
-                node.remove_node(&mut *dom, to, None, false);
-            }
-            // Replace the rendered nodes with resolved nodes
-            currently_rendered
-                .as_vnode()
-                .remove_node(&mut *dom, to, Some(replace_with), true);
-
-            // Switch to only writing templates
-            only_write_templates(to);
-
-            children.as_vnode().mount.take();
-
-            // First always render the children in the background. Rendering the children may cause this boundary to suspend
-            suspense_context.under_suspense_boundary(&dom.runtime(), || {
-                children.as_vnode().create(dom, parent, to, true);
-            });
-
-            // Store the (now mounted) children back into the scope state
-            let scope_state = &mut dom.scopes[scope_id.0];
-            let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();
-            props.children.clone_from(&children);
-            scope_state.last_rendered_node = Some(children);
-
-            // Run any closures that were waiting for the suspense to resolve
-            suspense_context.run_resolved_closures(&dom.runtime);
-        })
-    }
-
-    pub(crate) fn diff(scope_id: ScopeId, dom: &mut VirtualDom, to: &mut impl WriteMutations) {
-        dom.runtime.clone().with_scope_on_stack(scope_id, || {
-            let scope = &mut dom.scopes[scope_id.0];
-            let myself = Self::downcast_from_props(&mut *scope.props)
-                .unwrap()
-                .clone();
-
-            let last_rendered_node = scope.last_rendered_node.as_ref().unwrap().clone();
-
-            let Self {
-                fallback, children, ..
-            } = myself;
-
-            let suspense_context = scope.state().suspense_boundary().unwrap().clone();
-            let suspended_nodes = suspense_context.suspended_nodes();
-            let suspended = !suspense_context.suspended_futures().is_empty();
-            match (suspended_nodes, suspended) {
-                // We already have suspended nodes that still need to be suspended
-                // Just diff the normal and suspended nodes
-                (Some(suspended_nodes), true) => {
-                    let new_suspended_nodes: VNode = children.into();
-
-                    // Diff the placeholder nodes in the dom
-                    let new_placeholder =
-                        suspense_context.in_suspense_placeholder(&dom.runtime(), || {
-                            let old_placeholder = last_rendered_node;
-                            let new_placeholder = fallback.call(suspense_context.clone());
-
-                            old_placeholder.as_vnode().diff_node(
-                                new_placeholder.as_vnode(),
-                                dom,
-                                to,
-                                true,
-                            );
-                            new_placeholder
-                        });
-
-                    // Set the last rendered node to the placeholder
-                    dom.scopes[scope_id.0].last_rendered_node = Some(new_placeholder);
-
-                    // Diff the suspended nodes in the background
-                    suspense_context.under_suspense_boundary(&dom.runtime(), || {
-                        suspended_nodes.diff_node(&new_suspended_nodes, dom, to, false);
-                    });
-
-                    let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope(
-                        &dom.runtime,
-                        scope_id,
-                    )
-                    .unwrap();
-                    suspense_context.set_suspended_nodes(new_suspended_nodes);
-                }
-                // We have no suspended nodes, and we are not suspended. Just diff the children like normal
-                (None, false) => {
-                    let old_children = last_rendered_node;
-                    let new_children = children;
-
-                    suspense_context.under_suspense_boundary(&dom.runtime(), || {
-                        old_children
-                            .as_vnode()
-                            .diff_node(new_children.as_vnode(), dom, to, true);
-                    });
-
-                    // Set the last rendered node to the new children
-                    dom.scopes[scope_id.0].last_rendered_node = Some(new_children);
-                }
-                // We have no suspended nodes, but we just became suspended. Move the children to the background
-                (None, true) => {
-                    let old_children = last_rendered_node.as_vnode();
-                    let new_children: VNode = children.into();
-
-                    let new_placeholder = fallback.call(suspense_context.clone());
-
-                    // Move the children to the background
-                    let mount = old_children.mount.get();
-                    let parent = dom.get_mounted_parent(mount);
-
-                    suspense_context.in_suspense_placeholder(&dom.runtime(), || {
-                        old_children.move_node_to_background(
-                            std::slice::from_ref(new_placeholder.as_vnode()),
-                            parent,
-                            dom,
-                            to,
-                            false,
-                        );
-                    });
-
-                    // Then diff the new children in the background
-                    suspense_context.under_suspense_boundary(&dom.runtime(), || {
-                        old_children.diff_node(&new_children, dom, to, false);
-                    });
-
-                    // Set the last rendered node to the new suspense placeholder
-                    dom.scopes[scope_id.0].last_rendered_node = Some(new_placeholder);
-
-                    let suspense_context = SuspenseContext::downcast_suspense_boundary_from_scope(
-                        &dom.runtime,
-                        scope_id,
-                    )
-                    .unwrap();
-                    suspense_context.set_suspended_nodes(new_children);
-
-                    un_resolve_suspense(dom, scope_id);
-                }
-                // We have suspended nodes, but we just got out of suspense. Move the suspended nodes to the foreground
-                (Some(_), false) => {
-                    // Take the suspended nodes out of the suspense boundary so the children know that the boundary is not suspended while diffing
-                    let old_suspended_nodes = suspense_context.take_suspended_nodes().unwrap();
-                    let old_placeholder = last_rendered_node;
-                    let new_children = children;
-
-                    // First diff the two children nodes in the background
-                    suspense_context.under_suspense_boundary(&dom.runtime(), || {
-                        old_suspended_nodes.diff_node(new_children.as_vnode(), dom, to, false);
-
-                        // Then replace the placeholder with the new children
-                        let mount = old_placeholder.as_vnode().mount.get();
-                        let parent = dom.get_mounted_parent(mount);
-                        old_placeholder.as_vnode().replace(
-                            std::slice::from_ref(new_children.as_vnode()),
-                            parent,
-                            dom,
-                            to,
-                            true,
-                        );
-                    });
-
-                    // Set the last rendered node to the new children
-                    dom.scopes[scope_id.0].last_rendered_node = Some(new_children);
-
-                    mark_suspense_resolved(&suspense_context, dom, scope_id);
-                }
-            }
-        })
-    }
-}
-
-/// Move to a resolved suspense state
-fn mark_suspense_resolved(
-    suspense_context: &SuspenseContext,
-    dom: &mut VirtualDom,
-    scope_id: ScopeId,
-) {
-    dom.resolved_scopes.push(scope_id);
-    // Run any closures that were waiting for the suspense to resolve
-    suspense_context.run_resolved_closures(&dom.runtime);
-}
-
-/// Move from a resolved suspense state to an suspended state
-fn un_resolve_suspense(dom: &mut VirtualDom, scope_id: ScopeId) {
-    dom.resolved_scopes.retain(|&id| id != scope_id);
 }
 
 impl SuspenseContext {

+ 6 - 6
packages/core/src/suspense/mod.rs

@@ -84,7 +84,7 @@ impl SuspendedFuture {
 /// A context with information about suspended components
 #[derive(Debug, Clone)]
 pub struct SuspenseContext {
-    inner: Rc<SuspenseBoundaryInner>,
+    pub(crate) inner: Rc<SuspenseBoundaryInner>,
 }
 
 impl PartialEq for SuspenseContext {
@@ -206,14 +206,14 @@ impl SuspenseContext {
 
 /// A boundary that will capture any errors from child components
 pub struct SuspenseBoundaryInner {
-    suspended_tasks: RefCell<Vec<SuspendedFuture>>,
-    id: Cell<ScopeId>,
+    pub(crate) suspended_tasks: RefCell<Vec<SuspendedFuture>>,
+    pub(crate) id: Cell<ScopeId>,
     /// The nodes that are suspended under this boundary
-    suspended_nodes: RefCell<Option<VNode>>,
+    pub(crate) suspended_nodes: RefCell<Option<VNode>>,
     /// On the server, you can only resolve a suspense boundary once. This is used to track if the suspense boundary has been resolved and if it should be frozen
-    frozen: Cell<bool>,
+    pub(crate) frozen: Cell<bool>,
     /// Closures queued to run after the suspense boundary is resolved
-    after_suspense_resolved: RefCell<Vec<Box<dyn FnOnce()>>>,
+    pub(crate) after_suspense_resolved: RefCell<Vec<Box<dyn FnOnce()>>>,
 }
 
 impl Debug for SuspenseBoundaryInner {

+ 8 - 3
packages/core/src/virtual_dom.rs

@@ -2,7 +2,6 @@
 //!
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
 
-use crate::innerlude::Work;
 use crate::properties::RootProps;
 use crate::root_wrapper::RootScopeWrapper;
 use crate::{
@@ -12,6 +11,7 @@ use crate::{
     scopes::ScopeId,
     ComponentFunction, Element, Mutations,
 };
+use crate::{diff::Fiber, innerlude::Work};
 use crate::{Task, VComponent};
 use futures_util::StreamExt;
 use slab::Slab;
@@ -581,7 +581,11 @@ impl VirtualDom {
         self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone());
 
         // Rebuilding implies we append the created elements to the root
-        let m = self.create_scope(to, ScopeId::ROOT, new_nodes, None);
+        let m = Fiber::new(&self.runtime.clone(), self, to, true).create_scope(
+            ScopeId::ROOT,
+            new_nodes,
+            None,
+        );
 
         to.append_children(ElementId(0), m);
     }
@@ -608,7 +612,8 @@ impl VirtualDom {
                 Work::RerunScope(scope) => {
                     // If the scope is dirty, run the scope and get the mutations
                     self.runtime.clone().while_rendering(|| {
-                        self.run_and_diff_scope(to, scope.id);
+                        Fiber::new(&self.runtime.clone(), self, to, true)
+                            .run_and_diff_scope(scope.id);
                     });
                 }
             }