Browse Source

wip: error boundary

Jonathan Kelley 2 years ago
parent
commit
0e5a59f9ed

+ 1 - 2
packages/core/Cargo.toml

@@ -33,11 +33,10 @@ indexmap = "1.7"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
+anyhow = "1.0.66"
 
 [dev-dependencies]
 tokio = { version = "*", features = ["full"] }
-
-[dev-dependencies]
 dioxus = { path = "../dioxus" }
 
 [features]

+ 27 - 39
packages/core/src/create.rs

@@ -1,5 +1,3 @@
-use std::cell::Cell;
-
 use crate::factory::RenderReturn;
 use crate::innerlude::{Mutations, VComponent, VFragment, VText};
 use crate::mutations::Mutation;
@@ -7,7 +5,7 @@ use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, TemplateNode};
 use crate::virtual_dom::VirtualDom;
-use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext, TemplateAttribute};
+use crate::{AttributeValue, ScopeId, SuspenseContext, TemplateAttribute};
 
 impl VirtualDom {
     /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
@@ -71,12 +69,6 @@ impl VirtualDom {
                             mutations.push(CreateTextNode { value, id });
                             1
                         }
-                        DynamicNode::Placeholder(slot) => {
-                            let id = self.next_element(template, template.template.node_paths[*id]);
-                            slot.set(id);
-                            mutations.push(CreatePlaceholder { id });
-                            1
-                        }
                     }
                 }
             };
@@ -247,8 +239,7 @@ impl VirtualDom {
         use DynamicNode::*;
         match node {
             Text(text) => self.create_dynamic_text(mutations, template, text, idx),
-            Placeholder(slot) => self.create_placeholder(template, idx, slot, mutations),
-            Fragment(frag) => self.create_fragment(frag, mutations),
+            Fragment(frag) => self.create_fragment(frag, template, idx, mutations),
             Component(component) => self.create_component_node(mutations, template, component, idx),
         }
     }
@@ -277,37 +268,35 @@ impl VirtualDom {
         0
     }
 
-    fn create_placeholder(
+    pub(crate) fn create_fragment<'a>(
         &mut self,
-        template: &VNode,
+        frag: &'a VFragment<'a>,
+        template: &'a VNode<'a>,
         idx: usize,
-        slot: &Cell<ElementId>,
-        mutations: &mut Mutations,
+        mutations: &mut Mutations<'a>,
     ) -> usize {
-        // Allocate a dynamic element reference for this text node
-        let id = self.next_element(template, template.template.node_paths[idx]);
+        match frag {
+            VFragment::NonEmpty(nodes) => nodes
+                .iter()
+                .fold(0, |acc, child| acc + self.create(mutations, child)),
 
-        // Make sure the text node is assigned to the correct element
-        slot.set(id);
+            VFragment::Empty(slot) => {
+                // Allocate a dynamic element reference for this text node
+                let id = self.next_element(template, template.template.node_paths[idx]);
 
-        // Assign the ID to the existing node in the template
-        mutations.push(AssignId {
-            path: &template.template.node_paths[idx][1..],
-            id,
-        });
+                // Make sure the text node is assigned to the correct element
+                slot.set(id);
 
-        // Since the placeholder is already in the DOM, we don't create any new nodes
-        0
-    }
+                // Assign the ID to the existing node in the template
+                mutations.push(AssignId {
+                    path: &template.template.node_paths[idx][1..],
+                    id,
+                });
 
-    fn create_fragment<'a>(
-        &mut self,
-        frag: &'a VFragment<'a>,
-        mutations: &mut Mutations<'a>,
-    ) -> usize {
-        frag.nodes
-            .iter()
-            .fold(0, |acc, child| acc + self.create(mutations, child))
+                // Since the placeholder is already in the DOM, we don't create any new nodes
+                0
+            }
+        }
     }
 
     fn create_component_node<'a>(
@@ -330,10 +319,9 @@ impl VirtualDom {
         use RenderReturn::*;
 
         match return_nodes {
-            Sync(Some(t)) => self.mount_component(mutations, scope, t, idx),
-            Sync(None) | Async(_) => {
-                self.mount_component_placeholder(template, idx, scope, mutations)
-            }
+            Sync(Ok(t)) => self.mount_component(mutations, scope, t, idx),
+            Sync(Err(_e)) => todo!("Propogate error upwards"),
+            Async(_) => self.mount_component_placeholder(template, idx, scope, mutations),
         }
     }
 

+ 215 - 181
packages/core/src/diff.rs

@@ -21,24 +21,6 @@ use crate::{
 use fxhash::{FxHashMap, FxHashSet};
 use slab::Slab;
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct DirtyScope {
-    pub height: u32,
-    pub id: ScopeId,
-}
-
-impl PartialOrd for DirtyScope {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.height.cmp(&other.height))
-    }
-}
-
-impl Ord for DirtyScope {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.height.cmp(&other.height)
-    }
-}
-
 impl<'b> VirtualDom {
     pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
         let scope_state = &mut self.scopes[scope.0];
@@ -78,25 +60,26 @@ impl<'b> VirtualDom {
         use RenderReturn::{Async, Sync};
         match (left, right) {
             // diff
-            (Sync(Some(l)), Sync(Some(r))) => self.diff_vnode(m, l, r),
-
-            // remove old with placeholder
-            (Sync(Some(l)), Sync(None)) | (Sync(Some(l)), Async(_)) => {
-                //
-                let id = self.next_element(l, &[]); // todo!
-                m.push(Mutation::CreatePlaceholder { id });
-                self.drop_template(m, l, true);
-            }
-
-            // remove placeholder with nodes
-            (Sync(None), Sync(Some(_))) => {}
-            (Async(_), Sync(Some(v))) => {}
-
-            // nothing... just transfer the placeholders over
-            (Async(_), Async(_))
-            | (Sync(None), Sync(None))
-            | (Sync(None), Async(_))
-            | (Async(_), Sync(None)) => {}
+            (Sync(Ok(l)), Sync(Ok(r))) => self.diff_vnode(m, l, r),
+
+            _ => todo!("handle diffing nonstandard nodes"),
+            // // remove old with placeholder
+            // (Sync(Ok(l)), Sync(None)) | (Sync(Ok(l)), Async(_)) => {
+            //     //
+            //     let id = self.next_element(l, &[]); // todo!
+            //     m.push(Mutation::CreatePlaceholder { id });
+            //     self.drop_template(m, l, true);
+            // }
+
+            // // remove placeholder with nodes
+            // (Sync(None), Sync(Ok(_))) => {}
+            // (Async(_), Sync(Ok(v))) => {}
+
+            // // nothing... just transfer the placeholders over
+            // (Async(_), Async(_))
+            // | (Sync(None), Sync(None))
+            // | (Sync(None), Async(_))
+            // | (Async(_), Sync(None)) => {}
         }
     }
 
@@ -120,15 +103,19 @@ impl<'b> VirtualDom {
                 .mounted_element
                 .set(left_attr.mounted_element.get());
 
