mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 06:30:20 +00:00
wip: error boundary
This commit is contained in:
parent
112c954e00
commit
0e5a59f9ed
17 changed files with 343 additions and 316 deletions
|
@ -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]
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>(
|
||||
|
|
19
packages/core/src/dirty_scope.rs
Normal file
19
packages/core/src/dirty_scope.rs
Normal 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)
|
||||
}
|
||||
}
|
29
packages/core/src/error_boundary.rs
Normal file
29
packages/core/src/error_boundary.rs
Normal 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() })))
|
||||
}
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`].
|
||||
///
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 +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];
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {}
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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
packages/core/tests/element.rs
Normal file
20
packages/core/tests/element.rs
Normal 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());
|
||||
}
|
|
@ -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!(()))
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ impl ToTokens for CallBody {
|
|||
|
||||
if self.inline_cx {
|
||||
out_tokens.append_all(quote! {
|
||||
Some({
|
||||
Ok({
|
||||
let __cx = cx;
|
||||
#body
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue