diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 83521cd3f..55e0ff30a 100644 --- a/packages/core/Cargo.toml +++ b/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] diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 4e6611235..15a73a906 100644 --- a/packages/core/src/create.rs +++ b/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( - &mut self, - template: &VNode, - idx: usize, - slot: &Cell, - mutations: &mut Mutations, - ) -> usize { - // Allocate a dynamic element reference for this text node - let id = self.next_element(template, template.template.node_paths[idx]); - - // Make sure the text node is assigned to the correct element - slot.set(id); - - // Assign the ID to the existing node in the template - mutations.push(AssignId { - path: &template.template.node_paths[idx][1..], - id, - }); - - // Since the placeholder is already in the DOM, we don't create any new nodes - 0 - } - - fn create_fragment<'a>( + pub(crate) fn create_fragment<'a>( &mut self, frag: &'a VFragment<'a>, + template: &'a VNode<'a>, + idx: usize, mutations: &mut Mutations<'a>, ) -> usize { - frag.nodes - .iter() - .fold(0, |acc, child| acc + self.create(mutations, child)) + match frag { + VFragment::NonEmpty(nodes) => nodes + .iter() + .fold(0, |acc, child| acc + self.create(mutations, child)), + + VFragment::Empty(slot) => { + // Allocate a dynamic element reference for this text node + let id = self.next_element(template, template.template.node_paths[idx]); + + // Make sure the text node is assigned to the correct element + slot.set(id); + + // Assign the ID to the existing node in the template + mutations.push(AssignId { + path: &template.template.node_paths[idx][1..], + id, + }); + + // 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), } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 17149f83a..135bec3e1 100644 --- a/packages/core/src/diff.rs +++ b/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 { - 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), + (Sync(Ok(l)), Sync(Ok(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); - } + _ => 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(Some(_))) => {} - (Async(_), Sync(Some(v))) => {} + // // 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)) => {} + // // 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(); + 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), + } + } - // 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" - // ); + fn replace_placeholder_with_nodes( + &mut self, + muts: &mut Mutations<'b>, + l: &'b std::cell::Cell, + 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, + }) + } - // if new_is_keyed && old_is_keyed { - // self.diff_keyed_children(muts, old, new); - // } else { - // self.diff_non_keyed_children(muts, old, new); - // } - // } - // } + fn replace_nodes_with_placeholder( + &mut self, + muts: &mut Mutations<'b>, + l: &'b [VNode<'b>], + r: &'b std::cell::Cell, + ) { + // + + // 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); - // } + // // 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, - // }; + // // 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. + // // 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)]; + // 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" - // ); + // 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); - // } - } + // 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>( diff --git a/packages/core/src/dirty_scope.rs b/packages/core/src/dirty_scope.rs new file mode 100644 index 000000000..4dfbd9bd3 --- /dev/null +++ b/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 { + Some(self.height.cmp(&other.height)) + } +} + +impl Ord for DirtyScope { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.height.cmp(&other.height) + } +} diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs new file mode 100644 index 000000000..c2a1947da --- /dev/null +++ b/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>, +} + +/// 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() }))) +} diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 3869d1053..c70404757 100644 --- a/packages/core/src/factory.rs +++ b/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 { 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 { - 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)), } } } diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index 19f0b1190..c044daed4 100644 --- a/packages/core/src/fragment.rs +++ b/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 diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 26f6d69a7..5598f00c9 100644 --- a/packages/core/src/lib.rs +++ b/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>; + /// An Errored [`Element`] will propagate the error to the nearest error boundary. + pub type Element<'a> = anyhow::Result>; /// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`]. /// diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 1c98b9d1f..1ab579117 100644 --- a/packages/core/src/nodes.rs +++ b/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 { - Some(VNode { + ) -> anyhow::Result { + 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 { - Some(VNode { + ) -> anyhow::Result { + 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(&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), } 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), + NonEmpty(&'a [VNode<'a>]), } #[derive(Debug)] diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 4d954bedf..3c6e7b89d 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/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) }; diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index c0552580a..b206fbe7c 100644 --- a/packages/core/src/scope_arena.rs +++ b/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]; diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 9d90acf4a..35afefc1f 100644 --- a/packages/core/src/scopes.rs +++ b/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> { - 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. diff --git a/packages/core/src/subtree.rs b/packages/core/src/subtree.rs index 0d29f3d06..57d09efe2 100644 --- a/packages/core/src/subtree.rs +++ b/packages/core/src/subtree.rs @@ -28,31 +28,3 @@ pub struct Subtree { root: ScopeId, elements: Slab, } - -// 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 {} -// // } -// // } -// } -// } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index f0c29ca51..d87b9069a 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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"), } diff --git a/packages/core/tests/element.rs b/packages/core/tests/element.rs new file mode 100644 index 000000000..83cf557e9 --- /dev/null +++ b/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::().is_some()); +} diff --git a/packages/core/tests/task.rs b/packages/core/tests/task.rs index 539d9b1aa..83c61d0b0 100644 --- a/packages/core/tests/task.rs +++ b/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!(())) } diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index d974a6c50..825f7bda7 100644 --- a/packages/rsx/src/lib.rs +++ b/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 })