-            if left_attr.value != right_attr.value {
+            if left_attr.value != right_attr.value || left_attr.volatile {
                 // todo: add more types of attribute values
-                if let AttributeValue::Text(text) = right_attr.value {
-                    muts.push(Mutation::SetAttribute {
-                        id: left_attr.mounted_element.get(),
-                        name: left_attr.name,
-                        value: text,
-                        ns: right_attr.namespace,
-                    });
+                match right_attr.value {
+                    AttributeValue::Text(text) => {
+                        muts.push(Mutation::SetAttribute {
+                            id: left_attr.mounted_element.get(),
+                            name: left_attr.name,
+                            value: text,
+                            ns: right_attr.namespace,
+                        });
+                    }
+                    // todo: more types of attribute values
+                    _ => (),
                 }
             }
         }
@@ -139,10 +126,9 @@ impl<'b> VirtualDom {
             .zip(right_template.dynamic_nodes.iter())
         {
             match (left_node, right_node) {
-                (Component(left), Component(right)) => self.diff_vcomponent(muts, left, right),
                 (Text(left), Text(right)) => self.diff_vtext(muts, left, right),
                 (Fragment(left), Fragment(right)) => self.diff_vfragment(muts, left, right),
-                (Placeholder(left), Placeholder(right)) => right.set(left.get()),
+                (Component(left), Component(right)) => self.diff_vcomponent(muts, left, right),
                 _ => self.replace(muts, left_template, right_template, left_node, right_node),
             };
         }
@@ -168,17 +154,6 @@ impl<'b> VirtualDom {
         // this codepath is through "light_diff", but we check there that the pointers are the same
         assert_eq!(left.render_fn, right.render_fn);
 
-        /*
-
-
-
-        let left = rsx!{ Component {} }
-        let right = rsx!{ Component {} }
-
-
-
-        */
-
         // Make sure the new vcomponent has the right scopeid associated to it
         let scope_id = left.scope.get().unwrap();
         right.scope.set(Some(scope_id));
@@ -276,36 +251,63 @@ impl<'b> VirtualDom {
         left: &'b VFragment<'b>,
         right: &'b VFragment<'b>,
     ) {
-        // match (left.nodes, right.nodes) {
-        //     ([], []) => rp.set(lp.get()),
-        //     ([], _) => {
-        //         //
-        //         todo!()
-        //     }
-        //     (_, []) => {
-        //         // if this fragment is the only child of its parent, then we can use the "RemoveAllChildren" mutation
-        //         todo!()
-        //     }
-        //     _ => {
-        //         let new_is_keyed = new[0].key.is_some();
-        //         let old_is_keyed = old[0].key.is_some();
-
-        //         debug_assert!(
-        //             new.iter().all(|n| n.key.is_some() == new_is_keyed),
-        //             "all siblings must be keyed or all siblings must be non-keyed"
-        //         );
-        //         debug_assert!(
-        //             old.iter().all(|o| o.key.is_some() == old_is_keyed),
-        //             "all siblings must be keyed or all siblings must be non-keyed"
-        //         );
-
-        //         if new_is_keyed && old_is_keyed {
-        //             self.diff_keyed_children(muts, old, new);
-        //         } else {
-        //             self.diff_non_keyed_children(muts, old, new);
-        //         }
-        //     }
-        // }
+        use VFragment::*;
+        match (left, right) {
+            (Empty(l), Empty(r)) => r.set(l.get()),
+            (Empty(l), NonEmpty(r)) => self.replace_placeholder_with_nodes(muts, l, r),
+            (NonEmpty(l), Empty(r)) => self.replace_nodes_with_placeholder(muts, l, r),
+            (NonEmpty(old), NonEmpty(new)) => self.diff_non_empty_fragment(new, old, muts),
+        }
+    }
+
+    fn replace_placeholder_with_nodes(
+        &mut self,
+        muts: &mut Mutations<'b>,
+        l: &'b std::cell::Cell<ElementId>,
+        r: &'b [VNode<'b>],
+    ) {
+        let created = r
+            .iter()
+            .fold(0, |acc, child| acc + self.create(muts, child));
+        muts.push(Mutation::ReplaceWith {
+            id: l.get(),
+            m: created,
+        })
+    }
+
+    fn replace_nodes_with_placeholder(
+        &mut self,
+        muts: &mut Mutations<'b>,
+        l: &'b [VNode<'b>],
+        r: &'b std::cell::Cell<ElementId>,
+    ) {
+        //
+
+        // Remove the old nodes, except for one
+        self.remove_nodes(muts, &l[1..]);
+    }
+
+    fn diff_non_empty_fragment(
+        &mut self,
+        new: &'b [VNode<'b>],
+        old: &'b [VNode<'b>],
+        muts: &mut Mutations<'b>,
+    ) {
+        let new_is_keyed = new[0].key.is_some();
+        let old_is_keyed = old[0].key.is_some();
+        debug_assert!(
+            new.iter().all(|n| n.key.is_some() == new_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
+        debug_assert!(
+            old.iter().all(|o| o.key.is_some() == old_is_keyed),
+            "all siblings must be keyed or all siblings must be non-keyed"
+        );
+        if new_is_keyed && old_is_keyed {
+            // self.diff_keyed_children(muts, old, new);
+        } else {
+            self.diff_non_keyed_children(muts, old, new);
+        }
     }
 
     // Diff children that are not keyed.
@@ -330,8 +332,9 @@ impl<'b> VirtualDom {
 
         match old.len().cmp(&new.len()) {
             Ordering::Greater => self.remove_nodes(muts, &old[new.len()..]),
-            Ordering::Less => todo!(),
-            // Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
+            Ordering::Less => {
+                self.create_and_insert_after(muts, &new[old.len()..], old.last().unwrap())
+            }
             Ordering::Equal => {}
         }
 
@@ -340,95 +343,95 @@ impl<'b> VirtualDom {
         }
     }
 
