diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 1c68f97cc..79505b018 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": false } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 07f313020..bfc8f31c8 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,9 +1,10 @@ -//! This module contains the stateful PriorityFiber and all methods to diff VNodes, their properties, and their children. +//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children. //! -//! The [`PriorityFiber`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set +//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set //! of mutations for the RealDom to apply. //! //! ## Notice: +//! //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support //! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancelation, NodeRefs, and additional //! batching operations. @@ -23,12 +24,13 @@ //! and brick the user's page. //! //! ### Fragment Support -//! +//! -------------------- //! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments -//! can be particularly challenging when they are empty, so the placeholder node lets us "reserve" a spot for the empty +//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty //! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is -//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. This is -//! slightly inefficient, but represents a such an uncommon use case that it is not worth optimizing. +//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding +//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to +//! the platform. //! //! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is //! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children. @@ -44,7 +46,7 @@ //! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to //! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time //! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as -//! a way of short-circuiting the most expensive checks. +//! a way of short-circuiting the most mem-cmp expensive checks. //! //! ## Bloom Filter and Heuristics //! ------------------------------ @@ -62,7 +64,7 @@ //! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes //! for the client. As new nodes are created, old nodes will be over-written. //! -//! Further Reading and Thoughts +//! ## Further Reading and Thoughts //! ---------------------------- //! There are more ways of increasing diff performance here that are currently not implemented. //! More info on how to improve this diffing algorithm: @@ -81,13 +83,16 @@ use DomEdit::*; /// Our DiffMachine is an iterative tree differ. /// -/// It uses techniques of a register-based Turing Machines to allow pausing and restarting of the diff algorithm. This +/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This /// was origially implemented using recursive techniques, but Rust lacks the abilty to call async functions recursively, -/// meaning we could not "pause" the diffing algorithm. +/// meaning we could not "pause" the original diffing algorithm. /// /// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically /// calls "yield_now" which allows the machine to pause and return control to the caller. The caller can then wait for -/// the next period of idle time, preventing our diff algorithm from blocking the maint thread. +/// the next period of idle time, preventing our diff algorithm from blocking the main thread. +/// +/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's +/// stack machines all the way down! pub struct DiffMachine<'bump> { vdom: &'bump SharedResources, @@ -112,7 +117,6 @@ pub enum DiffInstruction<'a> { DiffNode { old: &'a VNode<'a>, new: &'a VNode<'a>, - progress: usize, }, Append {}, @@ -143,22 +147,6 @@ impl<'bump> DiffMachine<'bump> { } } - /// Allows the creation of a diff machine without the concept of scopes or a virtualdom - /// this is mostly useful for testing - /// - /// This will PANIC if any component elements are passed in. - pub fn new_headless(shared: &'bump SharedResources) -> Self { - todo!() - // Self { - // node_stack: smallvec![], - // mutations: Mutations::new(), - // scope_stack: smallvec![ScopeId(0)], - // vdom: shared, - // diffed: FxHashSet::default(), - // seen_scopes: FxHashSet::default(), - // } - } - // pub async fn diff_scope(&mut self, id: ScopeId) -> Result<()> { let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?; @@ -173,15 +161,14 @@ impl<'bump> DiffMachine<'bump> { /// /// We do depth-first to maintain high cache locality (nodes were originally generated recursively) and because we /// only need a stack (not a queue) of lists - /// - /// - /// 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(mut make) = self.node_stack.pop() { match &mut make { - DiffInstruction::DiffNode { old, new, progress } => { - // + DiffInstruction::DiffNode { old, new, .. } => { + self.diff_node(old, new); } DiffInstruction::Append {} => { @@ -195,18 +182,7 @@ 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(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(_) => { - // - } - } + self.create_node(node); } } } @@ -214,6 +190,21 @@ impl<'bump> DiffMachine<'bump> { Ok(()) } + // ================================= + // Tools for creating new nodes + // ================================= + + fn create_node(&mut self, node: &'bump VNode<'bump>) { + match 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(component) => self.create_component_node(component), + } + } + 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); @@ -338,459 +329,222 @@ impl<'bump> DiffMachine<'bump> { // 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); + todo!(); + // // 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(); + // // 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"); - } + // 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); + // // 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>) { - match (&old_node, &new_node) { - // Handle the "sane" cases first. - // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment". - // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable. - (VNode::Text(old), VNode::Text(new)) => { - let root = old_node.direct_id(); + // ================================= + // Tools for diffing nodes + // ================================= - if old.text != new.text { - self.edit_push_root(root); - self.edit_set_text(new.text); - self.edit_pop(); - } - - new.dom_id.set(Some(root)); - } - - (VNode::Element(old), VNode::Element(new)) => { - let root = old_node.direct_id(); - - // If the element type is completely different, the element needs to be re-rendered completely - // This is an optimization React makes due to how users structure their code - // - // This case is rather rare (typically only in non-keyed lists) - if new.tag_name != old.tag_name || new.namespace != old.namespace { - self.replace_node_with_node(root, old_node, new_node); - return; - } - - new.dom_id.set(Some(root)); - - // Don't push the root if we don't have to - let mut has_comitted = false; - let mut please_commit = |edits: &mut Vec| { - if !has_comitted { - has_comitted = true; - edits.push(PushRoot { id: root.as_u64() }); - } - }; - - // Diff Attributes - // - // It's extraordinarily rare to have the number/order of attributes change - // In these cases, we just completely erase the old set and make a new set - // - // TODO: take a more efficient path than this - if old.attributes.len() == new.attributes.len() { - 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); - } - } - } 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); - } - for attribute in new.attributes { - self.edit_set_attribute(attribute) - } - } - - // Diff listeners - // - // It's extraordinarily rare to have the number/order of listeners change - // In the cases where the listeners change, we completely wipe the data attributes and add new ones - // - // 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(); - 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 { - please_commit(&mut self.mutations.edits); - self.edit_remove_event_listener(old_l.event); - self.edit_new_event_listener(new_l, cur_scope); - } - new_l.mounted_node.set(old_l.mounted_node.get()); - self.fix_listener(new_l); - } - } else { - please_commit(&mut self.mutations.edits); - for listener in old.listeners { - self.edit_remove_event_listener(listener.event); - } - for listener in new.listeners { - listener.mounted_node.set(Some(root)); - self.edit_new_event_listener(listener, cur_scope); - - // Make sure the listener gets attached to the scope list - self.fix_listener(listener); - } - } - - if has_comitted { - self.edit_pop(); - } - - self.diff_children(old.children, new.children); - } - - (VNode::Component(old), VNode::Component(new)) => { - let scope_addr = old.ass_scope.get().unwrap(); - - // 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); - - // 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(); - - scope - .update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children)); - - // React doesn't automatically memoize, but we do. - let compare = old.comparator.unwrap(); - - match compare(new) { - true => { - // the props are the same... - } - false => { - // the props are different... - scope.run_scope().unwrap(); - self.diff_node(scope.frames.wip_head(), scope.frames.fin_head()); - } - } - - self.scope_stack.pop(); - - self.seen_scopes.insert(scope_addr); - } else { - let mut old_iter = RealChildIterator::new(old_node, &self.vdom); - let first = old_iter - .next() - .expect("Components should generate a placeholder root"); - - // remove any leftovers - for to_remove in old_iter { - self.edit_push_root(to_remove.direct_id()); - self.edit_remove(); - } - - // seems like we could combine this into a single instruction.... - self.replace_node_with_node(first.direct_id(), old_node, new_node); - - // Wipe the old one and plant the new one - let old_scope = old.ass_scope.get().unwrap(); - self.destroy_scopes(old_scope); - } - } - - (VNode::Fragment(old), VNode::Fragment(new)) => { - // This is the case where options or direct vnodes might be used. - // In this case, it's faster to just skip ahead to their diff - if old.children.len() == 1 && new.children.len() == 1 { - self.diff_node(&old.children[0], &new.children[0]); - return; - } - - self.diff_children(old.children, new.children); - } - - (VNode::Anchor(old), VNode::Anchor(new)) => { - new.dom_id.set(old.dom_id.get()); - } - - // The strategy here is to pick the first possible node from the previous set and use that as our replace with root - // - // We also walk the "real node" list to make sure all latent roots are claened up - // This covers the case any time a fragment or component shows up with pretty much anything else - // - // This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases - // are pretty rare. IE replacing a list (component or fragment) with a single node. - ( - VNode::Component(_) - | VNode::Fragment(_) - | VNode::Text(_) - | VNode::Element(_) - | VNode::Anchor(_), - VNode::Component(_) - | VNode::Fragment(_) - | VNode::Text(_) - | VNode::Element(_) - | VNode::Anchor(_), - ) => { - self.replace_and_create_many_with_many([old_node], [new_node]); - } - - // TODO - (VNode::Suspended(old), new) => { - // - self.replace_and_create_many_with_many([old_node], [new_node]); - } - // a node that was once real is now suspended - (old, VNode::Suspended(_)) => { - // - self.replace_and_create_many_with_many([old_node], [new_node]); - } - } - } - - // Diff the `old` node with the `new` node. Emits instructions to modify a - // physical DOM node that reflects `old` into something that reflects `new`. - // - // the real stack should be what it is coming in and out of this function (ideally empty) - // - // each function call assumes the stack is fresh (empty). pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) { - match (&old_node, &new_node) { - // Handle the "sane" cases first. - // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment". - // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable. - (VNode::Text(old), VNode::Text(new)) => { - let root = old_node.direct_id(); + use VNode::*; + match (old_node, new_node) { + // Check the most common cases first + (Text(old), Text(new)) => self.diff_text_nodes(old, new), + (Element(old), Element(new)) => self.diff_element_nodes(old, new), + (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()), - if old.text != new.text { - self.edit_push_root(root); - self.edit_set_text(new.text); - self.edit_pop(); - } - - new.dom_id.set(Some(root)); - } - - (VNode::Element(old), VNode::Element(new)) => { - let root = old_node.direct_id(); - - // If the element type is completely different, the element needs to be re-rendered completely - // This is an optimization React makes due to how users structure their code - // - // This case is rather rare (typically only in non-keyed lists) - if new.tag_name != old.tag_name || new.namespace != old.namespace { - self.replace_node_with_node(root, old_node, new_node); - return; - } - - new.dom_id.set(Some(root)); - - // Don't push the root if we don't have to - let mut has_comitted = false; - let mut please_commit = |edits: &mut Vec| { - if !has_comitted { - has_comitted = true; - edits.push(PushRoot { id: root.as_u64() }); - } - }; - - // Diff Attributes - // - // It's extraordinarily rare to have the number/order of attributes change - // In these cases, we just completely erase the old set and make a new set - // - // TODO: take a more efficient path than this - if old.attributes.len() == new.attributes.len() { - 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); - } - } - } 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); - } - for attribute in new.attributes { - self.edit_set_attribute(attribute) - } - } - - // Diff listeners - // - // It's extraordinarily rare to have the number/order of listeners change - // In the cases where the listeners change, we completely wipe the data attributes and add new ones - // - // 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(); - 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 { - please_commit(&mut self.mutations.edits); - self.edit_remove_event_listener(old_l.event); - self.edit_new_event_listener(new_l, cur_scope); - } - new_l.mounted_node.set(old_l.mounted_node.get()); - self.fix_listener(new_l); - } - } else { - please_commit(&mut self.mutations.edits); - for listener in old.listeners { - self.edit_remove_event_listener(listener.event); - } - for listener in new.listeners { - listener.mounted_node.set(Some(root)); - self.edit_new_event_listener(listener, cur_scope); - - // Make sure the listener gets attached to the scope list - self.fix_listener(listener); - } - } - - if has_comitted { - self.edit_pop(); - } - - self.diff_children(old.children, new.children); - } - - (VNode::Component(old), VNode::Component(new)) => { - let scope_addr = old.ass_scope.get().unwrap(); - - // 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); - - // 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(); - - scope - .update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children)); - - // React doesn't automatically memoize, but we do. - let compare = old.comparator.unwrap(); - - match compare(new) { - true => { - // the props are the same... - } - false => { - // the props are different... - scope.run_scope().unwrap(); - self.diff_node(scope.frames.wip_head(), scope.frames.fin_head()); - } - } - - self.scope_stack.pop(); - - self.seen_scopes.insert(scope_addr); - } else { - let mut old_iter = RealChildIterator::new(old_node, &self.vdom); - let first = old_iter - .next() - .expect("Components should generate a placeholder root"); - - // remove any leftovers - for to_remove in old_iter { - self.edit_push_root(to_remove.direct_id()); - self.edit_remove(); - } - - // seems like we could combine this into a single instruction.... - self.replace_node_with_node(first.direct_id(), old_node, new_node); - - // Wipe the old one and plant the new one - let old_scope = old.ass_scope.get().unwrap(); - self.destroy_scopes(old_scope); - } - } - - (VNode::Fragment(old), VNode::Fragment(new)) => { - // This is the case where options or direct vnodes might be used. - // In this case, it's faster to just skip ahead to their diff - if old.children.len() == 1 && new.children.len() == 1 { - self.diff_node(&old.children[0], &new.children[0]); - return; - } - - self.diff_children(old.children, new.children); - } - - (VNode::Anchor(old), VNode::Anchor(new)) => { - new.dom_id.set(old.dom_id.get()); - } - - // The strategy here is to pick the first possible node from the previous set and use that as our replace with root - // - // We also walk the "real node" list to make sure all latent roots are claened up - // This covers the case any time a fragment or component shows up with pretty much anything else - // - // This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases - // are pretty rare. IE replacing a list (component or fragment) with a single node. ( - VNode::Component(_) - | VNode::Fragment(_) - | VNode::Text(_) - | VNode::Element(_) - | VNode::Anchor(_), - VNode::Component(_) - | VNode::Fragment(_) - | VNode::Text(_) - | VNode::Element(_) - | VNode::Anchor(_), + Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_), + Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_), ) => { self.replace_and_create_many_with_many([old_node], [new_node]); } - // TODO - (VNode::Suspended(old), new) => { - // + // 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, VNode::Suspended(_)) => { - // + (old, Suspended(_)) => { self.replace_and_create_many_with_many([old_node], [new_node]); } } } - fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta { - let mut is_static = true; - let mut added_to_stack = 0; + fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) { + let root = old.dom_id.get().unwrap(); - // add them backwards - for child in children.iter().rev() { - let child_meta = self.create_vnode(child); - is_static = is_static && child_meta.is_static; - added_to_stack += child_meta.added_to_stack; + if old.text != new.text { + self.edit_push_root(root); + self.edit_set_text(new.text); + self.edit_pop(); } - CreateMeta { - is_static, - added_to_stack, + new.dom_id.set(Some(root)); + } + + fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) { + let root = old.dom_id.get().unwrap(); + + // If the element type is completely different, the element needs to be re-rendered completely + // This is an optimization React makes due to how users structure their code + // + // This case is rather rare (typically only in non-keyed lists) + if new.tag_name != old.tag_name || new.namespace != old.namespace { + self.replace_node_with_node(root, old_node, new_node); + return; } + + new.dom_id.set(Some(root)); + + // Don't push the root if we don't have to + let mut has_comitted = false; + let mut please_commit = |edits: &mut Vec| { + if !has_comitted { + has_comitted = true; + edits.push(PushRoot { id: root.as_u64() }); + } + }; + + // Diff Attributes + // + // It's extraordinarily rare to have the number/order of attributes change + // In these cases, we just completely erase the old set and make a new set + // + // TODO: take a more efficient path than this + if old.attributes.len() == new.attributes.len() { + 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); + } + } + } 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); + } + for attribute in new.attributes { + self.edit_set_attribute(attribute) + } + } + + // Diff listeners + // + // It's extraordinarily rare to have the number/order of listeners change + // In the cases where the listeners change, we completely wipe the data attributes and add new ones + // + // 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(); + 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 { + please_commit(&mut self.mutations.edits); + self.edit_remove_event_listener(old_l.event); + self.edit_new_event_listener(new_l, cur_scope); + } + new_l.mounted_node.set(old_l.mounted_node.get()); + self.fix_listener(new_l); + } + } else { + please_commit(&mut self.mutations.edits); + for listener in old.listeners { + self.edit_remove_event_listener(listener.event); + } + for listener in new.listeners { + listener.mounted_node.set(Some(root)); + self.edit_new_event_listener(listener, cur_scope); + + // Make sure the listener gets attached to the scope list + self.fix_listener(listener); + } + } + + if has_comitted { + self.edit_pop(); + } + + self.diff_children(old.children, new.children); + } + + fn diff_component_nodes( + &mut self, + old: &'bump VComponent<'bump>, + new: &'bump VComponent<'bump>, + ) { + let scope_addr = old.ass_scope.get().unwrap(); + + // 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); + + // 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(); + + scope.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children)); + + // React doesn't automatically memoize, but we do. + let compare = old.comparator.unwrap(); + + match compare(new) { + true => { + // the props are the same... + } + false => { + // the props are different... + scope.run_scope().unwrap(); + self.diff_node(scope.frames.wip_head(), scope.frames.fin_head()); + } + } + + self.scope_stack.pop(); + + self.seen_scopes.insert(scope_addr); + } else { + todo!(); + + // let mut old_iter = RealChildIterator::new(old_node, &self.vdom); + // let first = old_iter + // .next() + // .expect("Components should generate a placeholder root"); + + // // remove any leftovers + // for to_remove in old_iter { + // self.edit_push_root(to_remove.direct_id()); + // self.edit_remove(); + // } + + // // seems like we could combine this into a single instruction.... + // self.replace_node_with_node(first.direct_id(), old_node, new_node); + + // // Wipe the old one and plant the new one + // let old_scope = old.ass_scope.get().unwrap(); + // self.destroy_scopes(old_scope); + } + } + + fn diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) { + // This is the case where options or direct vnodes might be used. + // In this case, it's faster to just skip ahead to their diff + if old.children.len() == 1 && new.children.len() == 1 { + self.diff_node(&old.children[0], &new.children[0]); + return; + } + + self.diff_children(old.children, new.children); } /// Destroy a scope and all of its descendents. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 78b4cbde3..195dafa8b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -159,17 +159,19 @@ impl VirtualDom { .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() { - let meta = diff_machine.create_vnode(cur_component.frames.fin_head()); - diff_machine.edit_append_children(meta.added_to_stack); - } 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." - ); - } + 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() { + // let meta = diff_machine.create_vnode(cur_component.frames.fin_head()); + // diff_machine.edit_append_children(meta.added_to_stack); + // } 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." + // ); + // } Ok(diff_machine.mutations) } diff --git a/packages/core/src/yield_now.rs b/packages/core/src/yield_now.rs index 2a985483e..8fef0848e 100644 --- a/packages/core/src/yield_now.rs +++ b/packages/core/src/yield_now.rs @@ -1,3 +1,7 @@ +//! Utility Code taken from async_std to immediately yield the current task to the executor. +//! +//! + use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; diff --git a/packages/core/tests/iterative.rs b/packages/core/tests/iterative.rs index 295abbe81..6ec318e61 100644 --- a/packages/core/tests/iterative.rs +++ b/packages/core/tests/iterative.rs @@ -45,3 +45,13 @@ async fn test_iterative_diff() { let mut machine = DiffMachine::new_headless(&shared); let a = machine.work().await.unwrap(); } + +#[async_std::test] +async fn websys_loop() { + ///loop { + /// let deadline = request_idle_callback().await; + /// let edits = dom.work(deadline); + /// request_animation_frame().await; + /// apply(edits); + ///} +}