diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 3a0c66c02..264a6c56d 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -28,11 +28,6 @@ longest-increasing-subsequence = "0.1.0" # internall used log = "0.4" -# # Serialize the Edits for use in Webview/Liveview instances -serde = { version = "1", features = ["derive"], optional = true } - -appendlist = "1.4.0" - futures-util = "0.3.15" smallvec = "1.6.1" @@ -43,12 +38,15 @@ futures-channel = "0.3.16" # used for noderefs once_cell = "1.8.0" -indexmap = "1.7.0" + +# # Serialize the Edits for use in Webview/Liveview instances +serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] anyhow = "1.0.42" async-std = { version = "1.9.0", features = ["attributes"] } +criterion = "0.3.5" dioxus-html = { path = "../html" } fern = { version = "0.6.0", features = ["colored"] } simple_logger = "1.13.0" @@ -57,3 +55,7 @@ simple_logger = "1.13.0" [features] default = ["serialize"] serialize = ["serde"] + +[[bench]] +name = "create" +harness = false diff --git a/packages/core/benches/create.rs b/packages/core/benches/create.rs new file mode 100644 index 000000000..2b0c26ef5 --- /dev/null +++ b/packages/core/benches/create.rs @@ -0,0 +1,22 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn fibonacci(n: u64) -> u64 { + match n { + 0 => 1, + 1 => 1, + n => fibonacci(n - 1) + fibonacci(n - 2), + } +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20)))); +} + +criterion_group!(benches, criterion_benchmark); + +fn main() { + benches(); + Criterion::default().configure_from_args().final_summary(); + // $crate::__warn_about_html_reports_feature(); + // $crate::__warn_about_cargo_bench_support_feature(); +} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index f350da8b9..87282013c 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -84,9 +84,8 @@ //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ use crate::{arena::SharedResources, innerlude::*}; -use futures_util::Future; +use futures_util::{Future, FutureExt}; use fxhash::{FxBuildHasher, FxHashMap, FxHashSet}; -use indexmap::IndexSet; use smallvec::{smallvec, SmallVec}; use std::{ @@ -113,7 +112,7 @@ pub struct DiffMachine<'bump> { pub nodes_created_stack: SmallVec<[usize; 10]>, - pub instructions: SmallVec<[DiffInstruction<'bump>; 10]>, + pub instructions: Vec>, pub scope_stack: SmallVec<[ScopeId; 5]>, @@ -124,8 +123,7 @@ pub struct DiffMachine<'bump> { /// The stack instructions we use to diff and create new nodes. /// -/// Right now, we insert an instruction for every child node we want to create and diff. This can be less efficient than -/// a custom iterator type - but this is current easier to implement. In the future, let's try interact with the stack less. +/// These instructions are essentially mini state machines that stay on top of the stack until they are finished. #[derive(Debug)] pub enum DiffInstruction<'a> { DiffNode { @@ -138,28 +136,25 @@ pub enum DiffInstruction<'a> { new: &'a [VNode<'a>], }, - // diff two lists of equally sized children - DiffEqual { - progress: usize, - old: &'a [VNode<'a>], - new: &'a [VNode<'a>], - }, - Create { node: &'a VNode<'a>, and: MountType<'a>, }, - CreateChildren { - progress: usize, + Remove { + child: &'a VNode<'a>, + }, + + RemoveChildren { children: &'a [VNode<'a>], - and: MountType<'a>, }, Mount { and: MountType<'a>, }, + PopElement, + PopScope, } @@ -168,8 +163,8 @@ pub enum MountType<'a> { Absorb, Append, Replace { old: &'a VNode<'a> }, - InsertAfter { other_node: &'a VNode<'a> }, - InsertBefore { other_node: &'a VNode<'a> }, + InsertAfter { other_node: Option<&'a VNode<'a>> }, + InsertBefore { other_node: Option<&'a VNode<'a>> }, } impl<'bump> DiffMachine<'bump> { @@ -179,7 +174,7 @@ impl<'bump> DiffMachine<'bump> { shared: &'bump SharedResources, ) -> Self { Self { - instructions: smallvec![], + instructions: Vec::with_capacity(1000), nodes_created_stack: smallvec![], mutations: edits, scope_stack: smallvec![cur_scope], @@ -209,10 +204,9 @@ impl<'bump> DiffMachine<'bump> { /// /// We do depth-first to maintain high cache locality (nodes were originally generated recursively). pub async fn work(&mut self) -> Result<()> { - // todo: don't move the reused instructions around // defer to individual functions so the compiler produces better code // large functions tend to be difficult for the compiler to work with - while let Some(instruction) = self.instructions.last_mut() { + while let Some(instruction) = self.instructions.pop() { log::debug!("Handling diff instruction: {:?}", instruction); // todo: call this less frequently, there is a bit of overhead involved @@ -220,97 +214,42 @@ impl<'bump> DiffMachine<'bump> { match instruction { DiffInstruction::PopScope => { - self.instructions.pop(); self.scope_stack.pop(); } + DiffInstruction::PopElement => { + self.mutations.pop(); + } DiffInstruction::DiffNode { old, new, .. } => { - let (old, new) = (*old, *new); - self.instructions.pop(); - self.diff_node(old, new); } - DiffInstruction::DiffEqual { progress, old, new } => { - debug_assert_eq!(old.len(), new.len()); - - if let (Some(old_child), Some(new_child)) = - (old.get(*progress), new.get(*progress)) - { - *progress += 1; - self.diff_node(old_child, new_child); - } else { - self.instructions.pop(); - } - } - - // this is slightly more complicated, we need to find a way to pause our LIS code DiffInstruction::DiffChildren { old, new } => { - let (old, new) = (*old, *new); - self.instructions.pop(); - self.diff_children(old, new); } DiffInstruction::Create { node, and } => { - let (node, and) = (*node, *and); - self.instructions.pop(); - self.nodes_created_stack.push(0); self.instructions.push(DiffInstruction::Mount { and }); - self.create_node(node); } - DiffInstruction::CreateChildren { - progress, - children, - and, - } => { - let and = *and; - - if *progress == 0 { - self.nodes_created_stack.push(0); + DiffInstruction::Remove { child } => { + for child in RealChildIterator::new(child, self.vdom) { + self.mutations.push_root(child.direct_id()); + self.mutations.remove(); } + } - if let Some(child) = children.get(*progress) { - *progress += 1; - - if *progress == children.len() { - self.instructions.pop(); - self.instructions.push(DiffInstruction::Mount { and }); - } - - self.create_node(child); - } else if children.len() == 0 { - self.instructions.pop(); + DiffInstruction::RemoveChildren { children } => { + for child in RealChildIterator::new_from_slice(children, self.vdom) { + self.mutations.push_root(child.direct_id()); + self.mutations.remove(); } } DiffInstruction::Mount { and } => { - let nodes_created = self.nodes_created_stack.pop().unwrap(); - match and { - // add the nodes from this virtual list to the parent - // used by fragments and components - MountType::Absorb => { - *self.nodes_created_stack.last_mut().unwrap() += nodes_created; - } - MountType::Append => { - self.edit_append_children(nodes_created as u32); - } - MountType::Replace { old } => { - todo!() - // self.edit_replace_with(with as u32, many as u32); - } - MountType::InsertAfter { other_node } => { - self.edit_insert_after(nodes_created as u32); - } - MountType::InsertBefore { other_node } => { - self.edit_insert_before(nodes_created as u32); - } - } - - self.instructions.pop(); + self.mount(and); } }; } @@ -318,6 +257,33 @@ impl<'bump> DiffMachine<'bump> { Ok(()) } + fn mount(&mut self, and: MountType) { + let nodes_created = self.nodes_created_stack.pop().unwrap(); + match and { + // add the nodes from this virtual list to the parent + // used by fragments and components + MountType::Absorb => { + *self.nodes_created_stack.last_mut().unwrap() += nodes_created; + } + MountType::Append => { + self.mutations.edits.push(AppendChildren { + many: nodes_created as u32, + }); + } + MountType::Replace { old } => { + todo!() + // self.mutations.replace_with(with as u32, many as u32); + } + MountType::InsertAfter { other_node } => { + self.mutations.insert_after(nodes_created as u32); + } + + MountType::InsertBefore { other_node } => { + self.mutations.insert_before(nodes_created as u32); + } + } + } + // ================================= // Tools for creating new nodes // ================================= @@ -335,21 +301,21 @@ impl<'bump> DiffMachine<'bump> { fn create_text_node(&mut self, vtext: &'bump VText<'bump>) { let real_id = self.vdom.reserve_node(); - self.edit_create_text_node(vtext.text, real_id); + self.mutations.create_text_node(vtext.text, real_id); vtext.dom_id.set(Some(real_id)); *self.nodes_created_stack.last_mut().unwrap() += 1; } fn create_suspended_node(&mut self, suspended: &'bump VSuspended) { let real_id = self.vdom.reserve_node(); - self.edit_create_placeholder(real_id); + self.mutations.create_placeholder(real_id); suspended.node.set(Some(real_id)); *self.nodes_created_stack.last_mut().unwrap() += 1; } fn create_anchor_node(&mut self, anchor: &'bump VAnchor) { let real_id = self.vdom.reserve_node(); - self.edit_create_placeholder(real_id); + self.mutations.create_placeholder(real_id); anchor.dom_id.set(Some(real_id)); *self.nodes_created_stack.last_mut().unwrap() += 1; } @@ -366,7 +332,7 @@ impl<'bump> DiffMachine<'bump> { } = element; let real_id = self.vdom.reserve_node(); - self.edit_create_element(tag_name, *namespace, real_id); + self.mutations.create_element(tag_name, *namespace, real_id); *self.nodes_created_stack.last_mut().unwrap() += 1; @@ -377,28 +343,21 @@ impl<'bump> DiffMachine<'bump> { listeners.iter().for_each(|listener| { self.fix_listener(listener); listener.mounted_node.set(Some(real_id)); - self.edit_new_event_listener(listener, cur_scope.clone()); + self.mutations + .new_event_listener(listener, cur_scope.clone()); }); for attr in *attributes { - self.edit_set_attribute(attr); + self.mutations.set_attribute(attr); } if children.len() > 0 { - self.instructions.push(DiffInstruction::CreateChildren { - children, - progress: 0, - and: MountType::Append, - }); + self.create_children_instructions(children, MountType::Append); } } fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) { - self.instructions.push(DiffInstruction::CreateChildren { - children: frag.children, - progress: 0, - and: MountType::Absorb, - }); + self.create_children_instructions(frag.children, MountType::Absorb); } fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { @@ -417,6 +376,7 @@ impl<'bump> DiffMachine<'bump> { height, ScopeChildren(vcomponent.children), self.vdom.clone(), + vcomponent.name, ) }); @@ -479,23 +439,13 @@ impl<'bump> DiffMachine<'bump> { (Component(old), Component(new)) => self.diff_component_nodes(old, new), (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new), (Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()), + (Suspended(old), Suspended(new)) => new.node.set(old.node.get()), + // Anything else is just a basic replace and create ( - Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_), - Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_), - ) => { - self.replace_and_create_many_with_many([old_node], [new_node]); - } - - // TODO: these don't properly clean up any data - (Suspended(old), new) => { - self.replace_and_create_many_with_many([old_node], [new_node]); - } - - // a node that was once real is now suspended - (old, Suspended(_)) => { - self.replace_and_create_many_with_many([old_node], [new_node]); - } + Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_), + Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_), + ) => self.replace_and_create_many_with_many(old_node, new_node), } } @@ -503,9 +453,9 @@ impl<'bump> DiffMachine<'bump> { let root = old.dom_id.get().unwrap(); if old.text != new.text { - self.edit_push_root(root); - self.edit_set_text(new.text); - self.edit_pop(); + self.mutations.push_root(root); + self.mutations.set_text(new.text); + self.mutations.pop(); } new.dom_id.set(Some(root)); @@ -545,17 +495,17 @@ impl<'bump> DiffMachine<'bump> { for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) { if old_attr.value != new_attr.value { please_commit(&mut self.mutations.edits); - self.edit_set_attribute(new_attr); + self.mutations.set_attribute(new_attr); } } } else { // TODO: provide some sort of report on how "good" the diffing was please_commit(&mut self.mutations.edits); for attribute in old.attributes { - self.edit_remove_attribute(attribute); + self.mutations.remove_attribute(attribute); } for attribute in new.attributes { - self.edit_set_attribute(attribute) + self.mutations.set_attribute(attribute) } } @@ -572,8 +522,8 @@ impl<'bump> DiffMachine<'bump> { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { if old_l.event != new_l.event { please_commit(&mut self.mutations.edits); - self.edit_remove_event_listener(old_l.event); - self.edit_new_event_listener(new_l, cur_scope); + self.mutations.remove_event_listener(old_l.event); + self.mutations.new_event_listener(new_l, cur_scope); } new_l.mounted_node.set(old_l.mounted_node.get()); self.fix_listener(new_l); @@ -581,11 +531,11 @@ impl<'bump> DiffMachine<'bump> { } else { please_commit(&mut self.mutations.edits); for listener in old.listeners { - self.edit_remove_event_listener(listener.event); + self.mutations.remove_event_listener(listener.event); } for listener in new.listeners { listener.mounted_node.set(Some(root)); - self.edit_new_event_listener(listener, cur_scope); + self.mutations.new_event_listener(listener, cur_scope); // Make sure the listener gets attached to the scope list self.fix_listener(listener); @@ -593,7 +543,7 @@ impl<'bump> DiffMachine<'bump> { } if has_comitted { - self.edit_pop(); + self.mutations.pop(); } self.diff_children(old.children, new.children); @@ -646,8 +596,8 @@ impl<'bump> DiffMachine<'bump> { // // remove any leftovers // for to_remove in old_iter { - // self.edit_push_root(to_remove.direct_id()); - // self.edit_remove(); + // self.mutations.push_root(to_remove.direct_id()); + // self.mutations.remove(); // } // // seems like we could combine this into a single instruction.... @@ -670,6 +620,26 @@ impl<'bump> DiffMachine<'bump> { self.diff_children(old.children, new.children); } + // ============================================= + // Utilites for creating new diff instructions + // ============================================= + + fn create_children_instructions( + &mut self, + children: &'bump [VNode<'bump>], + and: MountType<'bump>, + ) { + self.nodes_created_stack.push(0); + self.instructions.push(DiffInstruction::Mount { and }); + + for child in children.into_iter().rev() { + self.instructions.push(DiffInstruction::Create { + and: MountType::Absorb, + node: child, + }); + } + } + /// Destroy a scope and all of its descendents. /// /// Calling this will run the destuctors on all hooks in the tree. @@ -727,11 +697,7 @@ impl<'bump> DiffMachine<'bump> { // Completely adding new nodes, removing any placeholder if it exists (IS_EMPTY, IS_NOT_EMPTY) => { - self.instructions.push(DiffInstruction::CreateChildren { - children: new, - progress: 0, - and: MountType::Append, - }); + self.create_children_instructions(new, MountType::Append); } // Completely removing old nodes and putting an anchor in its place @@ -755,11 +721,10 @@ impl<'bump> DiffMachine<'bump> { // Replace the anchor with whatever new nodes are coming down the pipe (VNode::Anchor(anchor), _) => { - self.instructions.push(DiffInstruction::CreateChildren { - children: new, - progress: 0, - and: MountType::Replace { old: first_old }, - }); + self.create_children_instructions( + new, + MountType::Replace { old: first_old }, + ); } // Replace whatever nodes are sitting there with the anchor @@ -905,13 +870,13 @@ impl<'bump> DiffMachine<'bump> { if shared_prefix_count == old.len() { // Load the last element let last_node = self.find_last_element(new.last().unwrap()).direct_id(); - self.edit_push_root(last_node); + self.mutations.push_root(last_node); // Create the new children and insert them after // todo!(); // let meta = self.create_children(&new[shared_prefix_count..]); - // self.edit_insert_after(meta.added_to_stack); + // self.mutations.insert_after(meta.added_to_stack); return KeyedPrefixResult::Finished; } @@ -937,7 +902,7 @@ impl<'bump> DiffMachine<'bump> { for child in new { todo!(); // let meta = self.create_vnode(child); - // self.edit_append_children(meta.added_to_stack); + // self.mutations.append_children(meta.added_to_stack); } } @@ -1077,7 +1042,7 @@ impl<'bump> DiffMachine<'bump> { root = Some(new_anchor); // let anchor_el = self.find_first_element(new_anchor); - // self.edit_push_root(anchor_el.direct_id()); + // self.mutations.push_root(anchor_el.direct_id()); // // let mut pushed = false; 'inner: loop { @@ -1100,27 +1065,27 @@ impl<'bump> DiffMachine<'bump> { // now move all the nodes into the right spot for child in RealChildIterator::new(next_new, self.vdom) { let el = child.direct_id(); - self.edit_push_root(el); - self.edit_insert_before(1); + self.mutations.push_root(el); + self.mutations.insert_before(1); } } else { self.instructions.push(DiffInstruction::Create { node: next_new, and: MountType::InsertBefore { - other_node: new_anchor, + other_node: Some(new_anchor), }, }); } } } - self.edit_pop(); + self.mutations.pop(); } let final_lis_node = root.unwrap(); let final_el_node = self.find_last_element(final_lis_node); let final_el = final_el_node.direct_id(); - self.edit_push_root(final_el); + self.mutations.push_root(final_el); let mut last_iter = new.iter().rev().enumerate(); let last_key = final_lis_node.key().unwrap(); @@ -1145,8 +1110,8 @@ impl<'bump> DiffMachine<'bump> { // now move all the nodes into the right spot for child in RealChildIterator::new(last_node, self.vdom) { let el = child.direct_id(); - self.edit_push_root(el); - self.edit_insert_after(1); + self.mutations.push_root(el); + self.mutations.insert_after(1); } } else { eprintln!("key is not contained {:?}", key); @@ -1154,10 +1119,10 @@ impl<'bump> DiffMachine<'bump> { // insert it before the current milestone todo!(); // let meta = self.create_vnode(last_node); - // self.edit_insert_after(meta.added_to_stack); + // self.mutations.insert_after(meta.added_to_stack); } } - self.edit_pop(); + self.mutations.pop(); } // Diff the suffix of keyed children that share the same keys in the same order. @@ -1198,19 +1163,17 @@ impl<'bump> DiffMachine<'bump> { match old.len().cmp(&new.len()) { // old.len > new.len -> removing some nodes Ordering::Greater => { - // diff them together - for (new_child, old_child) in new.iter().zip(old.iter()) { - self.diff_node(old_child, new_child); + // Generate instructions to diff the existing elements + for (new_child, old_child) in new.iter().zip(old.iter()).rev() { + self.instructions.push(DiffInstruction::DiffNode { + new: new_child, + old: old_child, + }); } - // todo: we would emit fewer instructions if we just did a replace many - // remove whatever is still dangling - for item in &old[new.len()..] { - for i in RealChildIterator::new(item, self.vdom) { - self.edit_push_root(i.direct_id()); - self.edit_remove(); - } - } + self.instructions.push(DiffInstruction::RemoveChildren { + children: &old[new.len()..], + }); } // old.len < new.len -> adding some nodes @@ -1218,27 +1181,30 @@ impl<'bump> DiffMachine<'bump> { // // we need to save the last old element and then replace it with all the new ones Ordering::Less => { - // Add the new elements to the last old element while it still exists - let last = self.find_last_element(old.last().unwrap()); - self.edit_push_root(last.direct_id()); - - // create the rest and insert them - todo!(); - // let meta = self.create_children(&new[old.len()..]); - // self.edit_insert_after(meta.added_to_stack); - - self.edit_pop(); - - // diff the rest - for (new_child, old_child) in new.iter().zip(old.iter()) { - self.diff_node(old_child, new_child) + // Generate instructions to diff the existing elements + for (new_child, old_child) in new.iter().zip(old.iter()).rev() { + self.instructions.push(DiffInstruction::DiffNode { + new: new_child, + old: old_child, + }); } + + // Generate instructions to add in the new elements + self.create_children_instructions( + &new[old.len()..], + MountType::InsertAfter { + other_node: old.last(), + }, + ); } // old.len == new.len -> no nodes added/removed, but perhaps changed Ordering::Equal => { - for (new_child, old_child) in new.iter().zip(old.iter()) { - self.diff_node(old_child, new_child); + for (new_child, old_child) in new.iter().zip(old.iter()).rev() { + self.instructions.push(DiffInstruction::DiffNode { + new: new_child, + old: old_child, + }); } } } @@ -1247,24 +1213,24 @@ impl<'bump> DiffMachine<'bump> { // ====================== // Support methods // ====================== - // Remove all of a node's children. - // - // The change list stack must have this shape upon entry to this function: - // - // [... parent] - // - // When this function returns, the change list stack is in the same state. - fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) { - // debug_assert!(self.traversal_is_committed()); - log::debug!("REMOVING CHILDREN"); - for _child in old { - // registry.remove_subtree(child); - } - // Fast way to remove all children: set the node's textContent to an empty - // string. - todo!() - // self.set_inner_text(""); - } + // // Remove all of a node's children. + // // + // // The change list stack must have this shape upon entry to this function: + // // + // // [... parent] + // // + // // When this function returns, the change list stack is in the same state. + // fn remove_all_children(&mut self, old: &'bump [VNode<'bump>]) { + // // debug_assert!(self.traversal_is_committed()); + // log::debug!("REMOVING CHILDREN"); + // for _child in old { + // // registry.remove_subtree(child); + // } + // // Fast way to remove all children: set the node's textContent to an empty + // // string. + // todo!() + // // self.set_inner_text(""); + // } // Remove the current child and all of its following siblings. // // The change list stack must have this shape upon entry to this function: @@ -1324,8 +1290,16 @@ impl<'bump> DiffMachine<'bump> { } } - fn remove_child(&mut self, node: &'bump VNode<'bump>) { - self.replace_and_create_many_with_many(Some(node), None); + // fn remove_child(&mut self, node: &'bump VNode<'bump>) { + // self.replace_and_create_many_with_many(Some(node), None); + // } + + fn replace_many_with_many(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) { + // + } + + fn replace_one_with_many(&mut self, old: &'bump VNode<'bump>, new: &'bump [VNode<'bump>]) { + // } /// Remove all the old nodes and replace them with newly created new nodes. @@ -1333,11 +1307,13 @@ impl<'bump> DiffMachine<'bump> { /// The new nodes *will* be created - don't create them yourself! fn replace_and_create_many_with_many( &mut self, - old_nodes: impl IntoIterator>, - new_nodes: impl IntoIterator>, + old_node: &'bump VNode<'bump>, + new_node: &'bump VNode<'bump>, + // old_nodes: impl IntoIterator>, + // new_nodes: impl IntoIterator>, ) { let mut nodes_to_replace = Vec::new(); - let mut nodes_to_search = old_nodes.into_iter().collect::>(); + let mut nodes_to_search = vec![old_node]; let mut scopes_obliterated = Vec::new(); while let Some(node) = nodes_to_search.pop() { match &node { @@ -1368,20 +1344,25 @@ impl<'bump> DiffMachine<'bump> { // self.create_garbage(node); } - let n = nodes_to_replace.len(); - for node in nodes_to_replace { - self.edit_push_root(node); - } + // let n = nodes_to_replace.len(); + // for node in nodes_to_replace { + // self.mutations.push_root(node); + // } - let mut nodes_created = 0; - for node in new_nodes { - todo!(); - // let meta = self.create_vnode(node); - // nodes_created += meta.added_to_stack; - } + // let mut nodes_created = 0; + // for node in new_nodes { + // todo!(); + // let meta = self.create_vnode(node); + // nodes_created += meta.added_to_stack; + // } // if 0 nodes are created, then it gets interperted as a deletion - self.edit_replace_with(n as u32, nodes_created); + // self.mutations.replace_with(n as u32, nodes_created); + + // self.instructions.push(DiffInstruction::CreateChildren { + // and: MountType::Replace { old: None }, + // children: + // }); // obliterate! for scope in scopes_obliterated { @@ -1411,12 +1392,12 @@ impl<'bump> DiffMachine<'bump> { old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>, ) { - self.edit_push_root(anchor); + self.mutations.push_root(anchor); todo!(); // let meta = self.create_vnode(new_node); - // self.edit_replace_with(1, meta.added_to_stack); + // self.mutations.replace_with(1, meta.added_to_stack); // self.create_garbage(old_node); - self.edit_pop(); + self.mutations.pop(); } fn remove_vnode(&mut self, node: &'bump VNode<'bump>) { @@ -1470,138 +1451,6 @@ impl<'bump> DiffMachine<'bump> { // if we have, then we're trying to alias it, which is not allowed unsafe { self.vdom.get_scope(*id) } } - - // Navigation - pub(crate) fn edit_push_root(&mut self, root: ElementId) { - let id = root.as_u64(); - self.mutations.edits.push(PushRoot { id }); - } - - pub(crate) fn edit_pop(&mut self) { - self.mutations.edits.push(PopRoot {}); - } - - // Add Nodes to the dom - // add m nodes from the stack - pub(crate) fn edit_append_children(&mut self, many: u32) { - self.mutations.edits.push(AppendChildren { many }); - } - - // replace the n-m node on the stack with the m nodes - // ends with the last element of the chain on the top of the stack - pub(crate) fn edit_replace_with(&mut self, n: u32, m: u32) { - self.mutations.edits.push(ReplaceWith { n, m }); - } - - pub(crate) fn edit_insert_after(&mut self, n: u32) { - self.mutations.edits.push(InsertAfter { n }); - } - - pub(crate) fn edit_insert_before(&mut self, n: u32) { - self.mutations.edits.push(InsertBefore { n }); - } - - // Remove Nodesfrom the dom - pub(crate) fn edit_remove(&mut self) { - self.mutations.edits.push(Remove); - } - - // Create - pub(crate) fn edit_create_text_node(&mut self, text: &'bump str, id: ElementId) { - let id = id.as_u64(); - self.mutations.edits.push(CreateTextNode { text, id }); - } - - pub(crate) fn edit_create_element( - &mut self, - tag: &'static str, - ns: Option<&'static str>, - id: ElementId, - ) { - let id = id.as_u64(); - match ns { - Some(ns) => self.mutations.edits.push(CreateElementNs { id, ns, tag }), - None => self.mutations.edits.push(CreateElement { id, tag }), - } - } - - // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom - pub(crate) fn edit_create_placeholder(&mut self, id: ElementId) { - let id = id.as_u64(); - self.mutations.edits.push(CreatePlaceholder { id }); - } - - // events - pub(crate) fn edit_new_event_listener(&mut self, listener: &Listener, scope: ScopeId) { - let Listener { - event, - mounted_node, - .. - } = listener; - - let element_id = mounted_node.get().unwrap().as_u64(); - - self.mutations.edits.push(NewEventListener { - scope, - event_name: event, - mounted_node_id: element_id, - }); - } - - pub(crate) fn edit_remove_event_listener(&mut self, event: &'static str) { - self.mutations.edits.push(RemoveEventListener { event }); - } - - // modify - pub(crate) fn edit_set_text(&mut self, text: &'bump str) { - self.mutations.edits.push(SetText { text }); - } - - pub(crate) fn edit_set_attribute(&mut self, attribute: &'bump Attribute) { - let Attribute { - name, - value, - is_static, - is_volatile, - namespace, - } = attribute; - // field: &'static str, - // value: &'bump str, - // ns: Option<&'static str>, - self.mutations.edits.push(SetAttribute { - field: name, - value, - ns: *namespace, - }); - } - - pub(crate) fn edit_set_attribute_ns( - &mut self, - attribute: &'bump Attribute, - namespace: &'bump str, - ) { - let Attribute { - name, - value, - is_static, - is_volatile, - // namespace, - .. - } = attribute; - // field: &'static str, - // value: &'bump str, - // ns: Option<&'static str>, - self.mutations.edits.push(SetAttribute { - field: name, - value, - ns: Some(namespace), - }); - } - - pub(crate) fn edit_remove_attribute(&mut self, attribute: &Attribute) { - let name = attribute.name; - self.mutations.edits.push(RemoveAttribute { name }); - } } // When we create new nodes, we need to propagate some information back up the call chain. @@ -1664,6 +1513,14 @@ impl<'a> RealChildIterator<'a> { stack: smallvec::smallvec![(0, starter)], } } + + pub fn new_from_slice(nodes: &'a [VNode<'a>], scopes: &'a SharedResources) -> Self { + let mut stack = smallvec::smallvec![]; + for node in nodes { + stack.push((0, node)); + } + Self { scopes, stack } + } // keep the memory around pub fn reset_with(&mut self, node: &'a VNode<'a>) { self.stack.clear(); @@ -1774,16 +1631,3 @@ fn compare_strs(a: &str, b: &str) -> bool { true } } - -struct DfsIterator<'a> { - idx: usize, - node: Option<(&'a VNode<'a>, &'a VNode<'a>)>, - nodes: Option<(&'a [VNode<'a>], &'a [VNode<'a>])>, -} -impl<'a> Iterator for DfsIterator<'a> { - type Item = (&'a VNode<'a>, &'a VNode<'a>); - - fn next(&mut self) -> Option { - todo!() - } -} diff --git a/packages/core/src/editor.rs b/packages/core/src/editor.rs index 3bc959b4a..b6cc620d8 100644 --- a/packages/core/src/editor.rs +++ b/packages/core/src/editor.rs @@ -9,7 +9,7 @@ use crate::innerlude::ScopeId; /// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the /// network or through FFI boundaries. -#[derive(Debug)] +#[derive(Debug, PartialEq)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/packages/core/src/hooklist.rs b/packages/core/src/hooklist.rs index 06ed54d32..3781fc2ed 100644 --- a/packages/core/src/hooklist.rs +++ b/packages/core/src/hooklist.rs @@ -1,10 +1,10 @@ use std::{ any::Any, - cell::{Cell, UnsafeCell}, + cell::{Cell, RefCell, UnsafeCell}, }; pub struct HookList { - vals: appendlist::AppendList>>, + vals: RefCell>>>, idx: Cell, } @@ -27,35 +27,17 @@ impl InnerHook { } } } - impl HookList { - /// Unsafely get a mutable reference to any of the hooks - /// - /// This is unsafe because an &mut T might be aliased if the hook data is already borrowed/in use in the component - /// - /// This method should be reserved for internal methods that are guaranteed that this hook is not aliased anyhwere - /// inside the component body, or outside into children components. - /// - /// This method is currently used only by the suspense system whose hook implementation guarantees that all &T is dropped - /// before the suspense handler is ran. - pub(crate) unsafe fn get_mut(&self, idx: usize) -> Option<&mut T> { - self.vals.get(idx).and_then(|inn| { - let raw_box = unsafe { &mut *inn.cell.get() }; - raw_box.downcast_mut::() - }) - } - pub(crate) fn next(&self) -> Option<&mut T> { - self.vals.get(self.idx.get()).and_then(|inn| { + self.vals.borrow().get(self.idx.get()).and_then(|inn| { self.idx.set(self.idx.get() + 1); let raw_box = unsafe { &mut *inn.cell.get() }; raw_box.downcast_mut::() }) } - #[inline] pub(crate) fn push(&self, new: T) { - self.vals.push(InnerHook::new(Box::new(new))) + self.vals.borrow_mut().push(InnerHook::new(Box::new(new))) } /// This resets the internal iterator count @@ -70,7 +52,7 @@ impl HookList { #[inline] pub(crate) fn len(&self) -> usize { - self.vals.len() + self.vals.borrow().len() } #[inline] diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index d26d4c161..09d287d90 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -12,15 +12,15 @@ pub use crate::innerlude::{ format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, - EventTrigger, LazyNodes, NodeFactory, Properties, ScopeId, SuspendedContext, VNode, VirtualDom, - VirtualEvent, FC, + EventTrigger, LazyNodes, Mutations, NodeFactory, Properties, ScopeId, SuspendedContext, VNode, + VirtualDom, VirtualEvent, FC, }; pub mod prelude { pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::context::Context; pub use crate::hooks::*; - pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, NodeFactory, FC}; + pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, Mutations, NodeFactory, FC}; pub use crate::nodes::VNode; pub use crate::VirtualDom; pub use dioxus_core_macro::{format_args_f, html, rsx, Props}; @@ -39,6 +39,7 @@ pub(crate) mod innerlude { pub use crate::heuristics::*; pub use crate::hooklist::*; pub use crate::hooks::*; + pub use crate::mutations::*; pub use crate::nodes::*; pub use crate::scheduler::*; pub use crate::scope::*; @@ -68,6 +69,7 @@ pub mod events; pub mod heuristics; pub mod hooklist; pub mod hooks; +pub mod mutations; pub mod nodes; pub mod scheduler; pub mod scope; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs new file mode 100644 index 000000000..ba43f9b4f --- /dev/null +++ b/packages/core/src/mutations.rs @@ -0,0 +1,154 @@ +use std::any::Any; + +use crate::innerlude::*; + +/// The "Mutations" object holds the changes that need to be made to the DOM. +/// +#[derive(Debug)] +pub struct Mutations<'s> { + pub edits: Vec>, + pub noderefs: Vec>, +} +use DomEdit::*; + +impl<'bump> Mutations<'bump> { + pub fn new() -> Self { + let edits = Vec::new(); + let noderefs = Vec::new(); + Self { edits, noderefs } + } + + pub fn extend(&mut self, other: &mut Mutations) {} + + // Navigation + pub(crate) fn push_root(&mut self, root: ElementId) { + let id = root.as_u64(); + self.edits.push(PushRoot { id }); + } + pub(crate) fn pop(&mut self) { + self.edits.push(PopRoot {}); + } + // replace the n-m node on the stack with the m nodes + // ends with the last element of the chain on the top of the stack + pub(crate) fn replace_with(&mut self, n: u32, m: u32) { + self.edits.push(ReplaceWith { n, m }); + } + pub(crate) fn insert_after(&mut self, n: u32) { + self.edits.push(InsertAfter { n }); + } + pub(crate) fn insert_before(&mut self, n: u32) { + self.edits.push(InsertBefore { n }); + } + // Remove Nodesfrom the dom + pub(crate) fn remove(&mut self) { + self.edits.push(Remove); + } + // Create + pub(crate) fn create_text_node(&mut self, text: &'bump str, id: ElementId) { + let id = id.as_u64(); + self.edits.push(CreateTextNode { text, id }); + } + pub(crate) fn create_element( + &mut self, + tag: &'static str, + ns: Option<&'static str>, + id: ElementId, + ) { + let id = id.as_u64(); + match ns { + Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }), + None => self.edits.push(CreateElement { id, tag }), + } + } + // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom + pub(crate) fn create_placeholder(&mut self, id: ElementId) { + let id = id.as_u64(); + self.edits.push(CreatePlaceholder { id }); + } + // events + pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) { + let Listener { + event, + mounted_node, + .. + } = listener; + + let element_id = mounted_node.get().unwrap().as_u64(); + + self.edits.push(NewEventListener { + scope, + event_name: event, + mounted_node_id: element_id, + }); + } + pub(crate) fn remove_event_listener(&mut self, event: &'static str) { + self.edits.push(RemoveEventListener { event }); + } + // modify + pub(crate) fn set_text(&mut self, text: &'bump str) { + self.edits.push(SetText { text }); + } + pub(crate) fn set_attribute(&mut self, attribute: &'bump Attribute) { + let Attribute { + name, + value, + is_static, + is_volatile, + namespace, + } = attribute; + + self.edits.push(SetAttribute { + field: name, + value, + ns: *namespace, + }); + } + pub(crate) fn set_attribute_ns(&mut self, attribute: &'bump Attribute, namespace: &'bump str) { + let Attribute { + name, + value, + is_static, + is_volatile, + .. + } = attribute; + + self.edits.push(SetAttribute { + field: name, + value, + ns: Some(namespace), + }); + } + pub(crate) fn remove_attribute(&mut self, attribute: &Attribute) { + let name = attribute.name; + self.edits.push(RemoveAttribute { name }); + } +} + +// refs are only assigned once +pub struct NodeRefMutation<'a> { + element: &'a mut Option>>, + element_id: ElementId, +} + +impl<'a> std::fmt::Debug for NodeRefMutation<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NodeRefMutation") + .field("element_id", &self.element_id) + .finish() + } +} + +impl<'a> NodeRefMutation<'a> { + pub fn downcast_ref(&self) -> Option<&T> { + self.element + .as_ref() + .and_then(|f| f.get()) + .and_then(|f| f.downcast_ref::()) + } + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.element + .as_mut() + .and_then(|f| f.get_mut()) + .and_then(|f| f.downcast_mut::()) + } +} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 05798f1ac..c9b1c3db8 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -164,6 +164,8 @@ pub struct VComponent<'src> { pub can_memoize: bool, + pub name: &'static str, + // a pointer into the bump arena (given by the 'src lifetime) pub(crate) raw_props: *const (), @@ -361,6 +363,8 @@ impl<'a> NodeFactory<'a> { { let bump = self.bump(); + let name = crate::util::type_name_of(component); + // We don't want the fat part of the fat pointer // This function does static dispatch so we don't need any VTable stuff let children: &'a V = bump.alloc(children); @@ -426,6 +430,7 @@ impl<'a> NodeFactory<'a> { can_memoize: P::IS_STATIC, ass_scope: Cell::new(None), key, + name, })) } @@ -454,6 +459,25 @@ impl<'a> NodeFactory<'a> { pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> { let children = node_iter.into_vnode_list(self); + // TODO + // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning + // + // if cfg!(debug_assertions) { + // if children.len() > 1 { + // if children.last().unwrap().key().is_none() { + // log::error!( + // r#" + // Warning: Each child in an array or iterator should have a unique "key" prop. + // Not providing a key will lead to poor performance with lists. + // See docs.rs/dioxus for more information. + // --- + // To help you identify where this error is coming from, we've generated a backtrace. + // "#, + // ); + // } + // } + // } + VNode::Fragment(VFragment { children, key: None, @@ -497,22 +521,6 @@ where nodes.push(node.into_vnode(cx)); } - if cfg!(debug_assertions) { - if nodes.len() > 1 { - if nodes.last().unwrap().key().is_none() { - log::error!( - r#" - Warning: Each child in an array or iterator should have a unique "key" prop. - Not providing a key will lead to poor performance with lists. - See docs.rs/dioxus for more information. - --- - To help you identify where this error is coming from, we've generated a backtrace. - "#, - ); - } - } - } - if nodes.len() == 0 { nodes.push(VNode::Anchor(VAnchor { dom_id: empty_cell(), @@ -657,9 +665,13 @@ impl Debug for VNode<'_> { VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text), VNode::Anchor(a) => write!(s, "VAnchor"), - VNode::Fragment(_) => write!(s, "fragment"), - VNode::Suspended { .. } => write!(s, "suspended"), - VNode::Component(_) => write!(s, "component"), + VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children), + VNode::Suspended { .. } => write!(s, "VSuspended"), + VNode::Component(comp) => write!( + s, + "VComponent {{ fc: {:?}, children: {:?} }}", + comp.name, comp.children + ), } } } diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 22e55a994..b8fc07f15 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -33,58 +33,10 @@ use futures_util::pin_mut; use futures_util::Future; use futures_util::FutureExt; use futures_util::StreamExt; -use indexmap::IndexSet; use smallvec::SmallVec; use crate::innerlude::*; -/// The "Mutations" object holds the changes that need to be made to the DOM. -/// -#[derive(Debug)] -pub struct Mutations<'s> { - pub edits: Vec>, - pub noderefs: Vec>, -} - -impl<'s> Mutations<'s> { - pub fn new() -> Self { - let edits = Vec::new(); - let noderefs = Vec::new(); - Self { edits, noderefs } - } - - pub fn extend(&mut self, other: &mut Mutations) {} -} - -// refs are only assigned once -pub struct NodeRefMutation<'a> { - element: &'a mut Option>>, - element_id: ElementId, -} - -impl<'a> std::fmt::Debug for NodeRefMutation<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NodeRefMutation") - .field("element_id", &self.element_id) - .finish() - } -} - -impl<'a> NodeRefMutation<'a> { - pub fn downcast_ref(&self) -> Option<&T> { - self.element - .as_ref() - .and_then(|f| f.get()) - .and_then(|f| f.downcast_ref::()) - } - pub fn downcast_mut(&mut self) -> Option<&mut T> { - self.element - .as_mut() - .and_then(|f| f.get_mut()) - .and_then(|f| f.downcast_mut::()) - } -} - pub struct Scheduler { current_priority: EventPriority, @@ -432,7 +384,7 @@ pub struct Waypoint { pub struct PriortySystem { pub pending_scopes: Vec, - pub dirty_scopes: IndexSet, + pub dirty_scopes: HashSet, } impl PriortySystem { diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 673944df2..00b4f251f 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -43,6 +43,9 @@ pub struct Scope { pub(crate) hooks: HookList, pub(crate) shared_contexts: RefCell>>, + // meta + pub(crate) function_name: &'static str, + // A reference to the resources shared by all the comonents pub(crate) vdom: SharedResources, } @@ -73,6 +76,8 @@ impl Scope { child_nodes: ScopeChildren, vdom: SharedResources, + + function_name: &'static str, ) -> Self { let child_nodes = unsafe { child_nodes.extend_lifetime() }; @@ -84,6 +89,7 @@ impl Scope { } Self { + function_name, child_nodes, caller, parent_idx: parent, @@ -141,7 +147,7 @@ impl Scope { // the user's component succeeded. We can safely cycle to the next frame self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; self.frames.cycle_frame(); - log::debug!("Cycle okay"); + log::debug!("Successfully rendered component"); Ok(()) } } diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index 0add0f456..c7309fe68 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -8,6 +8,10 @@ pub fn empty_cell() -> Cell> { Cell::new(None) } +pub fn type_name_of(_: T) -> &'static str { + std::any::type_name::() +} + // /// A helper type that lets scopes be ordered by their height // #[derive(Debug, Clone, Copy, PartialEq, Eq)] // pub struct HeightMarker { diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index ca355f986..d6ab528a8 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -40,7 +40,7 @@ pub struct VirtualDom { /// /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas /// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually. - shared: SharedResources, + pub shared: SharedResources, /// The index of the root component /// Should always be the first (gen=0, id=0) @@ -111,7 +111,8 @@ impl VirtualDom { let base_scope = components.insert_scope_with_key(move |myidx| { let caller = NodeFactory::create_component_caller(root, props_ptr as *const _); - Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), link) + let name = type_name_of(root); + Scope::new(caller, myidx, None, 0, ScopeChildren(&[]), link, name) }); Self { @@ -150,35 +151,18 @@ impl VirtualDom { /// The diff machine expects the RealDom's stack to be the root of the application /// /// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed - /// through "run" - /// + /// through "run". We completely avoid the task scheduler infrastructure. pub fn rebuild<'s>(&'s mut self) -> Result> { - let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared); + let mut fut = self.rebuild_async().boxed_local(); - let cur_component = diff_machine - .get_scope_mut(&self.base_scope) - .expect("The base scope should never be moved"); - - // todo!(); - - // // We run the component. If it succeeds, then we can diff it and add the changes to the dom. - if cur_component.run_scope().is_ok() { - diff_machine.instructions.push(DiffInstruction::Create { - node: cur_component.frames.fin_head(), - and: MountType::Append, - }); - } else { - // todo: should this be a hard error? - log::warn!( - "Component failed to run succesfully during rebuild. - This does not result in a failed rebuild, but indicates a logic failure within your app." - ); + loop { + if let Some(edits) = (&mut fut).now_or_never() { + break edits; + } } - - Ok(diff_machine.mutations) } - /// Rebuild the dom + /// Rebuild the dom from the ground up pub async fn rebuild_async<'s>(&'s mut self) -> Result> { let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared); @@ -186,8 +170,6 @@ impl VirtualDom { .get_scope_mut(&self.base_scope) .expect("The base scope should never be moved"); - // todo!(); - // // We run the component. If it succeeds, then we can diff it and add the changes to the dom. if cur_component.run_scope().is_ok() { diff_machine.instructions.push(DiffInstruction::Create { @@ -205,6 +187,27 @@ impl VirtualDom { Ok(diff_machine.mutations) } + + // diff the dom with itself + pub async fn diff_async<'s>(&'s mut self) -> Result> { + let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared); + + let cur_component = diff_machine + .get_scope_mut(&self.base_scope) + .expect("The base scope should never be moved"); + + cur_component.run_scope().unwrap(); + + diff_machine.instructions.push(DiffInstruction::DiffNode { + old: cur_component.frames.wip_head(), + new: cur_component.frames.fin_head(), + }); + + diff_machine.work().await.unwrap(); + + Ok(diff_machine.mutations) + } + /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete. /// /// This method will not wait for any suspended nodes to complete. diff --git a/packages/core/tests/iterative.rs b/packages/core/tests/create_iterative.rs similarity index 97% rename from packages/core/tests/iterative.rs rename to packages/core/tests/create_iterative.rs index 573bd4459..e797fff05 100644 --- a/packages/core/tests/iterative.rs +++ b/packages/core/tests/create_iterative.rs @@ -104,9 +104,7 @@ async fn test_iterative_create_components() { static Child: FC<()> = |cx| { cx.render(rsx! { h1 {} - div { - {cx.children()} - } + div { {cx.children()} } p {} }) }; diff --git a/packages/core/tests/debugdiff.rs b/packages/core/tests/debugdiff.rs new file mode 100644 index 000000000..9f6aac6c1 --- /dev/null +++ b/packages/core/tests/debugdiff.rs @@ -0,0 +1,2 @@ +/// A virtualdom wrapper used for testing purposes. +pub struct DebugDiff {} diff --git a/packages/core/tests/diff_iterative.rs b/packages/core/tests/diff_iterative.rs new file mode 100644 index 000000000..f5fba7852 --- /dev/null +++ b/packages/core/tests/diff_iterative.rs @@ -0,0 +1,46 @@ +//! tests to prove that the iterative implementation works + +use dioxus::prelude::*; + +mod test_logging; +use dioxus_core as dioxus; +use dioxus_html as dioxus_elements; + +#[async_std::test] +async fn test_iterative_create_components() { + static App: FC<()> = |cx| { + // test root fragments + cx.render(rsx! { + Child { "abc1" } + Child { "abc2" } + Child { "abc3" } + }) + }; + + fn Child(cx: Context<()>) -> DomTree { + // test root fragments, anchors, and ChildNode type + cx.render(rsx! { + h1 {} + div { {cx.children()} } + Fragment { + Fragment { + Fragment { + "wozza" + } + } + } + {(0..0).map(|f| rsx!{ div { "walalla"}})} + p {} + }) + } + + test_logging::set_up_logging(); + + let mut dom = VirtualDom::new(App); + + let mutations = dom.rebuild_async().await.unwrap(); + dbg!(mutations); + + let mutations = dom.diff_async().await.unwrap(); + dbg!(mutations); +} diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 9f74901ce..84b69120d 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -9,12 +9,13 @@ use bumpalo::Bump; use anyhow::{Context, Result}; use dioxus::{ arena::SharedResources, - diff::{CreateMeta, DiffMachine}, + diff::{CreateMeta, DiffInstruction, DiffMachine}, prelude::*, DomEdit, }; use dioxus_core as dioxus; use dioxus_html as dioxus_elements; +use futures_util::FutureExt; struct TestDom { bump: Bump, @@ -38,30 +39,41 @@ impl TestDom { lazy_nodes.into_vnode(NodeFactory::new(&self.bump)) } - fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Vec> { - let mut edits = Vec::new(); + fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { + // let mut edits = Vec::new(); let mut machine = DiffMachine::new_headless(&self.resources); - machine.diff_node(old, new); - edits + + machine + .instructions + .push(dioxus::diff::DiffInstruction::DiffNode { new, old }); + + machine.mutations } - fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> (CreateMeta, Vec>) + fn create<'a, F1>(&'a self, left: LazyNodes<'a, F1>) -> Mutations<'a> where F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, { let old = self.bump.alloc(self.render(left)); - let mut edits = Vec::new(); let mut machine = DiffMachine::new_headless(&self.resources); - let meta = machine.create_vnode(old); - (meta, edits) + + machine + .instructions + .push(dioxus::diff::DiffInstruction::Create { + node: old, + and: dioxus::diff::MountType::Append, + }); + work_sync(&mut machine); + + machine.mutations } fn lazy_diff<'a, F1, F2>( &'a self, left: LazyNodes<'a, F1>, right: LazyNodes<'a, F2>, - ) -> (Vec>, Vec>) + ) -> (Mutations<'a>, Mutations<'a>) where F1: FnOnce(NodeFactory<'a>) -> VNode<'a>, F2: FnOnce(NodeFactory<'a>) -> VNode<'a>, @@ -70,18 +82,37 @@ impl TestDom { let new = self.bump.alloc(self.render(right)); - let mut create_edits = Vec::new(); + // let mut create_edits = Vec::new(); let mut machine = DiffMachine::new_headless(&self.resources); - machine.create_vnode(old); + machine + .instructions + .push(dioxus::diff::DiffInstruction::Create { + and: dioxus::diff::MountType::Append, + node: old, + }); + work_sync(&mut machine); + let create_edits = machine.mutations; - let mut edits = Vec::new(); let mut machine = DiffMachine::new_headless(&self.resources); - machine.diff_node(old, new); + machine + .instructions + .push(DiffInstruction::DiffNode { old, new }); + work_sync(&mut machine); + let edits = machine.mutations; + (create_edits, edits) } } +fn work_sync(machine: &mut DiffMachine) { + let mut fut = machine.work().boxed_local(); + + while let None = (&mut fut).now_or_never() { + // + } +} + #[test] fn diffing_works() {} @@ -100,7 +131,7 @@ fn html_and_rsx_generate_the_same_output() { #[test] fn fragments_create_properly() { let dom = TestDom::new(); - let (meta, edits) = dom.create(rsx! { + let Mutations { edits, noderefs } = dom.create(rsx! { div { "Hello a" } div { "Hello b" } div { "Hello c" } @@ -109,7 +140,7 @@ fn fragments_create_properly() { assert!(&edits[3].is("CreateElement")); assert!(&edits[6].is("CreateElement")); - assert_eq!(meta.added_to_stack, 3); + assert_eq!(*edits.last().unwrap(), DomEdit::AppendChildren { many: 3 }); dbg!(edits); } @@ -152,7 +183,7 @@ fn empty_fragments_create_anchors_with_many_children() { let edits = dom.lazy_diff(left, right); dbg!(&edits); - let last_edit = edits.1.last().unwrap(); + let last_edit = edits.1.edits.last().unwrap(); assert!(last_edit.is("ReplaceWith")); } @@ -190,7 +221,7 @@ fn two_equal_fragments_are_equal() { let edits = dom.lazy_diff(left, right); dbg!(&edits); - assert!(edits.1.is_empty()); + assert!(edits.1.edits.is_empty()); } /// Should result the creation of more nodes appended after the old last node diff --git a/packages/core/tests/work_sync.rs b/packages/core/tests/work_sync.rs new file mode 100644 index 000000000..187a4443b --- /dev/null +++ b/packages/core/tests/work_sync.rs @@ -0,0 +1,35 @@ +//! Diffing is interruptible, but uses yield_now which is loop-pollable +//! +//! This means you can actually call it synchronously if you want. + +use anyhow::{Context, Result}; +use dioxus::{ + arena::SharedResources, + diff::{CreateMeta, DiffInstruction, DiffMachine}, + prelude::*, + scope::Scope, +}; +use dioxus_core as dioxus; +use dioxus_html as dioxus_elements; +use futures_util::FutureExt; + +#[test] +fn worksync() { + static App: FC<()> = |cx| { + cx.render(rsx! { + div {"hello"} + }) + }; + let mut dom = VirtualDom::new(App); + + let mut fut = dom.rebuild_async().boxed_local(); + + let mutations = loop { + let g = (&mut fut).now_or_never(); + if g.is_some() { + break g.unwrap().unwrap(); + } + }; + + dbg!(mutations); +}