-    // Diffing "keyed" children.
-    //
-    // With keyed children, we care about whether we delete, move, or create nodes
-    // versus mutate existing nodes in place. Presumably there is some sort of CSS
-    // transition animation that makes the virtual DOM diffing algorithm
-    // observable. By specifying keys for nodes, we know which virtual DOM nodes
-    // must reuse (or not reuse) the same physical DOM nodes.
-    //
-    // This is loosely based on Inferno's keyed patching implementation. However, we
-    // have to modify the algorithm since we are compiling the diff down into change
-    // list instructions that will be executed later, rather than applying the
-    // changes to the DOM directly as we compare virtual DOMs.
-    //
-    // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
-    //
-    // The stack is empty upon entry.
-    fn diff_keyed_children(
-        &mut self,
-        muts: &mut Mutations<'b>,
-        old: &'b [VNode<'b>],
-        new: &'b [VNode<'b>],
-    ) {
-        // if cfg!(debug_assertions) {
-        //     let mut keys = fxhash::FxHashSet::default();
-        //     let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
-        //         keys.clear();
-        //         for child in children {
-        //             let key = child.key;
-        //             debug_assert!(
-        //                 key.is_some(),
-        //                 "if any sibling is keyed, all siblings must be keyed"
-        //             );
-        //             keys.insert(key);
-        //         }
-        //         debug_assert_eq!(
-        //             children.len(),
-        //             keys.len(),
-        //             "keyed siblings must each have a unique key"
-        //         );
-        //     };
-        //     assert_unique_keys(old);
-        //     assert_unique_keys(new);
-        // }
-
-        // // First up, we diff all the nodes with the same key at the beginning of the
-        // // children.
-        // //
-        // // `shared_prefix_count` is the count of how many nodes at the start of
-        // // `new` and `old` share the same keys.
-        // let (left_offset, right_offset) = match self.diff_keyed_ends(muts, old, new) {
-        //     Some(count) => count,
-        //     None => return,
-        // };
-
-        // // Ok, we now hopefully have a smaller range of children in the middle
-        // // within which to re-order nodes with the same keys, remove old nodes with
-        // // now-unused keys, and create new nodes with fresh keys.
-
-        // let old_middle = &old[left_offset..(old.len() - right_offset)];
-        // let new_middle = &new[left_offset..(new.len() - right_offset)];
-
-        // debug_assert!(
-        //     !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
-        //     "keyed children must have the same number of children"
-        // );
-
-        // if new_middle.is_empty() {
-        //     // remove the old elements
-        //     self.remove_nodes(muts, old_middle);
-        // } else if old_middle.is_empty() {
-        //     // there were no old elements, so just create the new elements
-        //     // we need to find the right "foothold" though - we shouldn't use the "append" at all
-        //     if left_offset == 0 {
-        //         // insert at the beginning of the old list
-        //         let foothold = &old[old.len() - right_offset];
-        //         self.create_and_insert_before(new_middle, foothold);
-        //     } else if right_offset == 0 {
-        //         // insert at the end  the old list
-        //         let foothold = old.last().unwrap();
-        //         self.create_and_insert_after(new_middle, foothold);
-        //     } else {
-        //         // inserting in the middle
-        //         let foothold = &old[left_offset - 1];
-        //         self.create_and_insert_after(new_middle, foothold);
-        //     }
-        // } else {
-        //     self.diff_keyed_middle(muts, old_middle, new_middle);
-        // }
-    }
+    // // Diffing "keyed" children.
+    // //
+    // // With keyed children, we care about whether we delete, move, or create nodes
+    // // versus mutate existing nodes in place. Presumably there is some sort of CSS
+    // // transition animation that makes the virtual DOM diffing algorithm
+    // // observable. By specifying keys for nodes, we know which virtual DOM nodes
+    // // must reuse (or not reuse) the same physical DOM nodes.
+    // //
+    // // This is loosely based on Inferno's keyed patching implementation. However, we
+    // // have to modify the algorithm since we are compiling the diff down into change
+    // // list instructions that will be executed later, rather than applying the
+    // // changes to the DOM directly as we compare virtual DOMs.
+    // //
+    // // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
+    // //
+    // // The stack is empty upon entry.
+    // fn diff_keyed_children(
+    //     &mut self,
+    //     muts: &mut Mutations<'b>,
+    //     old: &'b [VNode<'b>],
+    //     new: &'b [VNode<'b>],
+    // ) {
+    //     if cfg!(debug_assertions) {
+    //         let mut keys = fxhash::FxHashSet::default();
+    //         let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
+    //             keys.clear();
+    //             for child in children {
+    //                 let key = child.key;
+    //                 debug_assert!(
+    //                     key.is_some(),
+    //                     "if any sibling is keyed, all siblings must be keyed"
+    //                 );
+    //                 keys.insert(key);
+    //             }
+    //             debug_assert_eq!(
+    //                 children.len(),
+    //                 keys.len(),
+    //                 "keyed siblings must each have a unique key"
+    //             );
+    //         };
+    //         assert_unique_keys(old);
+    //         assert_unique_keys(new);
+    //     }
+
+    //     // First up, we diff all the nodes with the same key at the beginning of the
+    //     // children.
+    //     //
+    //     // `shared_prefix_count` is the count of how many nodes at the start of
+    //     // `new` and `old` share the same keys.
+    //     let (left_offset, right_offset) = match self.diff_keyed_ends(muts, old, new) {
+    //         Some(count) => count,
+    //         None => return,
+    //     };
+
+    //     // Ok, we now hopefully have a smaller range of children in the middle
+    //     // within which to re-order nodes with the same keys, remove old nodes with
+    //     // now-unused keys, and create new nodes with fresh keys.
+
+    //     let old_middle = &old[left_offset..(old.len() - right_offset)];
+    //     let new_middle = &new[left_offset..(new.len() - right_offset)];
+
+    //     debug_assert!(
+    //         !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
+    //         "keyed children must have the same number of children"
+    //     );
+
+    //     if new_middle.is_empty() {
+    //         // remove the old elements
+    //         self.remove_nodes(muts, old_middle);
+    //     } else if old_middle.is_empty() {
+    //         // there were no old elements, so just create the new elements
+    //         // we need to find the right "foothold" though - we shouldn't use the "append" at all
+    //         if left_offset == 0 {
+    //             // insert at the beginning of the old list
+    //             let foothold = &old[old.len() - right_offset];
+    //             self.create_and_insert_before(muts, new_middle, foothold);
+    //         } else if right_offset == 0 {
+    //             // insert at the end  the old list
+    //             let foothold = old.last().unwrap();
+    //             self.create_and_insert_after(muts, new_middle, foothold);
+    //         } else {
+    //             // inserting in the middle
+    //             let foothold = &old[left_offset - 1];
+    //             self.create_and_insert_after(muts, new_middle, foothold);
+    //         }
+    //     } else {
+    //         self.diff_keyed_middle(muts, old_middle, new_middle);
+    //     }
+    // }
 
     // /// Diff both ends of the children that share keys.
     // ///
