wip: error boundary

This commit is contained in:
Jonathan Kelley 2022-11-22 18:38:27 -08:00
parent 112c954e00
commit 0e5a59f9ed
17 changed files with 343 additions and 316 deletions

View file

@ -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]

View file

@ -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<ElementId>,
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),
}
}

View file

@ -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),
(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<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,
})
}
// 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<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);
// }
// // 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>(

View file

@ -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)
}
}

View file

@ -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() })))
}

View file

@ -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)),
}
}
}

View file

@ -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

View file

@ -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`].
///

View file

@ -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)]

View file

@ -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) };

View file

@ -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];

View file

@ -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.

View file

@ -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 {}
// // }
// // }
// }
// }

View file

@ -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"),
}

View file

@ -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());
}

View file

@ -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!(()))
}

View file

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