diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 9760e758f..07f313020 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -103,6 +103,11 @@ pub struct DiffMachine<'bump> { pub seen_scopes: FxHashSet, } + +/// 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. pub enum DiffInstruction<'a> { DiffNode { old: &'a VNode<'a>, @@ -111,6 +116,7 @@ pub enum DiffInstruction<'a> { }, Append {}, + Replace { with: usize, }, @@ -189,76 +195,14 @@ impl<'bump> DiffMachine<'bump> { } DiffInstruction::Create { node, .. } => { + // defer to individual functions so the compiler produces better code + // large functions tend to be difficult for the compiler to work with match &node { - VNode::Text(text) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_text_node(text.text, real_id); - text.dom_id.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; - } - VNode::Suspended(suspended) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_placeholder(real_id); - suspended.node.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; - } - VNode::Anchor(anchor) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_placeholder(real_id); - anchor.dom_id.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; - } - - VNode::Element(el) => { - let VElement { - tag_name, - listeners, - attributes, - children, - namespace, - static_attrs: _, - static_children: _, - static_listeners: _, - dom_id, - key, - } = el; - - let real_id = self.vdom.reserve_node(); - self.edit_create_element(tag_name, *namespace, real_id); - dom_id.set(Some(real_id)); - - let cur_scope = self.current_scope().unwrap(); - - 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()); - - // if the node has an event listener, then it must be visited ? - }); - - for attr in *attributes { - self.edit_set_attribute(attr); - } - - // TODO: the append child edit - - *self.nodes_created_stack.last_mut().unwrap() += 1; - - // push every child onto the stack - self.nodes_created_stack.push(0); - for child in *children { - self.node_stack - .push(DiffInstruction::Create { node: child }) - } - } - - VNode::Fragment(frag) => { - for node in frag.children { - self.node_stack.push(DiffInstruction::Create { node }) - } - } - + VNode::Text(vtext) => self.create_text_node(vtext), + VNode::Suspended(suspended) => self.create_suspended_node(suspended), + VNode::Anchor(anchor) => self.create_anchor_node(anchor), + VNode::Element(element) => self.create_element_node(element), + VNode::Fragment(frag) => self.create_fragment_node(frag), VNode::Component(_) => { // } @@ -270,44 +214,142 @@ impl<'bump> DiffMachine<'bump> { Ok(()) } - fn create_text_node(&mut self) {} + 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); + vtext.dom_id.set(Some(real_id)); + *self.nodes_created_stack.last_mut().unwrap() += 1; + } - /// Create the new node, pushing instructions on our instruction stack to create any further children - /// - /// - pub fn create_iterative(&mut self, node: &'bump VNode<'bump>) { - match &node { - // singles - // update the parent - VNode::Text(text) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_text_node(text.text, real_id); - text.dom_id.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; - } - VNode::Suspended(suspended) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_placeholder(real_id); - suspended.node.set(Some(real_id)); - *self.nodes_created_stack.last_mut().unwrap() += 1; - } - VNode::Anchor(anchor) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_placeholder(real_id); - anchor.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); + suspended.node.set(Some(real_id)); + *self.nodes_created_stack.last_mut().unwrap() += 1; + } - VNode::Element(el) => { - // + fn create_anchor_node(&mut self, anchor: &'bump VAnchor) { + let real_id = self.vdom.reserve_node(); + self.edit_create_placeholder(real_id); + anchor.dom_id.set(Some(real_id)); + *self.nodes_created_stack.last_mut().unwrap() += 1; + } + + fn create_element_node(&mut self, element: &'bump VElement<'bump>) { + let VElement { + tag_name, + listeners, + attributes, + children, + namespace, + static_attrs: _, + static_children: _, + static_listeners: _, + dom_id, + key, + } = element; + + let real_id = self.vdom.reserve_node(); + self.edit_create_element(tag_name, *namespace, real_id); + dom_id.set(Some(real_id)); + + let cur_scope = self.current_scope().unwrap(); + + 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()); + + // if the node has an event listener, then it must be visited ? + }); + + for attr in *attributes { + self.edit_set_attribute(attr); + } + + // TODO: the append child edit + + *self.nodes_created_stack.last_mut().unwrap() += 1; + + // push every child onto the stack + self.nodes_created_stack.push(0); + for child in *children { + self.node_stack + .push(DiffInstruction::Create { node: child }) + } + } + + fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) { + for node in frag.children { + self.node_stack.push(DiffInstruction::Create { node }) + } + } + + fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) { + let caller = vcomponent.caller.clone(); + + let parent_idx = self.scope_stack.last().unwrap().clone(); + + // 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 height = parent_scope.height + 1; + Scope::new( + caller, + new_idx, + Some(parent_idx), + height, + ScopeChildren(vcomponent.children), + self.vdom.clone(), + ) + }); + + // Actually initialize the caller's slot with the right address + vcomponent.ass_scope.set(Some(new_idx)); + + if !vcomponent.can_memoize { + let cur_scope = self.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); + } + + // TODO: + // add noderefs to current noderef list Noderefs + // add effects to current effect list Effects + + let new_component = self.get_scope_mut(&new_idx).unwrap(); + + // Run the scope for one iteration to initialize it + match new_component.run_scope() { + Ok(_) => { + // all good, new nodes exist } - VNode::Fragment(frag) => { - // - } - VNode::Component(comp) => { - // + Err(err) => { + // failed to run. this is the first time the component ran, and it failed + // we manually set its head node to an empty fragment + panic!("failing components not yet implemented"); } } + + // 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); + + // Run the creation algorithm with this scope on the stack + let meta = self.create_vnode(nextnode); + + // pop the scope off the stack + self.scope_stack.pop(); + + if meta.added_to_stack == 0 { + panic!("Components should *always* generate nodes - even if they fail"); + } + + // Finally, insert this scope as a seen node. + self.seen_scopes.insert(new_idx); } pub fn diff_iterative(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) { @@ -734,184 +776,6 @@ impl<'bump> DiffMachine<'bump> { } } - // Emit instructions to create the given virtual node. - // - // The change list stack may have any shape upon entering this function: - // - // [...] - // - // When this function returns, the new node is on top of the change list stack: - // - // [... node] - pub fn create_vnode(&mut self, node: &'bump VNode<'bump>) -> CreateMeta { - match &node { - VNode::Text(text) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_text_node(text.text, real_id); - text.dom_id.set(Some(real_id)); - CreateMeta::new(text.is_static, 1) - } - - VNode::Anchor(anchor) => { - let real_id = self.vdom.reserve_node(); - self.edit_create_placeholder(real_id); - anchor.dom_id.set(Some(real_id)); - CreateMeta::new(false, 1) - } - - VNode::Element(el) => { - // we have the potential to completely eliminate working on this node in the future(!) - // - // This can only be done if all of the elements properties (attrs, children, listeners, etc) are static - // While creating these things, keep track if we can memoize this element. - // At the end, we'll set this flag on the element to skip it - let mut is_static: bool = true; - - let VElement { - tag_name, - listeners, - attributes, - children, - namespace, - static_attrs: _, - static_children: _, - static_listeners: _, - dom_id, - key, - } = el; - - let real_id = self.vdom.reserve_node(); - self.edit_create_element(tag_name, *namespace, real_id); - dom_id.set(Some(real_id)); - - let cur_scope = self.current_scope().unwrap(); - - 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()); - - // if the node has an event listener, then it must be visited ? - is_static = false; - }); - - for attr in *attributes { - is_static = is_static && attr.is_static; - self.edit_set_attribute(attr); - } - - // Fast path: if there is a single text child, it is faster to - // create-and-append the text node all at once via setting the - // parent's `textContent` in a single change list instruction than - // to emit three instructions to (1) create a text node, (2) set its - // text content, and finally (3) append the text node to this - // parent. - // - // Notice: this is a web-specific optimization and may be changed in the future - // - // TODO move over - // if children.len() == 1 { - // if let VNodeKind::Text(text) = &children[0] { - // self.set_text(text.text); - // return CreateMeta::new(is_static, 1); - // } - // } - - for child in *children { - let child_meta = self.create_vnode(child); - is_static = is_static && child_meta.is_static; - - // append whatever children were generated by this call - self.edit_append_children(child_meta.added_to_stack); - } - - CreateMeta::new(is_static, 1) - } - - VNode::Component(vcomponent) => { - let caller = vcomponent.caller.clone(); - - let parent_idx = self.scope_stack.last().unwrap().clone(); - - // 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 height = parent_scope.height + 1; - Scope::new( - caller, - new_idx, - Some(parent_idx), - height, - ScopeChildren(vcomponent.children), - self.vdom.clone(), - ) - }); - - // Actually initialize the caller's slot with the right address - vcomponent.ass_scope.set(Some(new_idx)); - - if !vcomponent.can_memoize { - let cur_scope = self.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); - } - - // TODO: - // add noderefs to current noderef list Noderefs - // add effects to current effect list Effects - - let new_component = self.get_scope_mut(&new_idx).unwrap(); - - // Run the scope for one iteration to initialize it - match new_component.run_scope() { - Ok(_) => { - // all good, new nodes exist - } - Err(err) => { - // failed to run. this is the first time the component ran, and it failed - // we manually set its head node to an empty fragment - panic!("failing components not yet implemented"); - } - } - - // 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); - - // Run the creation algorithm with this scope on the stack - let meta = self.create_vnode(nextnode); - - // pop the scope off the stack - self.scope_stack.pop(); - - if meta.added_to_stack == 0 { - panic!("Components should *always* generate nodes - even if they fail"); - } - - // Finally, insert this scope as a seen node. - self.seen_scopes.insert(new_idx); - - CreateMeta::new(vcomponent.is_static, meta.added_to_stack) - } - - // Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators). - // We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them. - // Fragments will just put all their nodes onto the stack after creation - VNode::Fragment(frag) => self.create_children(frag.children), - - VNode::Suspended(VSuspended { node: real_node }) => { - let id = self.vdom.reserve_node(); - self.edit_create_placeholder(id); - real_node.set(Some(id)); - CreateMeta::new(false, 1) - } - } - } - fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta { let mut is_static = true; let mut added_to_stack = 0;