@@ -437,7 +440,7 @@ impl<'b> VirtualDom {
     // /// If there is no offset, then this function returns None and the diffing is complete.
     // fn diff_keyed_ends(
     //     &mut self,
-    //     muts: &mut Renderer<'b>,
+    //     muts: &mut Mutations<'b>,
     //     old: &'b [VNode<'b>],
     //     new: &'b [VNode<'b>],
     // ) -> Option<(usize, usize)> {
@@ -496,7 +499,7 @@ impl<'b> VirtualDom {
     // #[allow(clippy::too_many_lines)]
     // fn diff_keyed_middle(
     //     &mut self,
-    //     muts: &mut Renderer<'b>,
+    //     muts: &mut Mutations<'b>,
     //     old: &'b [VNode<'b>],
     //     new: &'b [VNode<'b>],
     // ) {
@@ -677,6 +680,37 @@ impl<'b> VirtualDom {
     fn remove_nodes(&mut self, muts: &mut Mutations<'b>, nodes: &'b [VNode<'b>]) {
         //
     }
+
+    /// Push all the real nodes on the stack
+    fn push_elements_onto_stack(&mut self, node: &VNode) -> usize {
+        todo!()
+    }
+
+    pub(crate) fn create_and_insert_before(
+        &self,
+        mutations: &mut Mutations<'b>,
+        new: &[VNode],
+        after: &VNode,
+    ) {
+        let id = self.get_last_real_node(after);
+    }
+    pub(crate) fn create_and_insert_after(
+        &self,
+        mutations: &mut Mutations<'b>,
+        new: &[VNode],
+        after: &VNode,
+    ) {
+        let id = self.get_last_real_node(after);
+    }
+
+    fn get_last_real_node(&self, node: &VNode) -> ElementId {
+        match node.template.roots.last().unwrap() {
+            TemplateNode::Element { .. } => todo!(),
+            TemplateNode::Text(t) => todo!(),
+            TemplateNode::Dynamic(_) => todo!(),
+            TemplateNode::DynamicText(_) => todo!(),
+        }
+    }
 }
 
 fn matching_components<'a>(

+ 19 - 0
packages/core/src/dirty_scope.rs

@@ -0,0 +1,19 @@
+use crate::ScopeId;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DirtyScope {
+    pub height: u32,
+    pub id: ScopeId,
+}
+
+impl PartialOrd for DirtyScope {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.height.cmp(&other.height))
+    }
+}
+
+impl Ord for DirtyScope {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.height.cmp(&other.height)
+    }
+}

+ 29 - 0
packages/core/src/error_boundary.rs

