From 16bfc6d2488f7ec0266ff5b174142232fb04849f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 23 Aug 2021 10:43:49 -0400 Subject: [PATCH] wip: compiles again --- packages/core/src/childiter.rs | 130 +++++++ packages/core/src/diff.rs | 614 ++++++++----------------------- packages/core/src/diff_stack.rs | 115 ++++++ packages/core/src/diffsupport.rs | 72 ++++ packages/core/src/editor.rs | 11 +- packages/core/src/lib.rs | 4 + packages/core/src/mutations.rs | 44 ++- packages/core/src/nodes.rs | 10 - packages/core/src/virtual_dom.rs | 14 +- packages/core/tests/diffing.rs | 28 +- 10 files changed, 534 insertions(+), 508 deletions(-) create mode 100644 packages/core/src/childiter.rs create mode 100644 packages/core/src/diff_stack.rs create mode 100644 packages/core/src/diffsupport.rs diff --git a/packages/core/src/childiter.rs b/packages/core/src/childiter.rs new file mode 100644 index 000000000..e3c520d1c --- /dev/null +++ b/packages/core/src/childiter.rs @@ -0,0 +1,130 @@ +use crate::innerlude::*; + +/// This iterator iterates through a list of virtual children and only returns real children (Elements, Text, Anchors). +/// +/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like +/// "InsertBefore". +pub struct RealChildIterator<'a> { + scopes: &'a SharedResources, + + // Heuristcally we should never bleed into 4 completely nested fragments/components + // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane + // TODO: use const generics instead of the 4 estimation + stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>, +} + +impl<'a> RealChildIterator<'a> { + pub fn new(starter: &'a VNode<'a>, scopes: &'a SharedResources) -> Self { + Self { + scopes, + 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(); + self.stack.push((0, node)); + } +} + +impl<'a> Iterator for RealChildIterator<'a> { + type Item = &'a VNode<'a>; + + fn next(&mut self) -> Option<&'a VNode<'a>> { + let mut should_pop = false; + let mut returned_node: Option<&'a VNode<'a>> = None; + let mut should_push = None; + + while returned_node.is_none() { + if let Some((count, node)) = self.stack.last_mut() { + match &node { + // We can only exit our looping when we get "real" nodes + // This includes fragments and components when they're empty (have a single root) + VNode::Element(_) | VNode::Text(_) => { + // We've recursed INTO an element/text + // We need to recurse *out* of it and move forward to the next + should_pop = true; + returned_node = Some(&*node); + } + + // If we get a fragment we push the next child + VNode::Fragment(frag) => { + let subcount = *count as usize; + + if frag.children.len() == 0 { + should_pop = true; + returned_node = Some(&*node); + } + + if subcount >= frag.children.len() { + should_pop = true; + } else { + should_push = Some(&frag.children[subcount]); + } + } + // // If we get a fragment we push the next child + // VNodeKind::Fragment(frag) => { + // let subcount = *count as usize; + + // if frag.children.len() == 0 { + // should_pop = true; + // returned_node = Some(&*node); + // } + + // if subcount >= frag.children.len() { + // should_pop = true; + // } else { + // should_push = Some(&frag.children[subcount]); + // } + // } + + // Immediately abort suspended nodes - can't do anything with them yet + VNode::Suspended(node) => { + // VNodeKind::Suspended => should_pop = true, + todo!() + } + + VNode::Anchor(a) => { + todo!() + } + + // For components, we load their root and push them onto the stack + VNode::Component(sc) => { + let scope = + unsafe { self.scopes.get_scope(sc.ass_scope.get().unwrap()) }.unwrap(); + // let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap(); + + // Simply swap the current node on the stack with the root of the component + *node = scope.frames.fin_head(); + } + } + } else { + // If there's no more items on the stack, we're done! + return None; + } + + if should_pop { + self.stack.pop(); + if let Some((id, _)) = self.stack.last_mut() { + *id += 1; + } + should_pop = false; + } + + if let Some(push) = should_push { + self.stack.push((0, push)); + should_push = None; + } + } + + returned_node + } +} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 87282013c..b9dffde21 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -110,63 +110,13 @@ pub struct DiffMachine<'bump> { pub mutations: Mutations<'bump>, - pub nodes_created_stack: SmallVec<[usize; 10]>, - - pub instructions: Vec>, - - pub scope_stack: SmallVec<[ScopeId; 5]>, + pub stack: DiffStack<'bump>, pub diffed: FxHashSet, pub seen_scopes: FxHashSet, } -/// The stack instructions we use to diff and create new nodes. -/// -/// 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 { - old: &'a VNode<'a>, - new: &'a VNode<'a>, - }, - - DiffChildren { - old: &'a [VNode<'a>], - new: &'a [VNode<'a>], - }, - - Create { - node: &'a VNode<'a>, - and: MountType<'a>, - }, - - Remove { - child: &'a VNode<'a>, - }, - - RemoveChildren { - children: &'a [VNode<'a>], - }, - - Mount { - and: MountType<'a>, - }, - - PopElement, - - PopScope, -} - -#[derive(Debug, Clone, Copy)] -pub enum MountType<'a> { - Absorb, - Append, - Replace { old: &'a VNode<'a> }, - InsertAfter { other_node: Option<&'a VNode<'a>> }, - InsertBefore { other_node: Option<&'a VNode<'a>> }, -} - impl<'bump> DiffMachine<'bump> { pub(crate) fn new( edits: Mutations<'bump>, @@ -174,10 +124,8 @@ impl<'bump> DiffMachine<'bump> { shared: &'bump SharedResources, ) -> Self { Self { - instructions: Vec::with_capacity(1000), - nodes_created_stack: smallvec![], + stack: DiffStack::new(cur_scope), mutations: edits, - scope_stack: smallvec![cur_scope], vdom: shared, diffed: FxHashSet::default(), seen_scopes: FxHashSet::default(), @@ -192,7 +140,10 @@ impl<'bump> DiffMachine<'bump> { // pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> { - let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?; + let component = self + .vdom + .get_scope_mut(id) + .ok_or_else(|| Error::NotMounted)?; let (old, new) = (component.frames.wip_head(), component.frames.fin_head()); self.diff_node(old, new); Ok(()) @@ -206,7 +157,7 @@ impl<'bump> DiffMachine<'bump> { pub async fn work(&mut self) -> Result<()> { // 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.pop() { + while let Some(instruction) = self.stack.pop() { log::debug!("Handling diff instruction: {:?}", instruction); // todo: call this less frequently, there is a bit of overhead involved @@ -214,7 +165,7 @@ impl<'bump> DiffMachine<'bump> { match instruction { DiffInstruction::PopScope => { - self.scope_stack.pop(); + self.stack.pop_scope(); } DiffInstruction::PopElement => { self.mutations.pop(); @@ -229,22 +180,18 @@ impl<'bump> DiffMachine<'bump> { } DiffInstruction::Create { node, and } => { - self.nodes_created_stack.push(0); - self.instructions.push(DiffInstruction::Mount { and }); self.create_node(node); } DiffInstruction::Remove { child } => { for child in RealChildIterator::new(child, self.vdom) { - self.mutations.push_root(child.direct_id()); - self.mutations.remove(); + self.mutations.remove(child.direct_id().as_u64()); } } DiffInstruction::RemoveChildren { children } => { for child in RealChildIterator::new_from_slice(children, self.vdom) { - self.mutations.push_root(child.direct_id()); - self.mutations.remove(); + self.mutations.remove(child.direct_id().as_u64()); } } @@ -258,12 +205,12 @@ impl<'bump> DiffMachine<'bump> { } fn mount(&mut self, and: MountType) { - let nodes_created = self.nodes_created_stack.pop().unwrap(); + let nodes_created = self.stack.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; + *self.stack.nodes_created_stack.last_mut().unwrap() += nodes_created; } MountType::Append => { self.mutations.edits.push(AppendChildren { @@ -303,21 +250,21 @@ impl<'bump> DiffMachine<'bump> { let real_id = self.vdom.reserve_node(); self.mutations.create_text_node(vtext.text, real_id); vtext.dom_id.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; + self.stack.add_child_count(1); } fn create_suspended_node(&mut self, suspended: &'bump VSuspended) { let real_id = self.vdom.reserve_node(); self.mutations.create_placeholder(real_id); suspended.node.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; + self.stack.add_child_count(1); } fn create_anchor_node(&mut self, anchor: &'bump VAnchor) { let real_id = self.vdom.reserve_node(); self.mutations.create_placeholder(real_id); anchor.dom_id.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; + self.stack.add_child_count(1); } fn create_element_node(&mut self, element: &'bump VElement<'bump>) { @@ -334,11 +281,11 @@ impl<'bump> DiffMachine<'bump> { let real_id = self.vdom.reserve_node(); self.mutations.create_element(tag_name, *namespace, real_id); - *self.nodes_created_stack.last_mut().unwrap() += 1; + self.stack.add_child_count(1); dom_id.set(Some(real_id)); - let cur_scope = self.current_scope().unwrap(); + let cur_scope = self.stack.current_scope().unwrap(); listeners.iter().for_each(|listener| { self.fix_listener(listener); @@ -352,22 +299,22 @@ impl<'bump> DiffMachine<'bump> { } if children.len() > 0 { - self.create_children_instructions(children, MountType::Append); + self.stack.create_children(children, MountType::Append); } } fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) { - self.create_children_instructions(frag.children, MountType::Absorb); + self.stack.create_children(frag.children, MountType::Absorb); } fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { let caller = vcomponent.caller.clone(); - let parent_idx = self.scope_stack.last().unwrap().clone(); + let parent_idx = self.stack.current_scope().unwrap(); // Insert a new scope into our component list let new_idx = self.vdom.insert_scope_with_key(|new_idx| { - let parent_scope = self.get_scope(&parent_idx).unwrap(); + let parent_scope = self.vdom.get_scope(parent_idx).unwrap(); let height = parent_scope.height + 1; Scope::new( caller, @@ -384,7 +331,7 @@ impl<'bump> DiffMachine<'bump> { vcomponent.ass_scope.set(Some(new_idx)); if !vcomponent.can_memoize { - let cur_scope = self.get_scope_mut(&parent_idx).unwrap(); + let cur_scope = self.vdom.get_scope_mut(parent_idx).unwrap(); let extended = vcomponent as *const VComponent; let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) }; cur_scope.borrowed_props.borrow_mut().push(extended); @@ -394,7 +341,7 @@ impl<'bump> DiffMachine<'bump> { // add noderefs to current noderef list Noderefs // add effects to current effect list Effects - let new_component = self.get_scope_mut(&new_idx).unwrap(); + let new_component = self.vdom.get_scope_mut(new_idx).unwrap(); // Run the scope for one iteration to initialize it match new_component.run_scope() { @@ -411,16 +358,7 @@ impl<'bump> DiffMachine<'bump> { // Take the node that was just generated from running the component let nextnode = new_component.frames.fin_head(); - // Push the new scope onto the stack - self.scope_stack.push(new_idx); - self.instructions.push(DiffInstruction::PopScope); - - // Run the creation algorithm with this scope on the stack - // ?? I think we treat components as framgnets?? - self.instructions.push(DiffInstruction::Create { - node: nextnode, - and: MountType::Absorb, - }); + self.stack.create_component(new_idx, nextnode); // Finally, insert this scope as a seen node. self.seen_scopes.insert(new_idx); @@ -445,7 +383,7 @@ impl<'bump> DiffMachine<'bump> { ( Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_), Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) | Suspended(_), - ) => self.replace_and_create_many_with_many(old_node, new_node), + ) => self.replace_and_create_one_with_one(old_node, new_node), } } @@ -517,7 +455,7 @@ impl<'bump> DiffMachine<'bump> { // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener) // // TODO: take a more efficient path than this - let cur_scope: ScopeId = self.scope_stack.last().unwrap().clone(); + let cur_scope = self.stack.current_scope().unwrap(); if old.listeners.len() == new.listeners.len() { for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) { if old_l.event != new_l.event { @@ -559,13 +497,13 @@ impl<'bump> DiffMachine<'bump> { // Make sure we're dealing with the same component (by function pointer) if old.user_fc == new.user_fc { // - self.scope_stack.push(scope_addr); + self.stack.scope_stack.push(scope_addr); // Make sure the new component vnode is referencing the right scope id new.ass_scope.set(Some(scope_addr)); // make sure the component's caller function is up to date - let scope = self.get_scope_mut(&scope_addr).unwrap(); + let scope = self.vdom.get_scope_mut(scope_addr).unwrap(); scope.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children)); @@ -583,7 +521,7 @@ impl<'bump> DiffMachine<'bump> { } } - self.scope_stack.pop(); + self.stack.scope_stack.pop(); self.seen_scopes.insert(scope_addr); } else { @@ -624,54 +562,6 @@ impl<'bump> DiffMachine<'bump> { // 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. - /// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered. - fn destroy_scopes(&mut self, old_scope: ScopeId) { - let mut nodes_to_delete = vec![old_scope]; - let mut scopes_to_explore = vec![old_scope]; - - // explore the scope tree breadth first - while let Some(scope_id) = scopes_to_explore.pop() { - // If we're planning on deleting this node, then we don't need to both rendering it - self.seen_scopes.insert(scope_id); - let scope = self.get_scope(&scope_id).unwrap(); - for child in scope.descendents.borrow().iter() { - // Add this node to be explored - scopes_to_explore.push(child.clone()); - - // Also add it for deletion - nodes_to_delete.push(child.clone()); - } - } - - // Delete all scopes that we found as part of this subtree - for node in nodes_to_delete { - log::debug!("Removing scope {:#?}", node); - let _scope = self.vdom.try_remove(node).unwrap(); - // do anything we need to do to delete the scope - // I think we need to run the destructors on the hooks - // TODO - } - } - // Diff the given set of old and new children. // // The parent must be on top of the change list stack when this function is @@ -697,7 +587,7 @@ impl<'bump> DiffMachine<'bump> { // Completely adding new nodes, removing any placeholder if it exists (IS_EMPTY, IS_NOT_EMPTY) => { - self.create_children_instructions(new, MountType::Append); + self.stack.create_children(new, MountType::Append); } // Completely removing old nodes and putting an anchor in its place @@ -705,7 +595,7 @@ impl<'bump> DiffMachine<'bump> { // remove all the old nodes (IS_NOT_EMPTY, IS_EMPTY) => { for node in old { - self.remove_vnode(node); + self.remove_nodes(Some(node)); } } @@ -721,15 +611,13 @@ impl<'bump> DiffMachine<'bump> { // Replace the anchor with whatever new nodes are coming down the pipe (VNode::Anchor(anchor), _) => { - self.create_children_instructions( - new, - MountType::Replace { old: first_old }, - ); + self.stack + .create_children(new, MountType::Replace { old: first_old }); } // Replace whatever nodes are sitting there with the anchor (_, VNode::Anchor(anchor)) => { - self.replace_and_create_many_with_many(old, [first_new]); + self.replace_and_create_many_with_one(old, first_new); } // Use the complex diff algorithm to diff the nodes @@ -884,7 +772,7 @@ impl<'bump> DiffMachine<'bump> { // And if that was all of the new children, then remove all of the remaining // old children and we're finished. if shared_prefix_count == new.len() { - self.remove_children(&old[shared_prefix_count..]); + self.remove_nodes(&old[shared_prefix_count..]); return KeyedPrefixResult::Finished; } @@ -898,7 +786,7 @@ impl<'bump> DiffMachine<'bump> { // [... parent] // // When this function returns, the change list stack is in the same state. - pub fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) { + fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) { for child in new { todo!(); // let meta = self.create_vnode(child); @@ -1069,7 +957,7 @@ impl<'bump> DiffMachine<'bump> { self.mutations.insert_before(1); } } else { - self.instructions.push(DiffInstruction::Create { + self.stack.push(DiffInstruction::Create { node: next_new, and: MountType::InsertBefore { other_node: Some(new_anchor), @@ -1165,13 +1053,13 @@ impl<'bump> DiffMachine<'bump> { Ordering::Greater => { // Generate instructions to diff the existing elements for (new_child, old_child) in new.iter().zip(old.iter()).rev() { - self.instructions.push(DiffInstruction::DiffNode { + self.stack.push(DiffInstruction::DiffNode { new: new_child, old: old_child, }); } - self.instructions.push(DiffInstruction::RemoveChildren { + self.stack.push(DiffInstruction::RemoveChildren { children: &old[new.len()..], }); } @@ -1183,14 +1071,14 @@ impl<'bump> DiffMachine<'bump> { Ordering::Less => { // Generate instructions to diff the existing elements for (new_child, old_child) in new.iter().zip(old.iter()).rev() { - self.instructions.push(DiffInstruction::DiffNode { + self.stack.push(DiffInstruction::DiffNode { new: new_child, old: old_child, }); } // Generate instructions to add in the new elements - self.create_children_instructions( + self.stack.create_children( &new[old.len()..], MountType::InsertAfter { other_node: old.last(), @@ -1201,7 +1089,7 @@ impl<'bump> DiffMachine<'bump> { // old.len == new.len -> no nodes added/removed, but perhaps changed Ordering::Equal => { for (new_child, old_child) in new.iter().zip(old.iter()).rev() { - self.instructions.push(DiffInstruction::DiffNode { + self.stack.push(DiffInstruction::DiffNode { new: new_child, old: old_child, }); @@ -1210,40 +1098,6 @@ 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 the current child and all of its following siblings. - // - // The change list stack must have this shape upon entry to this function: - // - // [... parent child] - // - // After the function returns, the child is no longer on the change list stack: - // - // [... parent] - fn remove_children(&mut self, old: &'bump [VNode<'bump>]) { - self.replace_and_create_many_with_many(old, None) - } - fn find_last_element(&mut self, vnode: &'bump VNode<'bump>) -> &'bump VNode<'bump> { let mut search_node = Some(vnode); @@ -1260,7 +1114,7 @@ impl<'bump> DiffMachine<'bump> { } VNode::Component(el) => { let scope_id = el.ass_scope.get().unwrap(); - let scope = self.get_scope(&scope_id).unwrap(); + let scope = self.vdom.get_scope(scope_id).unwrap(); search_node = Some(scope.root()); } } @@ -1283,7 +1137,7 @@ impl<'bump> DiffMachine<'bump> { } VNode::Component(el) => { let scope_id = el.ass_scope.get().unwrap(); - let scope = self.get_scope(&scope_id).unwrap(); + let scope = self.vdom.get_scope(scope_id).unwrap(); search_node = Some(scope.root()); } } @@ -1294,56 +1148,107 @@ impl<'bump> DiffMachine<'bump> { // self.replace_and_create_many_with_many(Some(node), None); // } + fn replace_and_create_one_with_one( + &mut self, + old: &'bump VNode<'bump>, + new: &'bump VNode<'bump>, + ) { + self.stack.create_node(new, MountType::Replace { old }); + } + 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>]) { - // + self.stack.create_children(new, MountType::Replace { old }); } - /// Remove all the old nodes and replace them with newly created new nodes. - /// - /// The new nodes *will* be created - don't create them yourself! - fn replace_and_create_many_with_many( + fn replace_and_create_many_with_one( &mut self, - old_node: &'bump VNode<'bump>, - new_node: &'bump VNode<'bump>, - // old_nodes: impl IntoIterator>, - // new_nodes: impl IntoIterator>, + old: &'bump [VNode<'bump>], + new: &'bump VNode<'bump>, ) { - let mut nodes_to_replace = Vec::new(); - let mut nodes_to_search = vec![old_node]; - let mut scopes_obliterated = Vec::new(); - while let Some(node) = nodes_to_search.pop() { - match &node { - // the ones that have a direct id return immediately - VNode::Text(el) => nodes_to_replace.push(el.dom_id.get().unwrap()), - VNode::Element(el) => nodes_to_replace.push(el.dom_id.get().unwrap()), - VNode::Anchor(el) => nodes_to_replace.push(el.dom_id.get().unwrap()), - VNode::Suspended(el) => nodes_to_replace.push(el.node.get().unwrap()), + if let Some(first_old) = old.get(0) { + self.remove_nodes(&old[1..]); + self.stack + .create_node(new, MountType::Replace { old: first_old }); + } else { + self.stack.create_node(new, MountType::Append {}); + } + } - // Fragments will either have a single anchor or a list of children - VNode::Fragment(frag) => { - for child in frag.children { - nodes_to_search.push(child); - } + /// schedules nodes for garbage collection and pushes "remove" to the mutation stack + /// remove can happen whenever + fn remove_nodes(&mut self, nodes: impl IntoIterator>) { + // or cache the vec on the diff machine + for node in nodes { + match node { + VNode::Text(t) => { + t.dom_id.get().map(|id| { + self.mutations.remove(id.as_u64()); + self.vdom.collect_garbage(id); + }); } - - // Components can be any of the nodes above - // However, we do need to track which components need to be removed - VNode::Component(el) => { - let scope_id = el.ass_scope.get().unwrap(); - let scope = self.get_scope(&scope_id).unwrap(); + VNode::Suspended(s) => { + s.node.get().map(|id| { + self.mutations.remove(id.as_u64()); + self.vdom.collect_garbage(id); + }); + } + VNode::Anchor(a) => { + a.dom_id.get().map(|id| { + self.mutations.remove(id.as_u64()); + self.vdom.collect_garbage(id); + }); + } + VNode::Element(e) => { + e.dom_id.get().map(|id| self.mutations.remove(id.as_u64())); + } + VNode::Fragment(f) => { + self.remove_nodes(f.children); + } + VNode::Component(c) => { + let scope_id = c.ass_scope.get().unwrap(); + let scope = self.vdom.get_scope(scope_id).unwrap(); let root = scope.root(); - nodes_to_search.push(root); - scopes_obliterated.push(scope_id); + self.remove_nodes(Some(root)); } } - // TODO: enable internal garabge collection - // self.create_garbage(node); } + // let mut nodes_to_replace = Vec::new(); + // let mut nodes_to_search = vec![old_node]; + // let mut scopes_obliterated = Vec::new(); + // while let Some(node) = nodes_to_search.pop() { + // match &node { + // // the ones that have a direct id return immediately + // VNode::Text(el) => nodes_to_replace.push(el.dom_id.get().unwrap()), + // VNode::Element(el) => nodes_to_replace.push(el.dom_id.get().unwrap()), + // VNode::Anchor(el) => nodes_to_replace.push(el.dom_id.get().unwrap()), + // VNode::Suspended(el) => nodes_to_replace.push(el.node.get().unwrap()), + + // // Fragments will either have a single anchor or a list of children + // VNode::Fragment(frag) => { + // for child in frag.children { + // nodes_to_search.push(child); + // } + // } + + // // Components can be any of the nodes above + // // However, we do need to track which components need to be removed + // VNode::Component(el) => { + // let scope_id = el.ass_scope.get().unwrap(); + // let scope = self.vdom.get_scope(scope_id).unwrap(); + // let root = scope.root(); + // nodes_to_search.push(root); + // scopes_obliterated.push(scope_id); + // } + // } + // // TODO: enable internal garabge collection + // // self.create_garbage(node); + // } + // let n = nodes_to_replace.len(); // for node in nodes_to_replace { // self.mutations.push_root(node); @@ -1365,13 +1270,37 @@ impl<'bump> DiffMachine<'bump> { // }); // obliterate! - for scope in scopes_obliterated { - self.destroy_scopes(scope); + // for scope in scopes_obliterated { + + // todo: mark as garbage + + // self.destroy_scopes(scope); + // } + } + + /// Remove all the old nodes and replace them with newly created new nodes. + /// + /// The new nodes *will* be created - don't create them yourself! + fn replace_and_create_many_with_many( + &mut self, + old: &'bump [VNode<'bump>], + new: &'bump [VNode<'bump>], + ) { + if let Some(first_old) = old.get(0) { + self.remove_nodes(&old[1..]); + self.stack + .create_children(new, MountType::Replace { old: first_old }) + } else { + self.stack.create_children(new, MountType::Append {}); } } fn create_garbage(&mut self, node: &'bump VNode<'bump>) { - match self.current_scope().and_then(|id| self.get_scope(&id)) { + match self + .stack + .current_scope() + .and_then(|id| self.vdom.get_scope(id)) + { Some(scope) => { let garbage: &'bump VNode<'static> = unsafe { std::mem::transmute(node) }; scope.pending_garbage.borrow_mut().push(garbage); @@ -1382,92 +1311,26 @@ impl<'bump> DiffMachine<'bump> { } } - fn immediately_dispose_garabage(&mut self, node: ElementId) { - self.vdom.collect_garbage(node) - } - fn replace_node_with_node( &mut self, - anchor: ElementId, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>, ) { - self.mutations.push_root(anchor); - todo!(); - // let meta = self.create_vnode(new_node); - // self.mutations.replace_with(1, meta.added_to_stack); - // self.create_garbage(old_node); - self.mutations.pop(); - } - - fn remove_vnode(&mut self, node: &'bump VNode<'bump>) { - match &node { - VNode::Text(el) => self.immediately_dispose_garabage(node.direct_id()), - VNode::Element(el) => { - self.immediately_dispose_garabage(node.direct_id()); - for child in el.children { - self.remove_vnode(&child); - } - } - VNode::Anchor(a) => { - // - } - VNode::Fragment(frag) => { - for child in frag.children { - self.remove_vnode(&child); - } - } - VNode::Component(el) => { - // - // self.destroy_scopes(old_scope) - } - VNode::Suspended(_) => todo!(), - } - } - - fn current_scope(&self) -> Option { - self.scope_stack.last().map(|f| f.clone()) + self.stack.instructions.push(DiffInstruction::Create { + and: MountType::Replace { old: old_node }, + node: new_node, + }); } fn fix_listener<'a>(&mut self, listener: &'a Listener<'a>) { - let scope_id = self.current_scope(); + let scope_id = self.stack.current_scope(); if let Some(scope_id) = scope_id { - let scope = self.get_scope(&scope_id).unwrap(); + let scope = self.vdom.get_scope(scope_id).unwrap(); let mut queue = scope.listeners.borrow_mut(); let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) }; queue.push(long_listener as *const _) } } - - pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'bump mut Scope> { - // ensure we haven't seen this scope before - // if we have, then we're trying to alias it, which is not allowed - debug_assert!(!self.seen_scopes.contains(id)); - - unsafe { self.vdom.get_scope_mut(*id) } - } - pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'bump Scope> { - // ensure we haven't seen this scope before - // if we have, then we're trying to alias it, which is not allowed - unsafe { self.vdom.get_scope(*id) } - } -} - -// When we create new nodes, we need to propagate some information back up the call chain. -// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding. -#[derive(Debug)] -pub struct CreateMeta { - pub is_static: bool, - pub added_to_stack: u32, -} - -impl CreateMeta { - fn new(is_static: bool, added_to_tack: u32) -> Self { - Self { - is_static, - added_to_stack: added_to_tack, - } - } } enum KeyedPrefixResult { @@ -1478,156 +1341,3 @@ enum KeyedPrefixResult { // the beginning of `new` and `old` we already processed. MoreWorkToDo(usize), } - -fn find_first_real_node<'a>( - nodes: impl IntoIterator>, - scopes: &'a SharedResources, -) -> Option<&'a VNode<'a>> { - for node in nodes { - let mut iter = RealChildIterator::new(node, scopes); - if let Some(node) = iter.next() { - return Some(node); - } - } - - None -} - -/// This iterator iterates through a list of virtual children and only returns real children (Elements, Text, Anchors). -/// -/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like -/// "InsertBefore". -pub struct RealChildIterator<'a> { - scopes: &'a SharedResources, - - // Heuristcally we should never bleed into 4 completely nested fragments/components - // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane - // TODO: use const generics instead of the 4 estimation - stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>, -} - -impl<'a> RealChildIterator<'a> { - pub fn new(starter: &'a VNode<'a>, scopes: &'a SharedResources) -> Self { - Self { - scopes, - 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(); - self.stack.push((0, node)); - } -} - -impl<'a> Iterator for RealChildIterator<'a> { - type Item = &'a VNode<'a>; - - fn next(&mut self) -> Option<&'a VNode<'a>> { - let mut should_pop = false; - let mut returned_node: Option<&'a VNode<'a>> = None; - let mut should_push = None; - - while returned_node.is_none() { - if let Some((count, node)) = self.stack.last_mut() { - match &node { - // We can only exit our looping when we get "real" nodes - // This includes fragments and components when they're empty (have a single root) - VNode::Element(_) | VNode::Text(_) => { - // We've recursed INTO an element/text - // We need to recurse *out* of it and move forward to the next - should_pop = true; - returned_node = Some(&*node); - } - - // If we get a fragment we push the next child - VNode::Fragment(frag) => { - let subcount = *count as usize; - - if frag.children.len() == 0 { - should_pop = true; - returned_node = Some(&*node); - } - - if subcount >= frag.children.len() { - should_pop = true; - } else { - should_push = Some(&frag.children[subcount]); - } - } - // // If we get a fragment we push the next child - // VNodeKind::Fragment(frag) => { - // let subcount = *count as usize; - - // if frag.children.len() == 0 { - // should_pop = true; - // returned_node = Some(&*node); - // } - - // if subcount >= frag.children.len() { - // should_pop = true; - // } else { - // should_push = Some(&frag.children[subcount]); - // } - // } - - // Immediately abort suspended nodes - can't do anything with them yet - VNode::Suspended(node) => { - // VNodeKind::Suspended => should_pop = true, - todo!() - } - - VNode::Anchor(a) => { - todo!() - } - - // For components, we load their root and push them onto the stack - VNode::Component(sc) => { - let scope = - unsafe { self.scopes.get_scope(sc.ass_scope.get().unwrap()) }.unwrap(); - // let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap(); - - // Simply swap the current node on the stack with the root of the component - *node = scope.frames.fin_head(); - } - } - } else { - // If there's no more items on the stack, we're done! - return None; - } - - if should_pop { - self.stack.pop(); - if let Some((id, _)) = self.stack.last_mut() { - *id += 1; - } - should_pop = false; - } - - if let Some(push) = should_push { - self.stack.push((0, push)); - should_push = None; - } - } - - returned_node - } -} - -fn compare_strs(a: &str, b: &str) -> bool { - // Check by pointer, optimizing for static strs - if !std::ptr::eq(a, b) { - // If the pointers are different then check by value - a == b - } else { - true - } -} diff --git a/packages/core/src/diff_stack.rs b/packages/core/src/diff_stack.rs new file mode 100644 index 000000000..296bdd1d0 --- /dev/null +++ b/packages/core/src/diff_stack.rs @@ -0,0 +1,115 @@ +use crate::innerlude::*; +use smallvec::{smallvec, SmallVec}; + +/// The stack instructions we use to diff and create new nodes. +#[derive(Debug)] +pub enum DiffInstruction<'a> { + DiffNode { + old: &'a VNode<'a>, + new: &'a VNode<'a>, + }, + + DiffChildren { + old: &'a [VNode<'a>], + new: &'a [VNode<'a>], + }, + + Create { + node: &'a VNode<'a>, + and: MountType<'a>, + }, + + Remove { + child: &'a VNode<'a>, + }, + + RemoveChildren { + children: &'a [VNode<'a>], + }, + + Mount { + and: MountType<'a>, + }, + + PopElement, + + PopScope, +} + +#[derive(Debug, Clone, Copy)] +pub enum MountType<'a> { + Absorb, + Append, + Replace { old: &'a VNode<'a> }, + InsertAfter { other_node: Option<&'a VNode<'a>> }, + InsertBefore { other_node: Option<&'a VNode<'a>> }, +} + +pub struct DiffStack<'bump> { + pub instructions: Vec>, + pub nodes_created_stack: SmallVec<[usize; 10]>, + pub scope_stack: SmallVec<[ScopeId; 5]>, +} + +impl<'bump> DiffStack<'bump> { + pub fn new(cur_scope: ScopeId) -> Self { + Self { + instructions: Vec::with_capacity(1000), + nodes_created_stack: smallvec![], + scope_stack: smallvec![cur_scope], + } + } + + pub fn pop(&mut self) -> Option> { + self.instructions.pop() + } + + pub fn pop_scope(&mut self) -> Option { + self.scope_stack.pop() + } + + pub fn push(&mut self, instruction: DiffInstruction<'bump>) { + self.instructions.push(instruction) + } + + pub fn create_children(&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, + }); + } + } + + pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) { + self.nodes_created_stack.push(0); + self.instructions.push(DiffInstruction::Mount { and }); + self.instructions + .push(DiffInstruction::Create { and, node }); + } + + pub fn add_child_count(&mut self, count: usize) { + *self.nodes_created_stack.last_mut().unwrap() += count; + } + + pub fn current_scope(&self) -> Option { + self.scope_stack.last().map(|f| f.clone()) + } + + pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) { + // Push the new scope onto the stack + self.scope_stack.push(idx); + + self.instructions.push(DiffInstruction::PopScope); + + // Run the creation algorithm with this scope on the stack + // ?? I think we treat components as framgnets?? + self.instructions.push(DiffInstruction::Create { + node, + and: MountType::Absorb, + }); + } +} diff --git a/packages/core/src/diffsupport.rs b/packages/core/src/diffsupport.rs new file mode 100644 index 000000000..8b01182e2 --- /dev/null +++ b/packages/core/src/diffsupport.rs @@ -0,0 +1,72 @@ +/// Destroy a scope and all of its descendents. +/// +/// Calling this will run the destuctors on all hooks in the tree. +/// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered. +fn destroy_scopes(&mut self, old_scope: ScopeId) { + let mut nodes_to_delete = vec![old_scope]; + let mut scopes_to_explore = vec![old_scope]; + + // explore the scope tree breadth first + while let Some(scope_id) = scopes_to_explore.pop() { + // If we're planning on deleting this node, then we don't need to both rendering it + self.seen_scopes.insert(scope_id); + let scope = self.get_scope(&scope_id).unwrap(); + for child in scope.descendents.borrow().iter() { + // Add this node to be explored + scopes_to_explore.push(child.clone()); + + // Also add it for deletion + nodes_to_delete.push(child.clone()); + } + } + + // Delete all scopes that we found as part of this subtree + for node in nodes_to_delete { + log::debug!("Removing scope {:#?}", node); + let _scope = self.vdom.try_remove(node).unwrap(); + // do anything we need to do to delete the scope + // I think we need to run the destructors on the hooks + // TODO + } +} + +pub(crate) fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'bump mut Scope> { + // ensure we haven't seen this scope before + // if we have, then we're trying to alias it, which is not allowed + debug_assert!(!self.seen_scopes.contains(id)); + + unsafe { self.vdom.get_scope_mut(*id) } +} +pub(crate) fn get_scope(&mut self, id: &ScopeId) -> Option<&'bump Scope> { + // ensure we haven't seen this scope before + // if we have, then we're trying to alias it, which is not allowed + unsafe { self.vdom.get_scope(*id) } +} + +fn compare_strs(a: &str, b: &str) -> bool { + // Check by pointer, optimizing for static strs + if !std::ptr::eq(a, b) { + // If the pointers are different then check by value + a == b + } else { + true + } +} + +fn find_first_real_node<'a>( + nodes: impl IntoIterator>, + scopes: &'a SharedResources, +) -> Option<&'a VNode<'a>> { + for node in nodes { + let mut iter = RealChildIterator::new(node, scopes); + if let Some(node) = iter.next() { + return Some(node); + } + } + + None +} + +fn remove_children(&mut self, old: &'bump [VNode<'bump>]) { + self.replace_and_create_many_with_many(old, None) +} diff --git a/packages/core/src/editor.rs b/packages/core/src/editor.rs index b6cc620d8..9aa887832 100644 --- a/packages/core/src/editor.rs +++ b/packages/core/src/editor.rs @@ -24,10 +24,6 @@ pub enum DomEdit<'bump> { many: u32, }, ReplaceWith { - // the first n elements - n: u32, - - // the last m elements m: u32, }, InsertAfter { @@ -36,7 +32,10 @@ pub enum DomEdit<'bump> { InsertBefore { n: u32, }, - Remove, + // remove roots directly + Remove { + root: u64, + }, RemoveAllChildren, CreateTextNode { text: &'bump str, @@ -83,7 +82,7 @@ impl DomEdit<'_> { DomEdit::PopRoot => id == "PopRoot", DomEdit::AppendChildren { .. } => id == "AppendChildren", DomEdit::ReplaceWith { .. } => id == "ReplaceWith", - DomEdit::Remove => id == "Remove", + DomEdit::Remove { .. } => id == "Remove", DomEdit::RemoveAllChildren => id == "RemoveAllChildren", DomEdit::CreateTextNode { .. } => id == "CreateTextNode", DomEdit::CreateElement { .. } => id == "CreateElement", diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 09d287d90..34f8d3ce9 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -30,9 +30,11 @@ pub mod prelude { pub(crate) mod innerlude { pub use crate::arena::*; pub use crate::bumpframe::*; + pub use crate::childiter::*; pub use crate::component::*; pub use crate::context::*; pub use crate::diff::*; + pub use crate::diff_stack::*; pub use crate::editor::*; pub use crate::error::*; pub use crate::events::*; @@ -60,9 +62,11 @@ pub mod exports { pub mod arena; pub mod bumpframe; +pub mod childiter; pub mod component; pub mod context; pub mod diff; +pub mod diff_stack; pub mod editor; pub mod error; pub mod events; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index ba43f9b4f..d45a9bba4 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,17 +1,18 @@ -use std::any::Any; +//! Instructions returned by the VirtualDOM on how to modify the Real DOM. +//! use crate::innerlude::*; +use std::any::Any; -/// 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>, +pub struct Mutations<'a> { + pub edits: Vec>, + pub noderefs: Vec>, } + use DomEdit::*; -impl<'bump> Mutations<'bump> { +impl<'a> Mutations<'a> { pub fn new() -> Self { let edits = Vec::new(); let noderefs = Vec::new(); @@ -25,29 +26,34 @@ impl<'bump> Mutations<'bump> { 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 replace_with(&mut self, m: u32) { + self.edits.push(ReplaceWith { 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); + pub(crate) fn remove(&mut self, id: u64) { + self.edits.push(Remove { root: id }); } + // Create - pub(crate) fn create_text_node(&mut self, text: &'bump str, id: ElementId) { + pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) { let id = id.as_u64(); self.edits.push(CreateTextNode { text, id }); } + pub(crate) fn create_element( &mut self, tag: &'static str, @@ -65,6 +71,7 @@ impl<'bump> Mutations<'bump> { 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 { @@ -84,11 +91,13 @@ impl<'bump> Mutations<'bump> { 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) { + pub(crate) fn set_text(&mut self, text: &'a str) { self.edits.push(SetText { text }); } - pub(crate) fn set_attribute(&mut self, attribute: &'bump Attribute) { + + pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute) { let Attribute { name, value, @@ -103,7 +112,7 @@ impl<'bump> Mutations<'bump> { ns: *namespace, }); } - pub(crate) fn set_attribute_ns(&mut self, attribute: &'bump Attribute, namespace: &'bump str) { + pub(crate) fn set_attribute_ns(&mut self, attribute: &'a Attribute, namespace: &'a str) { let Attribute { name, value, @@ -118,6 +127,7 @@ impl<'bump> Mutations<'bump> { ns: Some(namespace), }); } + pub(crate) fn remove_attribute(&mut self, attribute: &Attribute) { let name = attribute.name; self.edits.push(RemoveAttribute { name }); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c9b1c3db8..14d21a5a7 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -98,13 +98,8 @@ pub struct VElement<'a> { pub namespace: Option<&'static str>, pub dom_id: Cell>, - pub static_listeners: bool, pub listeners: &'a [Listener<'a>], - - pub static_attrs: bool, pub attributes: &'a [Attribute<'a>], - - pub static_children: bool, pub children: &'a [VNode<'a>], } @@ -303,11 +298,6 @@ impl<'a> NodeFactory<'a> { attributes, children, dom_id: empty_cell(), - - // todo: wire up more constization - static_listeners: false, - static_attrs: false, - static_children: false, })) } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index d6ab528a8..5a68e3008 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -166,13 +166,14 @@ impl VirtualDom { pub async fn rebuild_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) + let cur_component = self + .shared + .get_scope_mut(self.base_scope) .expect("The base scope should never be moved"); // // 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 { + diff_machine.stack.push(DiffInstruction::Create { node: cur_component.frames.fin_head(), and: MountType::Append, }); @@ -192,13 +193,14 @@ impl VirtualDom { 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) + let cur_component = self + .shared + .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 { + diff_machine.stack.push(DiffInstruction::DiffNode { old: cur_component.frames.wip_head(), new: cur_component.frames.fin_head(), }); diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 84b69120d..640aad610 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -9,7 +9,7 @@ use bumpalo::Bump; use anyhow::{Context, Result}; use dioxus::{ arena::SharedResources, - diff::{CreateMeta, DiffInstruction, DiffMachine}, + diff::{DiffInstruction, DiffMachine}, prelude::*, DomEdit, }; @@ -44,7 +44,7 @@ impl TestDom { let mut machine = DiffMachine::new_headless(&self.resources); machine - .instructions + .stack .push(dioxus::diff::DiffInstruction::DiffNode { new, old }); machine.mutations @@ -58,12 +58,10 @@ impl TestDom { let mut machine = DiffMachine::new_headless(&self.resources); - machine - .instructions - .push(dioxus::diff::DiffInstruction::Create { - node: old, - and: dioxus::diff::MountType::Append, - }); + machine.stack.push(dioxus::diff::DiffInstruction::Create { + node: old, + and: dioxus::diff::MountType::Append, + }); work_sync(&mut machine); machine.mutations @@ -85,19 +83,15 @@ impl TestDom { // let mut create_edits = Vec::new(); let mut machine = DiffMachine::new_headless(&self.resources); - machine - .instructions - .push(dioxus::diff::DiffInstruction::Create { - and: dioxus::diff::MountType::Append, - node: old, - }); + machine.stack.push(dioxus::diff::DiffInstruction::Create { + and: dioxus::diff::MountType::Append, + node: old, + }); work_sync(&mut machine); let create_edits = machine.mutations; let mut machine = DiffMachine::new_headless(&self.resources); - machine - .instructions - .push(DiffInstruction::DiffNode { old, new }); + machine.stack.push(DiffInstruction::DiffNode { old, new }); work_sync(&mut machine); let edits = machine.mutations;