@@ -0,0 +1,29 @@
+use std::{cell::RefCell, rc::Rc};
+
+use crate::{ScopeId, ScopeState};
+
+pub struct ErrorContext {
+    error: RefCell<Option<(anyhow::Error, ScopeId)>>,
+}
+
+/// Catch all errors from the children and bubble them up to this component
+///
+/// Returns the error and scope that caused the error
+pub fn use_catch_error(cx: &ScopeState) -> Option<&(anyhow::Error, ScopeId)> {
+    let err_ctx = use_error_context(cx);
+
+    let out = cx.use_hook(|| None);
+
+    if let Some(error) = err_ctx.error.take() {
+        *out = Some(error);
+    }
+
+    out.as_ref()
+}
+
+/// Create a new error context at this component.
+///
+/// This component will start to catch any errors that occur in its children.
+pub fn use_error_context(cx: &ScopeState) -> &ErrorContext {
+    cx.use_hook(|| cx.provide_context(Rc::new(ErrorContext { error: None.into() })))
+}

+ 5 - 15
packages/core/src/factory.rs

@@ -150,8 +150,7 @@ pub trait IntoDynNode<'a, A = ()> {
 
 impl<'a, 'b> IntoDynNode<'a> for () {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        todo!()
-        // self
+        DynamicNode::Fragment(VFragment::Empty(Cell::new(ElementId(0))))
     }
 }
 impl<'a, 'b> IntoDynNode<'a> for VNode<'a> {
@@ -165,23 +164,14 @@ impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
         match self {
             Some(val) => val.into_vnode(_cx),
-            None => DynamicNode::Placeholder(Default::default()),
+            None => DynamicNode::Fragment(VFragment::Empty(Cell::new(ElementId(0)))),
         }
     }
 }
 
-impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for &Option<T> {
-    fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
-        // DynamicNode::Fragment { nodes: cx., inner: () }
-        todo!()
-    }
-}
-
 impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
     fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
-        DynamicNode::Fragment(VFragment {
-            nodes: cx.bump().alloc([self.call(cx)]),
-        })
+        DynamicNode::Fragment(VFragment::NonEmpty(cx.bump().alloc([self.call(cx)])))
     }
 }
 
@@ -249,8 +239,8 @@ where
         let children = nodes.into_bump_slice();
 
         match children.len() {
-            0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
-            _ => DynamicNode::Fragment(VFragment { nodes: children }),
+            0 => DynamicNode::Fragment(VFragment::Empty(Cell::new(ElementId(0)))),
+            _ => DynamicNode::Fragment(VFragment::NonEmpty(children)),
         }
     }
 }

+ 3 - 3
packages/core/src/fragment.rs

@@ -27,8 +27,8 @@ use crate::innerlude::*;
 /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
 #[allow(non_upper_case_globals, non_snake_case)]
 pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
-    let children = cx.props.0.as_ref()?;
-    Some(VNode {
+    let children = cx.props.0.as_ref().unwrap();
+    Ok(VNode {
         node_id: children.node_id.clone(),
         key: children.key.clone(),
         parent: children.parent.clone(),
@@ -96,7 +96,7 @@ impl<'a> Properties for FragmentProps<'a> {
     type Builder = FragmentBuilder<'a, false>;
     const IS_STATIC: bool = false;
     fn builder() -> Self::Builder {
-        FragmentBuilder(None)
+        todo!()
     }
     unsafe fn memoize(&self, _other: &Self) -> bool {
         false

+ 6 - 4
packages/core/src/lib.rs

@@ -3,6 +3,8 @@ mod arena;
 mod bump_frame;
 mod create;
 mod diff;
+mod dirty_scope;
+mod error_boundary;
 mod events;
 mod factory;
 mod fragment;
@@ -15,9 +17,9 @@ mod scheduler;
 mod scope_arena;
 mod scopes;
 mod virtual_dom;
-
 pub(crate) mod innerlude {
     pub use crate::arena::*;
+    pub use crate::dirty_scope::*;
     pub use crate::events::*;
     pub use crate::fragment::*;
     pub use crate::lazynodes::*;
@@ -28,10 +30,10 @@ pub(crate) mod innerlude {
     pub use crate::scopes::*;
     pub use crate::virtual_dom::*;
 
-    /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
+    /// An [`Element`] is a possibly-errored [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
     ///
-    /// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
-    pub type Element<'a> = Option<VNode<'a>>;
+    /// An Errored [`Element`] will propagate the error to the nearest error boundary.
+    pub type Element<'a> = anyhow::Result<VNode<'a>>;
 
     /// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
     ///

+ 8 - 21
packages/core/src/nodes.rs

@@ -2,7 +2,6 @@ use crate::{any_props::AnyProps, arena::ElementId, ScopeId, ScopeState, UiEvent}
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
-    hash::Hasher,
 };
 
 pub type TemplateId = &'static str;
@@ -42,7 +41,7 @@ impl<'a> VNode<'a> {
     pub fn placeholder_template(cx: &'a ScopeState) -> Self {
         Self::template_from_dynamic_node(
             cx,
-            DynamicNode::Placeholder(Cell::new(ElementId(0))),
+            DynamicNode::Fragment(VFragment::Empty(Cell::new(ElementId(0)))),
             "dioxus-placeholder",
         )
         .unwrap()
@@ -52,8 +51,8 @@ impl<'a> VNode<'a> {
         cx: &'a ScopeState,
         node: DynamicNode<'a>,
         id: &'static str,
-    ) -> Option<Self> {
-        Some(VNode {
+    ) -> anyhow::Result<Self> {
+        Ok(VNode {
             node_id: Cell::new(ElementId(0)),
             key: None,
             parent: None,
@@ -73,8 +72,8 @@ impl<'a> VNode<'a> {
         _cx: &'a ScopeState,
         text: &'static [TemplateNode<'static>],
         id: &'static str,
-    ) -> Option<Self> {
-        Some(VNode {
+    ) -> anyhow::Result<Self> {
+        Ok(VNode {
             node_id: Cell::new(ElementId(0)),
             key: None,
             parent: None,
@@ -99,18 +98,6 @@ pub struct Template<'a> {
     pub attr_paths: &'a [&'a [u8]],
 }
 
-impl<'a> std::hash::Hash for Template<'a> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.id.hash(state);
-    }
-}
-impl Eq for Template<'_> {}
-impl PartialEq for Template<'_> {
-    fn eq(&self, other: &Self) -> bool {
-        self.id == other.id
-    }
-}
-
 /// A weird-ish variant of VNodes with way more limited types
 #[derive(Debug, Clone, Copy)]
 pub enum TemplateNode<'a> {
@@ -131,7 +118,6 @@ pub enum DynamicNode<'a> {
     Component(VComponent<'a>),
     Text(VText<'a>),
     Fragment(VFragment<'a>),
-    Placeholder(Cell<ElementId>),
 }
 
 impl<'a> DynamicNode<'a> {
@@ -165,8 +151,9 @@ pub struct VText<'a> {
 }
 
 #[derive(Debug)]
-pub struct VFragment<'a> {
-    pub nodes: &'a [VNode<'a>],
+pub enum VFragment<'a> {
+    Empty(Cell<ElementId>),
+    NonEmpty(&'a [VNode<'a>]),
 }
 
 #[derive(Debug)]

+ 1 - 1
packages/core/src/scheduler/wait.rs

@@ -72,7 +72,7 @@ impl VirtualDom {
 
             fiber.waiting_on.borrow_mut().remove(&id);
 
-            if let RenderReturn::Sync(Some(template)) = ret {
+            if let RenderReturn::Sync(Ok(template)) = ret {
                 let mutations_ref = &mut fiber.mutations.borrow_mut();
                 let mutations = &mut **mutations_ref;
                 let template: &VNode = unsafe { std::mem::transmute(template) };

+ 1 - 8
packages/core/src/scope_arena.rs

@@ -1,8 +1,8 @@
 use crate::{
     any_props::AnyProps,
     bump_frame::BumpFrame,
-    diff::DirtyScope,
     factory::RenderReturn,
+    innerlude::DirtyScope,
     innerlude::{SuspenseId, SuspenseLeaf},
     scheduler::RcWake,
     scopes::{ScopeId, ScopeState},
@@ -48,13 +48,6 @@ impl VirtualDom {
             .and_then(|id| self.scopes.get_mut(id.0).map(|f| f as *mut ScopeState))
     }
 
-    pub(crate) unsafe fn run_scope_extend<'a>(
-        &mut self,
-        scope_id: ScopeId,
-    ) -> &'a RenderReturn<'a> {
-        unsafe { self.run_scope(scope_id).extend_lifetime_ref() }
-    }
-
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
         let mut new_nodes = unsafe {
             let scope = &mut self.scopes[scope_id.0];

+ 4 - 5
packages/core/src/scopes.rs

@@ -5,8 +5,7 @@ use crate::{
     factory::RenderReturn,
     innerlude::{Scheduler, SchedulerMsg},
     lazynodes::LazyNodes,
-    nodes::VNode,
-    TaskId,
+    Element, TaskId,
 };
 use bumpalo::Bump;
 use std::future::Future;
@@ -118,7 +117,7 @@ impl ScopeState {
     ///
     /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
     pub fn root_node<'a>(&'a self) -> &'a RenderReturn<'a> {
-        let r = unsafe { &*self.current_frame().node.get() };
+        let r: &RenderReturn = unsafe { &*self.current_frame().node.get() };
         unsafe { std::mem::transmute(r) }
     }
 
@@ -325,8 +324,8 @@ impl ScopeState {
     ///     cx.render(lazy_tree)
     /// }
     ///```
-    pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
-        Some(rsx.call(self))
+    pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
+        Ok(rsx.call(self))
     }
 
     /// Store a value between renders. The foundational hook for all other hooks.

+ 0 - 28
packages/core/src/subtree.rs

@@ -28,31 +28,3 @@ pub struct Subtree {
     root: ScopeId,
     elements: Slab<ElementPath>,
 }
-
-// fn app(cx: Scope) -> Element {
-//     // whenever a user connects, they get a new connection
-//     // this requires the virtualdom to be Send + Sync
-//     rsx! {
-//         ClientForEach(|req| rsx!{
-//             Route {}
-//             Route {}
-//             Route {}
-//             Route {}
-//             Route {}
-//             Route {}
-//         })
-
-//         // windows.map(|w| {
-//         //     WebviewWindow {}
-//         //     WebviewWindow {}
-//         //     WebviewWindow {}
-//         //     WebviewWindow {}
-//         // })
-
-//         // if show_settings {
-//         //     WebviewWindow {
-//         //         Settings {}
-//         //     }
-//         // }
-//     }
-// }

+ 7 - 12
packages/core/src/virtual_dom.rs

@@ -4,11 +4,9 @@
 
 use crate::{
     any_props::VProps,
-    arena::ElementId,
-    arena::ElementRef,
-    diff::DirtyScope,
+    arena::{ElementId, ElementRef},
     factory::RenderReturn,
-    innerlude::{Mutations, Scheduler, SchedulerMsg},
+    innerlude::{DirtyScope, Mutations, Scheduler, SchedulerMsg},
     mutations::Mutation,
     nodes::{Template, TemplateId},
     scheduler::{SuspenseBoundary, SuspenseId},
@@ -469,20 +467,17 @@ impl VirtualDom {
     ///
     /// apply_edits(edits);
     /// ```
-    pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
+    pub fn rebuild(&mut self) -> Mutations {
         let mut mutations = Mutations::new(0);
 
-        match unsafe { self.run_scope_extend(ScopeId(0)) } {
+        match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
             // Rebuilding implies we append the created elements to the root
-            RenderReturn::Sync(Some(node)) => {
+            RenderReturn::Sync(Ok(node)) => {
                 let m = self.create_scope(ScopeId(0), &mut mutations, node);
                 mutations.push(Mutation::AppendChildren { m });
             }
-            // If nothing was rendered, then insert a placeholder element instead
-            RenderReturn::Sync(None) => {
-                mutations.push(Mutation::CreatePlaceholder { id: ElementId(1) });
-                mutations.push(Mutation::AppendChildren { m: 1 });
-            }
+            // If an error occurs, we should try to render the default error component and context where the error occured
+            RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),
             RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
         }
 

+ 20 - 0
packages/core/tests/element.rs

@@ -0,0 +1,20 @@
+use dioxus::prelude::*;
+use dioxus_core::SuspenseContext;
+
+/// Ensure no issues with not building the virtualdom before
+#[test]
+fn root_node_isnt_null() {
+    let dom = VirtualDom::new(|cx| render!("Hello world!"));
+
+    let scope = dom.base_scope();
+
+    // The root should be a valid pointer
+    assert_ne!(scope.root_node() as *const _, std::ptr::null_mut());
+
+    // The height should be 0
+    assert_eq!(scope.height(), 0);
+
+    // There should be a default suspense context
+    // todo: there should also be a default error boundary
+    assert!(scope.has_context::<SuspenseContext>().is_some());
+}

+ 3 - 3
packages/core/tests/task.rs

@@ -1,6 +1,6 @@
 //! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
 
-use dioxus_core::*;
+use dioxus::prelude::*;
 use std::time::Duration;
 
 #[tokio::test]
@@ -11,7 +11,7 @@ async fn it_works() {
 
     tokio::select! {
         _ = dom.wait_for_work() => {}
-        _ = tokio::time::sleep(Duration::from_millis(1000)) => {}
+        _ = tokio::time::sleep(Duration::from_millis(600)) => {}
     };
 }
 
@@ -32,5 +32,5 @@ fn app(cx: Scope) -> Element {
         });
     });
 
-    None
+    cx.render(rsx!(()))
 }

+ 1 - 1
packages/rsx/src/lib.rs

@@ -70,7 +70,7 @@ impl ToTokens for CallBody {
 
         if self.inline_cx {
             out_tokens.append_all(quote! {
-                Some({
+                Ok({
                     let __cx = cx;
                     #body
                 })