|
@@ -1,186 +1,214 @@
|
|
-use crate::innerlude::{VNode, VText};
|
|
|
|
-
|
|
|
|
-// Diff the `old` node with the `new` node. Emits instructions to modify a
|
|
|
|
-// physical DOM node that reflects `old` into something that reflects `new`.
|
|
|
|
-//
|
|
|
|
-// Upon entry to this function, the physical DOM node must be on the top of the
|
|
|
|
-// change list stack:
|
|
|
|
-//
|
|
|
|
-// [... node]
|
|
|
|
-//
|
|
|
|
-// The change list stack is in the same state when this function exits.
|
|
|
|
-pub(crate) fn diff(
|
|
|
|
- // cached_set: &CachedSet,
|
|
|
|
- // change_list: &mut ChangeListBuilder,
|
|
|
|
- // registry: &mut EventsRegistry,
|
|
|
|
- old: &VNode,
|
|
|
|
- new: &VNode,
|
|
|
|
- // cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
-) {
|
|
|
|
- match (old, new) {
|
|
|
|
- // This case occurs when we generate two text nodes that are the sa
|
|
|
|
- (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => {
|
|
|
|
- //
|
|
|
|
- if old_text != new_text {}
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // compare elements
|
|
|
|
- // if different, schedule different types of update
|
|
|
|
- (VNode::Element(_), VNode::Element(_)) => {}
|
|
|
|
- (VNode::Element(_), VNode::Text(_)) => {}
|
|
|
|
- (VNode::Element(_), VNode::Component(_)) => {}
|
|
|
|
- (VNode::Text(_), VNode::Element(_)) => {}
|
|
|
|
- (VNode::Text(_), VNode::Component(_)) => {}
|
|
|
|
|
|
+/// Diff the `old` node with the `new` node. Emits instructions to modify a
|
|
|
|
+/// physical DOM node that reflects `old` into something that reflects `new`.
|
|
|
|
+///
|
|
|
|
+/// Upon entry to this function, the physical DOM node must be on the top of the
|
|
|
|
+/// change list stack:
|
|
|
|
+///
|
|
|
|
+/// [... node]
|
|
|
|
+///
|
|
|
|
+/// The change list stack is in the same state when this function exits.
|
|
|
|
+///
|
|
|
|
+/// ----
|
|
|
|
+///
|
|
|
|
+/// There are more ways of increasing diff performance here that are currently not implemented.
|
|
|
|
+/// Additionally, the caching mechanism has also been tweaked.
|
|
|
|
+///
|
|
|
|
+/// Instead of having "cached" nodes, each component is, by default, a cached node. This leads to increased
|
|
|
|
+/// memory overhead for large numbers of small components, but we can optimize this by tracking alloc size over time
|
|
|
|
+/// and shrinking bumps down if possible.
|
|
|
|
+///
|
|
|
|
+/// Additionally, clean up of these components is not done at diff time (though it should), but rather, the diffing
|
|
|
|
+/// proprogates removal lifecycle events for affected components into the event queue. It's not imperative that these
|
|
|
|
+/// are ran immediately, but it should be noted that cleanup of components might be able to emit changes.
|
|
|
|
+///
|
|
|
|
+/// This diffing only ever occurs on a component-by-component basis (not entire trees at once).
|
|
|
|
+///
|
|
|
|
+/// Currently, the listener situation is a bit broken.
|
|
|
|
+/// We aren't removing listeners (choosing to leak them instead) :(
|
|
|
|
+/// Eventually, we'll set things up so add/remove listener is an instruction again
|
|
|
|
+///
|
|
|
|
+/// A major assumption of this diff algorithm when combined with the ChangeList is that the Changelist will be
|
|
|
|
+/// fresh and the event queue is clean. This lets us continue to batch edits together under the same ChangeList
|
|
|
|
+///
|
|
|
|
+/// More info on how to improve this diffing algorithm:
|
|
|
|
+/// - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
|
|
|
+use fxhash::{FxHashMap, FxHashSet};
|
|
|
|
+use generational_arena::Index;
|
|
|
|
+
|
|
|
|
+use crate::{
|
|
|
|
+ changelist::EditList,
|
|
|
|
+ innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
|
|
|
|
+ virtual_dom::LifecycleEvent,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+use std::cmp::Ordering;
|
|
|
|
+
|
|
|
|
+/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
|
|
|
|
+/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
|
|
|
|
+///
|
|
|
|
+/// By re-entering via NodeDiff, we can connect disparate edits together into a single EditList. This batching of edits
|
|
|
|
+/// leads to very fast re-renders (all done in a single animation frame).
|
|
|
|
+///
|
|
|
|
+/// It also means diffing two trees is only ever complex as diffing a single smaller tree, and then re-entering at a
|
|
|
|
+/// different cursor position.
|
|
|
|
+///
|
|
|
|
+/// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
|
|
|
|
+/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
|
|
|
|
+/// subscriptions and props changes.
|
|
|
|
+struct DiffingMachine<'a> {
|
|
|
|
+ change_list: &'a mut EditList<'a>,
|
|
|
|
+ queue: FxHashMap<Index, NeedToDiff>,
|
|
|
|
+}
|
|
|
|
|
|
- (VNode::Component(_), VNode::Element(_)) => {}
|
|
|
|
- (VNode::Component(_), VNode::Text(_)) => {}
|
|
|
|
|
|
+enum NeedToDiff {
|
|
|
|
+ PropsChanged,
|
|
|
|
+ Subscription,
|
|
|
|
+}
|
|
|
|
|
|
- // No immediate change to dom. If props changed though, queue a "props changed" update
|
|
|
|
- // However, mark these for a
|
|
|
|
- (VNode::Component(_), VNode::Component(_)) => {}
|
|
|
|
- //
|
|
|
|
- (VNode::Suspended, _) | (_, VNode::Suspended) => {
|
|
|
|
- todo!("Suspended components not currently available")
|
|
|
|
- } // (VNode::Element(_), VNode::Suspended) => {}
|
|
|
|
- // (VNode::Text(_), VNode::Suspended) => {}
|
|
|
|
- // (VNode::Component(_), VNode::Suspended) => {}
|
|
|
|
- // (VNode::Suspended, VNode::Element(_)) => {}
|
|
|
|
- // (VNode::Suspended, VNode::Text(_)) => {}
|
|
|
|
- // (VNode::Suspended, VNode::Suspended) => {}
|
|
|
|
- // (VNode::Suspended, VNode::Component(_)) => {}
|
|
|
|
- }
|
|
|
|
- todo!()
|
|
|
|
- // match (&new.kind, &old.kind) {
|
|
|
|
- // (
|
|
|
|
- // &NodeKind::Text(TextNode { text: new_text }),
|
|
|
|
- // &NodeKind::Text(TextNode { text: old_text }),
|
|
|
|
- // ) => {
|
|
|
|
- // if new_text != old_text {
|
|
|
|
- // change_list.commit_traversal();
|
|
|
|
- // change_list.set_text(new_text);
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
|
|
+impl<'a> DiffingMachine<'a> {
|
|
|
|
+ fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
|
|
|
|
+ /*
|
|
|
|
+ For each valid case, we "commit traversal", meaning we save this current position in the tree.
|
|
|
|
+ Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
|
|
|
|
+ When re-entering, we reuse the EditList in DiffState
|
|
|
|
+ */
|
|
|
|
+ match (old, new) {
|
|
|
|
+ // This case occurs when two text nodes are generation
|
|
|
|
+ (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => {
|
|
|
|
+ if old_text != new_text {
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.change_list.set_text(new_text);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- // (&NodeKind::Text(_), &NodeKind::Element(_)) => {
|
|
|
|
- // change_list.commit_traversal();
|
|
|
|
- // create(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
- // registry.remove_subtree(&old);
|
|
|
|
- // change_list.replace_with();
|
|
|
|
- // }
|
|
|
|
|
|
+ // Definitely different, need to commit update
|
|
|
|
+ (VNode::Text(_), VNode::Element(_)) => {
|
|
|
|
+ // TODO: Hook up the events properly
|
|
|
|
+ // todo!("Hook up events registry");
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ // diff_support::create(cached_set, self.change_list, registry, new, cached_roots);
|
|
|
|
+ self.create(new);
|
|
|
|
+ // registry.remove_subtree(&old);
|
|
|
|
+ self.change_list.replace_with();
|
|
|
|
+ }
|
|
|
|
|
|
- // (&NodeKind::Element(_), &NodeKind::Text(_)) => {
|
|
|
|
- // change_list.commit_traversal();
|
|
|
|
- // create(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
- // // Note: text nodes cannot have event listeners, so we don't need to
|
|
|
|
- // // remove the old node's listeners from our registry her.
|
|
|
|
- // change_list.replace_with();
|
|
|
|
- // }
|
|
|
|
|
|
+ // Definitely different, need to commit update
|
|
|
|
+ (VNode::Element(_), VNode::Text(_)) => {
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.create(new);
|
|
|
|
|
|
- // (
|
|
|
|
- // &NodeKind::Element(ElementNode {
|
|
|
|
- // key: _,
|
|
|
|
- // tag_name: new_tag_name,
|
|
|
|
- // listeners: new_listeners,
|
|
|
|
- // attributes: new_attributes,
|
|
|
|
- // children: new_children,
|
|
|
|
- // namespace: new_namespace,
|
|
|
|
- // }),
|
|
|
|
- // &NodeKind::Element(ElementNode {
|
|
|
|
- // key: _,
|
|
|
|
- // tag_name: old_tag_name,
|
|
|
|
- // listeners: old_listeners,
|
|
|
|
- // attributes: old_attributes,
|
|
|
|
- // children: old_children,
|
|
|
|
- // namespace: old_namespace,
|
|
|
|
- // }),
|
|
|
|
- // ) => {
|
|
|
|
- // if new_tag_name != old_tag_name || new_namespace != old_namespace {
|
|
|
|
- // change_list.commit_traversal();
|
|
|
|
- // create(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
- // registry.remove_subtree(&old);
|
|
|
|
- // change_list.replace_with();
|
|
|
|
- // return;
|
|
|
|
- // }
|
|
|
|
- // diff_listeners(change_list, registry, old_listeners, new_listeners);
|
|
|
|
- // diff_attributes(
|
|
|
|
- // change_list,
|
|
|
|
- // old_attributes,
|
|
|
|
- // new_attributes,
|
|
|
|
- // new_namespace.is_some(),
|
|
|
|
- // );
|
|
|
|
- // diff_children(
|
|
|
|
- // cached_set,
|
|
|
|
- // change_list,
|
|
|
|
- // registry,
|
|
|
|
- // old_children,
|
|
|
|
- // new_children,
|
|
|
|
- // cached_roots,
|
|
|
|
- // );
|
|
|
|
- // }
|
|
|
|
|
|
+ // create(cached_set, self.change_list, registry, new, cached_roots);
|
|
|
|
+ // Note: text nodes cannot have event listeners, so we don't need to
|
|
|
|
+ // remove the old node's listeners from our registry her.
|
|
|
|
+ self.change_list.replace_with();
|
|
|
|
+ }
|
|
|
|
+ // compare elements
|
|
|
|
+ // if different, schedule different types of update
|
|
|
|
+ (VNode::Element(eold), VNode::Element(enew)) => {
|
|
|
|
+ // If the element type is completely different, the element needs to be re-rendered completely
|
|
|
|
+ if enew.tag_name != eold.tag_name || enew.namespace != eold.namespace {
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ // create(cached_set, self.change_list, registry, new, cached_roots);
|
|
|
|
+ // registry.remove_subtree(&old);
|
|
|
|
+ self.change_list.replace_with();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- // // Both the new and old nodes are cached.
|
|
|
|
- // (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
|
|
|
|
- // cached_roots.insert(new.id);
|
|
|
|
|
|
+ self.diff_listeners(eold.listeners, enew.listeners);
|
|
|
|
|
|
- // if new.id == old.id {
|
|
|
|
- // // This is the same cached node, so nothing has changed!
|
|
|
|
- // return;
|
|
|
|
- // }
|
|
|
|
|
|
+ self.diff_attr(eold.attributes, enew.attributes, enew.namespace.is_some());
|
|
|
|
|
|
- // let (new, new_template) = cached_set.get(new.id);
|
|
|
|
- // let (old, old_template) = cached_set.get(old.id);
|
|
|
|
- // if new_template == old_template {
|
|
|
|
- // // If they are both using the same template, then just diff the
|
|
|
|
- // // subtrees.
|
|
|
|
- // diff(cached_set, change_list, registry, old, new, cached_roots);
|
|
|
|
- // } else {
|
|
|
|
- // // Otherwise, they are probably different enough that
|
|
|
|
- // // re-constructing the subtree from scratch should be faster.
|
|
|
|
- // // This doubly holds true if we have a new template.
|
|
|
|
- // change_list.commit_traversal();
|
|
|
|
- // create_and_replace(
|
|
|
|
- // cached_set,
|
|
|
|
- // change_list,
|
|
|
|
- // registry,
|
|
|
|
- // new_template,
|
|
|
|
- // old,
|
|
|
|
- // new,
|
|
|
|
- // cached_roots,
|
|
|
|
- // );
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
|
|
+ self.diff_children(eold.children, enew.children);
|
|
|
|
+ }
|
|
|
|
+ // No immediate change to dom. If props changed though, queue a "props changed" update
|
|
|
|
+ // However, mark these for a
|
|
|
|
+ (VNode::Component(_), VNode::Component(_)) => {
|
|
|
|
+ todo!("Usage of component VNode not currently supported");
|
|
|
|
+ // // Both the new and old nodes are cached.
|
|
|
|
+ // (&NodeKind::Cached(ref new), &NodeKind::Cached(ref old)) => {
|
|
|
|
+ // cached_roots.insert(new.id);
|
|
|
|
+ // if new.id == old.id {
|
|
|
|
+ // // This is the same cached node, so nothing has changed!
|
|
|
|
+ // return;
|
|
|
|
+ // }
|
|
|
|
+ // let (new, new_template) = cached_set.get(new.id);
|
|
|
|
+ // let (old, old_template) = cached_set.get(old.id);
|
|
|
|
+ // if new_template == old_template {
|
|
|
|
+ // // If they are both using the same template, then just diff the
|
|
|
|
+ // // subtrees.
|
|
|
|
+ // diff(cached_set, change_list, registry, old, new, cached_roots);
|
|
|
|
+ // } else {
|
|
|
|
+ // // Otherwise, they are probably different enough that
|
|
|
|
+ // // re-constructing the subtree from scratch should be faster.
|
|
|
|
+ // // This doubly holds true if we have a new template.
|
|
|
|
+ // change_list.commit_traversal();
|
|
|
|
+ // create_and_replace(
|
|
|
|
+ // cached_set,
|
|
|
|
+ // change_list,
|
|
|
|
+ // registry,
|
|
|
|
+ // new_template,
|
|
|
|
+ // old,
|
|
|
|
+ // new,
|
|
|
|
+ // cached_roots,
|
|
|
|
+ // );
|
|
|
|
+ // }
|
|
|
|
+ // }
|
|
|
|
+ // queue a lifecycle event.
|
|
|
|
+ // no change
|
|
|
|
+ }
|
|
|
|
|
|
- // // New cached node when the old node was not cached. In this scenario,
|
|
|
|
- // // we assume that they are pretty different, and it isn't worth diffing
|
|
|
|
- // // the subtrees, so we just create the new cached node afresh.
|
|
|
|
- // (&NodeKind::Cached(ref c), _) => {
|
|
|
|
- // change_list.commit_traversal();
|
|
|
|
- // cached_roots.insert(c.id);
|
|
|
|
- // let (new, new_template) = cached_set.get(c.id);
|
|
|
|
- // create_and_replace(
|
|
|
|
- // cached_set,
|
|
|
|
- // change_list,
|
|
|
|
- // registry,
|
|
|
|
- // new_template,
|
|
|
|
- // old,
|
|
|
|
- // new,
|
|
|
|
- // cached_roots,
|
|
|
|
- // );
|
|
|
|
- // }
|
|
|
|
|
|
+ // A component has been birthed!
|
|
|
|
+ // Queue its arrival
|
|
|
|
+ (_, VNode::Component(_)) => {
|
|
|
|
+ todo!("Usage of component VNode not currently supported");
|
|
|
|
+ // // Old cached node and new non-cached node. Again, assume that they are
|
|
|
|
+ // // probably pretty different and create the new non-cached node afresh.
|
|
|
|
+ // (_, &NodeKind::Cached(_)) => {
|
|
|
|
+ // change_list.commit_traversal();
|
|
|
|
+ // create(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
+ // registry.remove_subtree(&old);
|
|
|
|
+ // change_list.replace_with();
|
|
|
|
+ // }
|
|
|
|
+ // }
|
|
|
|
+ }
|
|
|
|
|
|
- // // Old cached node and new non-cached node. Again, assume that they are
|
|
|
|
- // // probably pretty different and create the new non-cached node afresh.
|
|
|
|
- // (_, &NodeKind::Cached(_)) => {
|
|
|
|
- // change_list.commit_traversal();
|
|
|
|
- // create(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
- // registry.remove_subtree(&old);
|
|
|
|
- // change_list.replace_with();
|
|
|
|
- // }
|
|
|
|
- // }
|
|
|
|
-}
|
|
|
|
|
|
+ // A component was removed :(
|
|
|
|
+ // Queue its removal
|
|
|
|
+ (VNode::Component(_), _) => {
|
|
|
|
+ // // New cached node when the old node was not cached. In this scenario,
|
|
|
|
+ // // we assume that they are pretty different, and it isn't worth diffing
|
|
|
|
+ // // the subtrees, so we just create the new cached node afresh.
|
|
|
|
+ // (&NodeKind::Cached(ref c), _) => {
|
|
|
|
+ // change_list.commit_traversal();
|
|
|
|
+ // cached_roots.insert(c.id);
|
|
|
|
+ // let (new, new_template) = cached_set.get(c.id);
|
|
|
|
+ // create_and_replace(
|
|
|
|
+ // cached_set,
|
|
|
|
+ // change_list,
|
|
|
|
+ // registry,
|
|
|
|
+ // new_template,
|
|
|
|
+ // old,
|
|
|
|
+ // new,
|
|
|
|
+ // cached_roots,
|
|
|
|
+ // );
|
|
|
|
+ // }
|
|
|
|
+ todo!("Usage of component VNode not currently supported");
|
|
|
|
+ }
|
|
|
|
|
|
-#[cfg(predicate)]
|
|
|
|
-mod todo_diff {
|
|
|
|
|
|
+ // A suspended component appeared!
|
|
|
|
+ // Don't do much, just wait
|
|
|
|
+ (VNode::Suspended, _) | (_, VNode::Suspended) => {
|
|
|
|
+ // (VNode::Element(_), VNode::Suspended) => {}
|
|
|
|
+ // (VNode::Text(_), VNode::Suspended) => {}
|
|
|
|
+ // (VNode::Component(_), VNode::Suspended) => {}
|
|
|
|
+ // (VNode::Suspended, VNode::Element(_)) => {}
|
|
|
|
+ // (VNode::Suspended, VNode::Text(_)) => {}
|
|
|
|
+ // (VNode::Suspended, VNode::Suspended) => {}
|
|
|
|
+ // (VNode::Suspended, VNode::Component(_)) => {}
|
|
|
|
+ todo!("Suspended components not currently available")
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
// Diff event listeners between `old` and `new`.
|
|
// Diff event listeners between `old` and `new`.
|
|
//
|
|
//
|
|
@@ -189,14 +217,9 @@ mod todo_diff {
|
|
// [... node]
|
|
// [... node]
|
|
//
|
|
//
|
|
// The change list stack is left unchanged.
|
|
// The change list stack is left unchanged.
|
|
- fn diff_listeners(
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Listener],
|
|
|
|
- new: &[Listener],
|
|
|
|
- ) {
|
|
|
|
|
|
+ fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) {
|
|
if !old.is_empty() || !new.is_empty() {
|
|
if !old.is_empty() || !new.is_empty() {
|
|
- change_list.commit_traversal();
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
}
|
|
}
|
|
|
|
|
|
'outer1: for new_l in new {
|
|
'outer1: for new_l in new {
|
|
@@ -204,28 +227,28 @@ mod todo_diff {
|
|
// Safety relies on removing `new_l` from the registry manually at
|
|
// Safety relies on removing `new_l` from the registry manually at
|
|
// the end of its lifetime. This happens below in the `'outer2`
|
|
// the end of its lifetime. This happens below in the `'outer2`
|
|
// loop, and elsewhere in diffing when removing old dom trees.
|
|
// loop, and elsewhere in diffing when removing old dom trees.
|
|
- registry.add(new_l);
|
|
|
|
|
|
+ // registry.add(new_l);
|
|
}
|
|
}
|
|
|
|
|
|
for old_l in old {
|
|
for old_l in old {
|
|
if new_l.event == old_l.event {
|
|
if new_l.event == old_l.event {
|
|
- change_list.update_event_listener(new_l);
|
|
|
|
|
|
+ self.change_list.update_event_listener(new_l);
|
|
continue 'outer1;
|
|
continue 'outer1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- change_list.new_event_listener(new_l);
|
|
|
|
|
|
+ self.change_list.new_event_listener(new_l);
|
|
}
|
|
}
|
|
|
|
|
|
'outer2: for old_l in old {
|
|
'outer2: for old_l in old {
|
|
- registry.remove(old_l);
|
|
|
|
|
|
+ // registry.remove(old_l);
|
|
|
|
|
|
for new_l in new {
|
|
for new_l in new {
|
|
if new_l.event == old_l.event {
|
|
if new_l.event == old_l.event {
|
|
continue 'outer2;
|
|
continue 'outer2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- change_list.remove_event_listener(old_l.event);
|
|
|
|
|
|
+ self.change_list.remove_event_listener(old_l.event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -236,31 +259,37 @@ mod todo_diff {
|
|
// [... node]
|
|
// [... node]
|
|
//
|
|
//
|
|
// The change list stack is left unchanged.
|
|
// The change list stack is left unchanged.
|
|
- fn diff_attributes(
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- old: &[Attribute],
|
|
|
|
- new: &[Attribute],
|
|
|
|
|
|
+ fn diff_attr(
|
|
|
|
+ &mut self,
|
|
|
|
+ old: &'a [Attribute<'a>],
|
|
|
|
+ new: &'a [Attribute<'a>],
|
|
is_namespaced: bool,
|
|
is_namespaced: bool,
|
|
) {
|
|
) {
|
|
// Do O(n^2) passes to add/update and remove attributes, since
|
|
// Do O(n^2) passes to add/update and remove attributes, since
|
|
// there are almost always very few attributes.
|
|
// there are almost always very few attributes.
|
|
'outer: for new_attr in new {
|
|
'outer: for new_attr in new {
|
|
if new_attr.is_volatile() {
|
|
if new_attr.is_volatile() {
|
|
- change_list.commit_traversal();
|
|
|
|
- change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.change_list
|
|
|
|
+ .set_attribute(new_attr.name, new_attr.value, is_namespaced);
|
|
} else {
|
|
} else {
|
|
for old_attr in old {
|
|
for old_attr in old {
|
|
if old_attr.name == new_attr.name {
|
|
if old_attr.name == new_attr.name {
|
|
if old_attr.value != new_attr.value {
|
|
if old_attr.value != new_attr.value {
|
|
- change_list.commit_traversal();
|
|
|
|
- change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.change_list.set_attribute(
|
|
|
|
+ new_attr.name,
|
|
|
|
+ new_attr.value,
|
|
|
|
+ is_namespaced,
|
|
|
|
+ );
|
|
}
|
|
}
|
|
continue 'outer;
|
|
continue 'outer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.change_list
|
|
|
|
+ .set_attribute(new_attr.name, new_attr.value, is_namespaced);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -271,8 +300,8 @@ mod todo_diff {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- change_list.remove_attribute(old_attr.name);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.change_list.remove_attribute(old_attr.name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -284,18 +313,11 @@ mod todo_diff {
|
|
// [... parent]
|
|
// [... parent]
|
|
//
|
|
//
|
|
// the change list stack is in the same state when this function returns.
|
|
// the change list stack is in the same state when this function returns.
|
|
- fn diff_children(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- new: &[Node],
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) {
|
|
|
|
|
|
+ fn diff_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
|
|
if new.is_empty() {
|
|
if new.is_empty() {
|
|
if !old.is_empty() {
|
|
if !old.is_empty() {
|
|
- change_list.commit_traversal();
|
|
|
|
- remove_all_children(change_list, registry, old);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.remove_all_children(old);
|
|
}
|
|
}
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -303,36 +325,29 @@ mod todo_diff {
|
|
if new.len() == 1 {
|
|
if new.len() == 1 {
|
|
match (old.first(), &new[0]) {
|
|
match (old.first(), &new[0]) {
|
|
(
|
|
(
|
|
- Some(&Node {
|
|
|
|
- kind: NodeKind::Text(TextNode { text: old_text }),
|
|
|
|
- }),
|
|
|
|
- &Node {
|
|
|
|
- kind: NodeKind::Text(TextNode { text: new_text }),
|
|
|
|
- },
|
|
|
|
|
|
+ Some(&VNode::Text(VText { text: old_text })),
|
|
|
|
+ &VNode::Text(VText { text: new_text }),
|
|
) if old_text == new_text => {
|
|
) if old_text == new_text => {
|
|
// Don't take this fast path...
|
|
// Don't take this fast path...
|
|
}
|
|
}
|
|
- (
|
|
|
|
- _,
|
|
|
|
- &Node {
|
|
|
|
- kind: NodeKind::Text(TextNode { text }),
|
|
|
|
- },
|
|
|
|
- ) => {
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- change_list.set_text(text);
|
|
|
|
- for o in old {
|
|
|
|
- registry.remove_subtree(o);
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ (_, &VNode::Text(VText { text })) => {
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.change_list.set_text(text);
|
|
|
|
+ // for o in old {
|
|
|
|
+ // registry.remove_subtree(o);
|
|
|
|
+ // }
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
+
|
|
(_, _) => {}
|
|
(_, _) => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if old.is_empty() {
|
|
if old.is_empty() {
|
|
if !new.is_empty() {
|
|
if !new.is_empty() {
|
|
- change_list.commit_traversal();
|
|
|
|
- create_and_append_children(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.create_and_append_children(new);
|
|
}
|
|
}
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -350,11 +365,14 @@ mod todo_diff {
|
|
);
|
|
);
|
|
|
|
|
|
if new_is_keyed && old_is_keyed {
|
|
if new_is_keyed && old_is_keyed {
|
|
- let t = change_list.next_temporary();
|
|
|
|
- diff_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
|
|
|
|
- change_list.set_next_temporary(t);
|
|
|
|
|
|
+ let t = self.change_list.next_temporary();
|
|
|
|
+ // diff_keyed_children(self.change_list, old, new);
|
|
|
|
+ // diff_keyed_children(self.change_list, old, new, cached_roots);
|
|
|
|
+ // diff_keyed_children(cached_set, self.change_list, registry, old, new, cached_roots);
|
|
|
|
+ self.change_list.set_next_temporary(t);
|
|
} else {
|
|
} else {
|
|
- diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
|
|
|
|
|
|
+ self.diff_non_keyed_children(old, new);
|
|
|
|
+ // diff_non_keyed_children(cached_set, change_list, registry, old, new, cached_roots);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -379,17 +397,12 @@ mod todo_diff {
|
|
// [... parent]
|
|
// [... parent]
|
|
//
|
|
//
|
|
// Upon exiting, the change list stack is in the same state.
|
|
// Upon exiting, the change list stack is in the same state.
|
|
- fn diff_keyed_children(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- new: &[Node],
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) {
|
|
|
|
|
|
+ fn diff_keyed_children(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) {
|
|
|
|
+ // let DiffState { change_list, queue } = &*state;
|
|
|
|
+
|
|
if cfg!(debug_assertions) {
|
|
if cfg!(debug_assertions) {
|
|
- let mut keys = FxHashSet::default();
|
|
|
|
- let mut assert_unique_keys = |children: &[Node]| {
|
|
|
|
|
|
+ let mut keys = fxhash::FxHashSet::default();
|
|
|
|
+ let mut assert_unique_keys = |children: &[VNode]| {
|
|
keys.clear();
|
|
keys.clear();
|
|
for child in children {
|
|
for child in children {
|
|
let key = child.key();
|
|
let key = child.key();
|
|
@@ -414,11 +427,15 @@ mod todo_diff {
|
|
//
|
|
//
|
|
// `shared_prefix_count` is the count of how many nodes at the start of
|
|
// `shared_prefix_count` is the count of how many nodes at the start of
|
|
// `new` and `old` share the same keys.
|
|
// `new` and `old` share the same keys.
|
|
- let shared_prefix_count =
|
|
|
|
- match diff_keyed_prefix(cached_set, change_list, registry, old, new, cached_roots) {
|
|
|
|
- KeyedPrefixResult::Finished => return,
|
|
|
|
- KeyedPrefixResult::MoreWorkToDo(count) => count,
|
|
|
|
- };
|
|
|
|
|
|
+ let shared_prefix_count = match self.diff_keyed_prefix(old, new) {
|
|
|
|
+ KeyedPrefixResult::Finished => return,
|
|
|
|
+ KeyedPrefixResult::MoreWorkToDo(count) => count,
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ match self.diff_keyed_prefix(old, new) {
|
|
|
|
+ KeyedPrefixResult::Finished => return,
|
|
|
|
+ KeyedPrefixResult::MoreWorkToDo(count) => count,
|
|
|
|
+ };
|
|
|
|
|
|
// Next, we find out how many of the nodes at the end of the children have
|
|
// Next, we find out how many of the nodes at the end of the children have
|
|
// the same key. We do _not_ diff them yet, since we want to emit the change
|
|
// the same key. We do _not_ diff them yet, since we want to emit the change
|
|
@@ -440,13 +457,9 @@ mod todo_diff {
|
|
// Ok, we now hopefully have a smaller range of children in the middle
|
|
// Ok, we now hopefully have a smaller range of children in the middle
|
|
// within which to re-order nodes with the same keys, remove old nodes with
|
|
// within which to re-order nodes with the same keys, remove old nodes with
|
|
// now-unused keys, and create new nodes with fresh keys.
|
|
// now-unused keys, and create new nodes with fresh keys.
|
|
- diff_keyed_middle(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
|
|
+ self.diff_keyed_middle(
|
|
&old[shared_prefix_count..old_shared_suffix_start],
|
|
&old[shared_prefix_count..old_shared_suffix_start],
|
|
&new[shared_prefix_count..new_shared_suffix_start],
|
|
&new[shared_prefix_count..new_shared_suffix_start],
|
|
- cached_roots,
|
|
|
|
shared_prefix_count,
|
|
shared_prefix_count,
|
|
shared_suffix_count,
|
|
shared_suffix_count,
|
|
old_shared_suffix_start,
|
|
old_shared_suffix_start,
|
|
@@ -457,27 +470,10 @@ mod todo_diff {
|
|
let new_suffix = &new[new_shared_suffix_start..];
|
|
let new_suffix = &new[new_shared_suffix_start..];
|
|
debug_assert_eq!(old_suffix.len(), new_suffix.len());
|
|
debug_assert_eq!(old_suffix.len(), new_suffix.len());
|
|
if !old_suffix.is_empty() {
|
|
if !old_suffix.is_empty() {
|
|
- diff_keyed_suffix(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- old_suffix,
|
|
|
|
- new_suffix,
|
|
|
|
- cached_roots,
|
|
|
|
- new_shared_suffix_start,
|
|
|
|
- );
|
|
|
|
|
|
+ self.diff_keyed_suffix(old_suffix, new_suffix, new_shared_suffix_start)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- enum KeyedPrefixResult {
|
|
|
|
- // Fast path: we finished diffing all the children just by looking at the
|
|
|
|
- // prefix of shared keys!
|
|
|
|
- Finished,
|
|
|
|
- // There is more diffing work to do. Here is a count of how many children at
|
|
|
|
- // the beginning of `new` and `old` we already processed.
|
|
|
|
- MoreWorkToDo(usize),
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
// Diff the prefix of children in `new` and `old` that share the same keys in
|
|
// Diff the prefix of children in `new` and `old` that share the same keys in
|
|
// the same order.
|
|
// the same order.
|
|
//
|
|
//
|
|
@@ -486,15 +482,8 @@ mod todo_diff {
|
|
// [... parent]
|
|
// [... parent]
|
|
//
|
|
//
|
|
// Upon exit, the change list stack is the same.
|
|
// Upon exit, the change list stack is the same.
|
|
- fn diff_keyed_prefix(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- new: &[Node],
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) -> KeyedPrefixResult {
|
|
|
|
- change_list.go_down();
|
|
|
|
|
|
+ fn diff_keyed_prefix(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) -> KeyedPrefixResult {
|
|
|
|
+ self.change_list.go_down();
|
|
let mut shared_prefix_count = 0;
|
|
let mut shared_prefix_count = 0;
|
|
|
|
|
|
for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
|
|
for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
|
|
@@ -502,36 +491,32 @@ mod todo_diff {
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
- change_list.go_to_sibling(i);
|
|
|
|
- diff(cached_set, change_list, registry, old, new, cached_roots);
|
|
|
|
|
|
+ self.change_list.go_to_sibling(i);
|
|
|
|
+
|
|
|
|
+ self.diff_node(old, new);
|
|
|
|
+
|
|
shared_prefix_count += 1;
|
|
shared_prefix_count += 1;
|
|
}
|
|
}
|
|
|
|
|
|
// If that was all of the old children, then create and append the remaining
|
|
// If that was all of the old children, then create and append the remaining
|
|
// new children and we're finished.
|
|
// new children and we're finished.
|
|
if shared_prefix_count == old.len() {
|
|
if shared_prefix_count == old.len() {
|
|
- change_list.go_up();
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- create_and_append_children(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- &new[shared_prefix_count..],
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
|
|
+ self.change_list.go_up();
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.create_and_append_children(&new[shared_prefix_count..]);
|
|
return KeyedPrefixResult::Finished;
|
|
return KeyedPrefixResult::Finished;
|
|
}
|
|
}
|
|
|
|
|
|
// And if that was all of the new children, then remove all of the remaining
|
|
// And if that was all of the new children, then remove all of the remaining
|
|
// old children and we're finished.
|
|
// old children and we're finished.
|
|
if shared_prefix_count == new.len() {
|
|
if shared_prefix_count == new.len() {
|
|
- change_list.go_to_sibling(shared_prefix_count);
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
|
|
|
|
|
|
+ self.change_list.go_to_sibling(shared_prefix_count);
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
|
|
return KeyedPrefixResult::Finished;
|
|
return KeyedPrefixResult::Finished;
|
|
}
|
|
}
|
|
|
|
|
|
- change_list.go_up();
|
|
|
|
|
|
+ self.change_list.go_up();
|
|
KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
|
|
KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -549,12 +534,9 @@ mod todo_diff {
|
|
//
|
|
//
|
|
// Upon exit from this function, it will be restored to that same state.
|
|
// Upon exit from this function, it will be restored to that same state.
|
|
fn diff_keyed_middle(
|
|
fn diff_keyed_middle(
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- mut new: &[Node],
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
|
|
+ &mut self,
|
|
|
|
+ old: &[VNode<'a>],
|
|
|
|
+ mut new: &[VNode<'a>],
|
|
shared_prefix_count: usize,
|
|
shared_prefix_count: usize,
|
|
shared_suffix_count: usize,
|
|
shared_suffix_count: usize,
|
|
old_shared_suffix_start: usize,
|
|
old_shared_suffix_start: usize,
|
|
@@ -595,14 +577,16 @@ mod todo_diff {
|
|
// afresh.
|
|
// afresh.
|
|
if shared_suffix_count == 0 && shared_keys.is_empty() {
|
|
if shared_suffix_count == 0 && shared_keys.is_empty() {
|
|
if shared_prefix_count == 0 {
|
|
if shared_prefix_count == 0 {
|
|
- change_list.commit_traversal();
|
|
|
|
- remove_all_children(change_list, registry, old);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.remove_all_children(old);
|
|
} else {
|
|
} else {
|
|
- change_list.go_down_to_child(shared_prefix_count);
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- remove_self_and_next_siblings(change_list, registry, &old[shared_prefix_count..]);
|
|
|
|
|
|
+ self.change_list.go_down_to_child(shared_prefix_count);
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
|
|
}
|
|
}
|
|
- create_and_append_children(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
|
|
+
|
|
|
|
+ self.create_and_append_children(new);
|
|
|
|
+
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -619,8 +603,8 @@ mod todo_diff {
|
|
.unwrap_or(old.len());
|
|
.unwrap_or(old.len());
|
|
|
|
|
|
if end - start > 0 {
|
|
if end - start > 0 {
|
|
- change_list.commit_traversal();
|
|
|
|
- let mut t = change_list.save_children_to_temporaries(
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ let mut t = self.change_list.save_children_to_temporaries(
|
|
shared_prefix_count + start,
|
|
shared_prefix_count + start,
|
|
shared_prefix_count + end,
|
|
shared_prefix_count + end,
|
|
);
|
|
);
|
|
@@ -643,9 +627,10 @@ mod todo_diff {
|
|
let mut removed_count = 0;
|
|
let mut removed_count = 0;
|
|
for (i, old_child) in old.iter().enumerate().rev() {
|
|
for (i, old_child) in old.iter().enumerate().rev() {
|
|
if !shared_keys.contains(&old_child.key()) {
|
|
if !shared_keys.contains(&old_child.key()) {
|
|
- registry.remove_subtree(old_child);
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- change_list.remove_child(i + shared_prefix_count);
|
|
|
|
|
|
+ // registry.remove_subtree(old_child);
|
|
|
|
+ // todo
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.change_list.remove_child(i + shared_prefix_count);
|
|
removed_count += 1;
|
|
removed_count += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -688,47 +673,45 @@ mod todo_diff {
|
|
// shared suffix to the change list stack.
|
|
// shared suffix to the change list stack.
|
|
//
|
|
//
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.go_down_to_child(old_shared_suffix_start - removed_count);
|
|
|
|
|
|
+ self.change_list
|
|
|
|
+ .go_down_to_child(old_shared_suffix_start - removed_count);
|
|
// [... parent first_child_of_shared_suffix]
|
|
// [... parent first_child_of_shared_suffix]
|
|
} else {
|
|
} else {
|
|
// There is no shared suffix coming after these middle children.
|
|
// There is no shared suffix coming after these middle children.
|
|
// Therefore we have to process the last child in `new` and move it to
|
|
// Therefore we have to process the last child in `new` and move it to
|
|
// the end of the parent's children if it isn't already there.
|
|
// the end of the parent's children if it isn't already there.
|
|
let last_index = new.len() - 1;
|
|
let last_index = new.len() - 1;
|
|
- let last = new.last().unwrap_throw();
|
|
|
|
|
|
+ // uhhhh why an unwrap?
|
|
|
|
+ let last = new.last().unwrap();
|
|
|
|
+ // let last = new.last().unwrap_throw();
|
|
new = &new[..new.len() - 1];
|
|
new = &new[..new.len() - 1];
|
|
if shared_keys.contains(&last.key()) {
|
|
if shared_keys.contains(&last.key()) {
|
|
let old_index = new_index_to_old_index[last_index];
|
|
let old_index = new_index_to_old_index[last_index];
|
|
let temp = old_index_to_temp[old_index];
|
|
let temp = old_index_to_temp[old_index];
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.go_down_to_temp_child(temp);
|
|
|
|
|
|
+ self.change_list.go_down_to_temp_child(temp);
|
|
// [... parent last]
|
|
// [... parent last]
|
|
- diff(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- &old[old_index],
|
|
|
|
- last,
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
|
|
+ self.diff_node(&old[old_index], last);
|
|
|
|
+
|
|
if new_index_is_in_lis.contains(&last_index) {
|
|
if new_index_is_in_lis.contains(&last_index) {
|
|
// Don't move it, since it is already where it needs to be.
|
|
// Don't move it, since it is already where it needs to be.
|
|
} else {
|
|
} else {
|
|
- change_list.commit_traversal();
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
// [... parent last]
|
|
// [... parent last]
|
|
- change_list.append_child();
|
|
|
|
|
|
+ self.change_list.append_child();
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.go_down_to_temp_child(temp);
|
|
|
|
|
|
+ self.change_list.go_down_to_temp_child(temp);
|
|
// [... parent last]
|
|
// [... parent last]
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- change_list.commit_traversal();
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
// [... parent]
|
|
// [... parent]
|
|
- create(cached_set, change_list, registry, last, cached_roots);
|
|
|
|
|
|
+ self.create(last);
|
|
|
|
+
|
|
// [... parent last]
|
|
// [... parent last]
|
|
- change_list.append_child();
|
|
|
|
|
|
+ self.change_list.append_child();
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.go_down_to_reverse_child(0);
|
|
|
|
|
|
+ self.change_list.go_down_to_reverse_child(0);
|
|
// [... parent last]
|
|
// [... parent last]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -737,11 +720,11 @@ mod todo_diff {
|
|
let old_index = new_index_to_old_index[new_index];
|
|
let old_index = new_index_to_old_index[new_index];
|
|
if old_index == u32::MAX as usize {
|
|
if old_index == u32::MAX as usize {
|
|
debug_assert!(!shared_keys.contains(&new_child.key()));
|
|
debug_assert!(!shared_keys.contains(&new_child.key()));
|
|
- change_list.commit_traversal();
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
// [... parent successor]
|
|
// [... parent successor]
|
|
- create(cached_set, change_list, registry, new_child, cached_roots);
|
|
|
|
|
|
+ self.create(new_child);
|
|
// [... parent successor new_child]
|
|
// [... parent successor new_child]
|
|
- change_list.insert_before();
|
|
|
|
|
|
+ self.change_list.insert_before();
|
|
// [... parent new_child]
|
|
// [... parent new_child]
|
|
} else {
|
|
} else {
|
|
debug_assert!(shared_keys.contains(&new_child.key()));
|
|
debug_assert!(shared_keys.contains(&new_child.key()));
|
|
@@ -750,30 +733,23 @@ mod todo_diff {
|
|
|
|
|
|
if new_index_is_in_lis.contains(&new_index) {
|
|
if new_index_is_in_lis.contains(&new_index) {
|
|
// [... parent successor]
|
|
// [... parent successor]
|
|
- change_list.go_to_temp_sibling(temp);
|
|
|
|
|
|
+ self.change_list.go_to_temp_sibling(temp);
|
|
// [... parent new_child]
|
|
// [... parent new_child]
|
|
} else {
|
|
} else {
|
|
- change_list.commit_traversal();
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
// [... parent successor]
|
|
// [... parent successor]
|
|
- change_list.push_temporary(temp);
|
|
|
|
|
|
+ self.change_list.push_temporary(temp);
|
|
// [... parent successor new_child]
|
|
// [... parent successor new_child]
|
|
- change_list.insert_before();
|
|
|
|
|
|
+ self.change_list.insert_before();
|
|
// [... parent new_child]
|
|
// [... parent new_child]
|
|
}
|
|
}
|
|
|
|
|
|
- diff(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- &old[old_index],
|
|
|
|
- new_child,
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
|
|
+ self.diff_node(&old[old_index], new_child);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// [... parent child]
|
|
// [... parent child]
|
|
- change_list.go_up();
|
|
|
|
|
|
+ self.change_list.go_up();
|
|
// [... parent]
|
|
// [... parent]
|
|
}
|
|
}
|
|
|
|
|
|
@@ -785,35 +761,26 @@ mod todo_diff {
|
|
//
|
|
//
|
|
// When this function exits, the change list stack remains the same.
|
|
// When this function exits, the change list stack remains the same.
|
|
fn diff_keyed_suffix(
|
|
fn diff_keyed_suffix(
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- new: &[Node],
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
|
|
+ &mut self,
|
|
|
|
+ old: &[VNode<'a>],
|
|
|
|
+ new: &[VNode<'a>],
|
|
new_shared_suffix_start: usize,
|
|
new_shared_suffix_start: usize,
|
|
) {
|
|
) {
|
|
debug_assert_eq!(old.len(), new.len());
|
|
debug_assert_eq!(old.len(), new.len());
|
|
debug_assert!(!old.is_empty());
|
|
debug_assert!(!old.is_empty());
|
|
|
|
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.go_down();
|
|
|
|
|
|
+ self.change_list.go_down();
|
|
// [... parent new_child]
|
|
// [... parent new_child]
|
|
|
|
|
|
for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
|
|
for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
|
|
- change_list.go_to_sibling(new_shared_suffix_start + i);
|
|
|
|
- diff(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- old_child,
|
|
|
|
- new_child,
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
|
|
+ self.change_list.go_to_sibling(new_shared_suffix_start + i);
|
|
|
|
+
|
|
|
|
+ self.diff_node(old_child, new_child);
|
|
}
|
|
}
|
|
|
|
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.go_up();
|
|
|
|
|
|
+ self.change_list.go_up();
|
|
}
|
|
}
|
|
|
|
|
|
// Diff children that are not keyed.
|
|
// Diff children that are not keyed.
|
|
@@ -824,128 +791,50 @@ mod todo_diff {
|
|
// [... parent]
|
|
// [... parent]
|
|
//
|
|
//
|
|
// the change list stack is in the same state when this function returns.
|
|
// the change list stack is in the same state when this function returns.
|
|
- fn diff_non_keyed_children(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- new: &[Node],
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) {
|
|
|
|
|
|
+ fn diff_non_keyed_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
|
|
// Handled these cases in `diff_children` before calling this function.
|
|
// Handled these cases in `diff_children` before calling this function.
|
|
debug_assert!(!new.is_empty());
|
|
debug_assert!(!new.is_empty());
|
|
debug_assert!(!old.is_empty());
|
|
debug_assert!(!old.is_empty());
|
|
|
|
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.go_down();
|
|
|
|
|
|
+ self.change_list.go_down();
|
|
// [... parent child]
|
|
// [... parent child]
|
|
|
|
|
|
for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
|
|
for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
|
|
// [... parent prev_child]
|
|
// [... parent prev_child]
|
|
- change_list.go_to_sibling(i);
|
|
|
|
|
|
+ self.change_list.go_to_sibling(i);
|
|
// [... parent this_child]
|
|
// [... parent this_child]
|
|
- diff(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- old_child,
|
|
|
|
- new_child,
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
|
|
+ self.diff_node(old_child, new_child);
|
|
}
|
|
}
|
|
|
|
|
|
match old.len().cmp(&new.len()) {
|
|
match old.len().cmp(&new.len()) {
|
|
Ordering::Greater => {
|
|
Ordering::Greater => {
|
|
// [... parent prev_child]
|
|
// [... parent prev_child]
|
|
- change_list.go_to_sibling(new.len());
|
|
|
|
|
|
+ self.change_list.go_to_sibling(new.len());
|
|
// [... parent first_child_to_remove]
|
|
// [... parent first_child_to_remove]
|
|
- change_list.commit_traversal();
|
|
|
|
- remove_self_and_next_siblings(change_list, registry, &old[new.len()..]);
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ // support::remove_self_and_next_siblings(state, &old[new.len()..]);
|
|
|
|
+ self.remove_self_and_next_siblings(&old[new.len()..]);
|
|
// [... parent]
|
|
// [... parent]
|
|
}
|
|
}
|
|
Ordering::Less => {
|
|
Ordering::Less => {
|
|
// [... parent last_child]
|
|
// [... parent last_child]
|
|
- change_list.go_up();
|
|
|
|
|
|
+ self.change_list.go_up();
|
|
// [... parent]
|
|
// [... parent]
|
|
- change_list.commit_traversal();
|
|
|
|
- create_and_append_children(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- &new[old.len()..],
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
|
|
+ self.change_list.commit_traversal();
|
|
|
|
+ self.create_and_append_children(&new[old.len()..]);
|
|
}
|
|
}
|
|
Ordering::Equal => {
|
|
Ordering::Equal => {
|
|
// [... parent child]
|
|
// [... parent child]
|
|
- change_list.go_up();
|
|
|
|
|
|
+ self.change_list.go_up();
|
|
// [... parent]
|
|
// [... parent]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Create the given children and append them to the parent node.
|
|
|
|
- //
|
|
|
|
- // The parent node must currently be on top of the change list stack:
|
|
|
|
- //
|
|
|
|
- // [... parent]
|
|
|
|
- //
|
|
|
|
- // When this function returns, the change list stack is in the same state.
|
|
|
|
- fn create_and_append_children(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- new: &[Node],
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) {
|
|
|
|
- debug_assert!(change_list.traversal_is_committed());
|
|
|
|
- for child in new {
|
|
|
|
- create(cached_set, change_list, registry, child, cached_roots);
|
|
|
|
- change_list.append_child();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 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(
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- ) {
|
|
|
|
- debug_assert!(change_list.traversal_is_committed());
|
|
|
|
- for child in old {
|
|
|
|
- registry.remove_subtree(child);
|
|
|
|
- }
|
|
|
|
- // Fast way to remove all children: set the node's textContent to an empty
|
|
|
|
- // string.
|
|
|
|
- change_list.set_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_self_and_next_siblings(
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- old: &[Node],
|
|
|
|
- ) {
|
|
|
|
- debug_assert!(change_list.traversal_is_committed());
|
|
|
|
- for child in old {
|
|
|
|
- registry.remove_subtree(child);
|
|
|
|
- }
|
|
|
|
- change_list.remove_self_and_next_siblings();
|
|
|
|
- }
|
|
|
|
|
|
+ // ======================
|
|
|
|
+ // Support methods
|
|
|
|
+ // ======================
|
|
|
|
|
|
// Emit instructions to create the given virtual node.
|
|
// Emit instructions to create the given virtual node.
|
|
//
|
|
//
|
|
@@ -956,19 +845,13 @@ mod todo_diff {
|
|
// When this function returns, the new node is on top of the change list stack:
|
|
// When this function returns, the new node is on top of the change list stack:
|
|
//
|
|
//
|
|
// [... node]
|
|
// [... node]
|
|
- fn create(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- node: &Node,
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) {
|
|
|
|
- debug_assert!(change_list.traversal_is_committed());
|
|
|
|
- match node.kind {
|
|
|
|
- NodeKind::Text(TextNode { text }) => {
|
|
|
|
- change_list.create_text_node(text);
|
|
|
|
|
|
+ fn create(&mut self, node: &VNode<'a>) {
|
|
|
|
+ debug_assert!(self.change_list.traversal_is_committed());
|
|
|
|
+ match node {
|
|
|
|
+ VNode::Text(VText { text }) => {
|
|
|
|
+ self.change_list.create_text_node(text);
|
|
}
|
|
}
|
|
- NodeKind::Element(&ElementNode {
|
|
|
|
|
|
+ VNode::Element(&VElement {
|
|
key: _,
|
|
key: _,
|
|
tag_name,
|
|
tag_name,
|
|
listeners,
|
|
listeners,
|
|
@@ -977,20 +860,21 @@ mod todo_diff {
|
|
namespace,
|
|
namespace,
|
|
}) => {
|
|
}) => {
|
|
if let Some(namespace) = namespace {
|
|
if let Some(namespace) = namespace {
|
|
- change_list.create_element_ns(tag_name, namespace);
|
|
|
|
|
|
+ self.change_list.create_element_ns(tag_name, namespace);
|
|
} else {
|
|
} else {
|
|
- change_list.create_element(tag_name);
|
|
|
|
|
|
+ self.change_list.create_element(tag_name);
|
|
}
|
|
}
|
|
|
|
|
|
for l in listeners {
|
|
for l in listeners {
|
|
- unsafe {
|
|
|
|
- registry.add(l);
|
|
|
|
- }
|
|
|
|
- change_list.new_event_listener(l);
|
|
|
|
|
|
+ // unsafe {
|
|
|
|
+ // registry.add(l);
|
|
|
|
+ // }
|
|
|
|
+ self.change_list.new_event_listener(l);
|
|
}
|
|
}
|
|
|
|
|
|
for attr in attributes {
|
|
for attr in attributes {
|
|
- change_list.set_attribute(&attr.name, &attr.value, namespace.is_some());
|
|
|
|
|
|
+ self.change_list
|
|
|
|
+ .set_attribute(&attr.name, &attr.value, namespace.is_some());
|
|
}
|
|
}
|
|
|
|
|
|
// Fast path: if there is a single text child, it is faster to
|
|
// Fast path: if there is a single text child, it is faster to
|
|
@@ -1000,169 +884,237 @@ mod todo_diff {
|
|
// text content, and finally (3) append the text node to this
|
|
// text content, and finally (3) append the text node to this
|
|
// parent.
|
|
// parent.
|
|
if children.len() == 1 {
|
|
if children.len() == 1 {
|
|
- if let Node {
|
|
|
|
- kind: NodeKind::Text(TextNode { text }),
|
|
|
|
- } = children[0]
|
|
|
|
- {
|
|
|
|
- change_list.set_text(text);
|
|
|
|
|
|
+ if let VNode::Text(VText { text }) = children[0] {
|
|
|
|
+ self.change_list.set_text(text);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for child in children {
|
|
for child in children {
|
|
- create(cached_set, change_list, registry, child, cached_roots);
|
|
|
|
- change_list.append_child();
|
|
|
|
|
|
+ self.create(child);
|
|
|
|
+ self.change_list.append_child();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- NodeKind::Cached(ref c) => {
|
|
|
|
- cached_roots.insert(c.id);
|
|
|
|
- let (node, template) = cached_set.get(c.id);
|
|
|
|
- if let Some(template) = template {
|
|
|
|
- create_with_template(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- template,
|
|
|
|
- node,
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
- } else {
|
|
|
|
- create(cached_set, change_list, registry, node, cached_roots);
|
|
|
|
- }
|
|
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ todo: integrate re-entrace
|
|
|
|
+ */
|
|
|
|
+ // NodeKind::Cached(ref c) => {
|
|
|
|
+ // cached_roots.insert(c.id);
|
|
|
|
+ // let (node, template) = cached_set.get(c.id);
|
|
|
|
+ // if let Some(template) = template {
|
|
|
|
+ // create_with_template(
|
|
|
|
+ // cached_set,
|
|
|
|
+ // self.change_list,
|
|
|
|
+ // registry,
|
|
|
|
+ // template,
|
|
|
|
+ // node,
|
|
|
|
+ // cached_roots,
|
|
|
|
+ // );
|
|
|
|
+ // } else {
|
|
|
|
+ // create(cached_set, change_list, registry, node, cached_roots);
|
|
|
|
+ // }
|
|
|
|
+ // }
|
|
|
|
+ VNode::Suspended => {
|
|
|
|
+ todo!("Creation of VNode::Suspended not yet supported")
|
|
|
|
+ }
|
|
|
|
+ VNode::Component(_) => {
|
|
|
|
+ todo!("Creation of VNode::Component not yet supported")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Get or create the template.
|
|
|
|
- //
|
|
|
|
- // Upon entering this function the change list stack may be in any shape:
|
|
|
|
|
|
+ // Remove all of a node's children.
|
|
//
|
|
//
|
|
- // [...]
|
|
|
|
|
|
+ // The change list stack must have this shape upon entry to this function:
|
|
//
|
|
//
|
|
- // When this function returns, it leaves a freshly cloned copy of the template
|
|
|
|
- // on the top of the change list stack:
|
|
|
|
|
|
+ // [... parent]
|
|
//
|
|
//
|
|
- // [... template]
|
|
|
|
- #[inline]
|
|
|
|
- fn get_or_create_template<'a>(
|
|
|
|
- cached_set: &'a CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- template_id: CacheId,
|
|
|
|
- ) -> (&'a Node<'a>, bool) {
|
|
|
|
- let (template, template_template) = cached_set.get(template_id);
|
|
|
|
- debug_assert!(
|
|
|
|
- template_template.is_none(),
|
|
|
|
- "templates should not be templated themselves"
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- // If we haven't already created and saved the physical DOM subtree for this
|
|
|
|
- // template, do that now.
|
|
|
|
- if change_list.has_template(template_id) {
|
|
|
|
- // Clone the template and push it onto the stack.
|
|
|
|
- //
|
|
|
|
- // [...]
|
|
|
|
- change_list.push_template(template_id);
|
|
|
|
- // [... template]
|
|
|
|
|
|
+ // When this function returns, the change list stack is in the same state.
|
|
|
|
+ pub fn remove_all_children(&mut self, old: &[VNode<'a>]) {
|
|
|
|
+ debug_assert!(self.change_list.traversal_is_committed());
|
|
|
|
+ for child in old {
|
|
|
|
+ // registry.remove_subtree(child);
|
|
|
|
+ }
|
|
|
|
+ // Fast way to remove all children: set the node's textContent to an empty
|
|
|
|
+ // string.
|
|
|
|
+ self.change_list.set_text("");
|
|
|
|
+ }
|
|
|
|
|
|
- (template, true)
|
|
|
|
- } else {
|
|
|
|
- // [...]
|
|
|
|
- create(cached_set, change_list, registry, template, cached_roots);
|
|
|
|
- // [... template]
|
|
|
|
- change_list.save_template(template_id);
|
|
|
|
- // [... template]
|
|
|
|
|
|
+ // Create the given children and append them to the parent node.
|
|
|
|
+ //
|
|
|
|
+ // The parent node must currently be on top of the change list stack:
|
|
|
|
+ //
|
|
|
|
+ // [... parent]
|
|
|
|
+ //
|
|
|
|
+ // When this function returns, the change list stack is in the same state.
|
|
|
|
+ pub fn create_and_append_children(&mut self, new: &[VNode<'a>]) {
|
|
|
|
+ debug_assert!(self.change_list.traversal_is_committed());
|
|
|
|
+ for child in new {
|
|
|
|
+ self.create(child);
|
|
|
|
+ self.change_list.append_child();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- (template, false)
|
|
|
|
|
|
+ // 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]
|
|
|
|
+ pub fn remove_self_and_next_siblings(&mut self, old: &[VNode<'a>]) {
|
|
|
|
+ debug_assert!(self.change_list.traversal_is_committed());
|
|
|
|
+ for child in old {
|
|
|
|
+ // registry.remove_subtree(child);
|
|
}
|
|
}
|
|
|
|
+ self.change_list.remove_self_and_next_siblings();
|
|
}
|
|
}
|
|
|
|
+}
|
|
|
|
|
|
- fn create_and_replace(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- new_template: Option<CacheId>,
|
|
|
|
- old: &Node,
|
|
|
|
- new: &Node,
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) {
|
|
|
|
- debug_assert!(change_list.traversal_is_committed());
|
|
|
|
-
|
|
|
|
- if let Some(template_id) = new_template {
|
|
|
|
- let (template, needs_listeners) = get_or_create_template(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- cached_roots,
|
|
|
|
- template_id,
|
|
|
|
- );
|
|
|
|
- change_list.replace_with();
|
|
|
|
-
|
|
|
|
- let mut old_forcing = None;
|
|
|
|
- if needs_listeners {
|
|
|
|
- old_forcing = Some(change_list.push_force_new_listeners());
|
|
|
|
- }
|
|
|
|
|
|
+enum KeyedPrefixResult {
|
|
|
|
+ // Fast path: we finished diffing all the children just by looking at the
|
|
|
|
+ // prefix of shared keys!
|
|
|
|
+ Finished,
|
|
|
|
+ // There is more diffing work to do. Here is a count of how many children at
|
|
|
|
+ // the beginning of `new` and `old` we already processed.
|
|
|
|
+ MoreWorkToDo(usize),
|
|
|
|
+}
|
|
|
|
|
|
- diff(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- template,
|
|
|
|
- new,
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- if let Some(old) = old_forcing {
|
|
|
|
- change_list.pop_force_new_listeners(old);
|
|
|
|
- }
|
|
|
|
|
|
+mod support {
|
|
|
|
+ use super::*;
|
|
|
|
+
|
|
|
|
+ // // Get or create the template.
|
|
|
|
+ // //
|
|
|
|
+ // // Upon entering this function the change list stack may be in any shape:
|
|
|
|
+ // //
|
|
|
|
+ // // [...]
|
|
|
|
+ // //
|
|
|
|
+ // // When this function returns, it leaves a freshly cloned copy of the template
|
|
|
|
+ // // on the top of the change list stack:
|
|
|
|
+ // //
|
|
|
|
+ // // [... template]
|
|
|
|
+ // #[inline]
|
|
|
|
+ // pub fn get_or_create_template<'a>(// cached_set: &'a CachedSet,
|
|
|
|
+ // // change_list: &mut ChangeListBuilder,
|
|
|
|
+ // // registry: &mut EventsRegistry,
|
|
|
|
+ // // cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
+ // // template_id: CacheId,
|
|
|
|
+ // ) -> (&'a Node<'a>, bool) {
|
|
|
|
+ // let (template, template_template) = cached_set.get(template_id);
|
|
|
|
+ // debug_assert!(
|
|
|
|
+ // template_template.is_none(),
|
|
|
|
+ // "templates should not be templated themselves"
|
|
|
|
+ // );
|
|
|
|
+
|
|
|
|
+ // // If we haven't already created and saved the physical DOM subtree for this
|
|
|
|
+ // // template, do that now.
|
|
|
|
+ // if change_list.has_template(template_id) {
|
|
|
|
+ // // Clone the template and push it onto the stack.
|
|
|
|
+ // //
|
|
|
|
+ // // [...]
|
|
|
|
+ // change_list.push_template(template_id);
|
|
|
|
+ // // [... template]
|
|
|
|
+
|
|
|
|
+ // (template, true)
|
|
|
|
+ // } else {
|
|
|
|
+ // // [...]
|
|
|
|
+ // create(cached_set, change_list, registry, template, cached_roots);
|
|
|
|
+ // // [... template]
|
|
|
|
+ // change_list.save_template(template_id);
|
|
|
|
+ // // [... template]
|
|
|
|
+
|
|
|
|
+ // (template, false)
|
|
|
|
+ // }
|
|
|
|
+ // }
|
|
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- } else {
|
|
|
|
- create(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
- change_list.replace_with();
|
|
|
|
- }
|
|
|
|
- registry.remove_subtree(old);
|
|
|
|
- }
|
|
|
|
|
|
+ // pub fn create_and_replace(
|
|
|
|
+ // cached_set: &CachedSet,
|
|
|
|
+ // change_list: &mut ChangeListBuilder,
|
|
|
|
+ // registry: &mut EventsRegistry,
|
|
|
|
+ // new_template: Option<CacheId>,
|
|
|
|
+ // old: &Node,
|
|
|
|
+ // new: &Node,
|
|
|
|
+ // cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
+ // ) {
|
|
|
|
+ // debug_assert!(change_list.traversal_is_committed());
|
|
|
|
+
|
|
|
|
+ // if let Some(template_id) = new_template {
|
|
|
|
+ // let (template, needs_listeners) = get_or_create_template(
|
|
|
|
+ // cached_set,
|
|
|
|
+ // change_list,
|
|
|
|
+ // registry,
|
|
|
|
+ // cached_roots,
|
|
|
|
+ // template_id,
|
|
|
|
+ // );
|
|
|
|
+ // change_list.replace_with();
|
|
|
|
|
|
- fn create_with_template(
|
|
|
|
- cached_set: &CachedSet,
|
|
|
|
- change_list: &mut ChangeListBuilder,
|
|
|
|
- registry: &mut EventsRegistry,
|
|
|
|
- template_id: CacheId,
|
|
|
|
- node: &Node,
|
|
|
|
- cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
- ) {
|
|
|
|
- debug_assert!(change_list.traversal_is_committed());
|
|
|
|
|
|
+ // let mut old_forcing = None;
|
|
|
|
+ // if needs_listeners {
|
|
|
|
+ // old_forcing = Some(change_list.push_force_new_listeners());
|
|
|
|
+ // }
|
|
|
|
|
|
- // [...]
|
|
|
|
- let (template, needs_listeners) =
|
|
|
|
- get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
|
|
|
|
- // [... template]
|
|
|
|
|
|
+ // diff(
|
|
|
|
+ // cached_set,
|
|
|
|
+ // change_list,
|
|
|
|
+ // registry,
|
|
|
|
+ // template,
|
|
|
|
+ // new,
|
|
|
|
+ // cached_roots,
|
|
|
|
+ // );
|
|
|
|
|
|
- // Now diff the node with its template.
|
|
|
|
- //
|
|
|
|
- // We must force adding new listeners instead of updating existing ones,
|
|
|
|
- // since listeners don't get cloned in `cloneNode`.
|
|
|
|
- let mut old_forcing = None;
|
|
|
|
- if needs_listeners {
|
|
|
|
- old_forcing = Some(change_list.push_force_new_listeners());
|
|
|
|
- }
|
|
|
|
|
|
+ // if let Some(old) = old_forcing {
|
|
|
|
+ // change_list.pop_force_new_listeners(old);
|
|
|
|
+ // }
|
|
|
|
|
|
- diff(
|
|
|
|
- cached_set,
|
|
|
|
- change_list,
|
|
|
|
- registry,
|
|
|
|
- template,
|
|
|
|
- node,
|
|
|
|
- cached_roots,
|
|
|
|
- );
|
|
|
|
|
|
+ // change_list.commit_traversal();
|
|
|
|
+ // } else {
|
|
|
|
+ // create(cached_set, change_list, registry, new, cached_roots);
|
|
|
|
+ // change_list.replace_with();
|
|
|
|
+ // }
|
|
|
|
+ // registry.remove_subtree(old);
|
|
|
|
+ // }
|
|
|
|
|
|
- if let Some(old) = old_forcing {
|
|
|
|
- change_list.pop_force_new_listeners(old);
|
|
|
|
- }
|
|
|
|
|
|
+ // pub fn create_with_template(
|
|
|
|
+ // cached_set: &CachedSet,
|
|
|
|
+ // change_list: &mut ChangeListBuilder,
|
|
|
|
+ // registry: &mut EventsRegistry,
|
|
|
|
+ // template_id: CacheId,
|
|
|
|
+ // node: &Node,
|
|
|
|
+ // cached_roots: &mut FxHashSet<CacheId>,
|
|
|
|
+ // ) {
|
|
|
|
+ // debug_assert!(change_list.traversal_is_committed());
|
|
|
|
+
|
|
|
|
+ // // [...]
|
|
|
|
+ // let (template, needs_listeners) =
|
|
|
|
+ // get_or_create_template(cached_set, change_list, registry, cached_roots, template_id);
|
|
|
|
+ // // [... template]
|
|
|
|
+
|
|
|
|
+ // // Now diff the node with its template.
|
|
|
|
+ // //
|
|
|
|
+ // // We must force adding new listeners instead of updating existing ones,
|
|
|
|
+ // // since listeners don't get cloned in `cloneNode`.
|
|
|
|
+ // let mut old_forcing = None;
|
|
|
|
+ // if needs_listeners {
|
|
|
|
+ // old_forcing = Some(change_list.push_force_new_listeners());
|
|
|
|
+ // }
|
|
|
|
|
|
- // Make sure that we come back up to the level we were at originally.
|
|
|
|
- change_list.commit_traversal();
|
|
|
|
- }
|
|
|
|
|
|
+ // diff(
|
|
|
|
+ // cached_set,
|
|
|
|
+ // change_list,
|
|
|
|
+ // registry,
|
|
|
|
+ // template,
|
|
|
|
+ // node,
|
|
|
|
+ // cached_roots,
|
|
|
|
+ // );
|
|
|
|
+
|
|
|
|
+ // if let Some(old) = old_forcing {
|
|
|
|
+ // change_list.pop_force_new_listeners(old);
|
|
|
|
+ // }
|
|
|
|
+
|
|
|
|
+ // // Make sure that we come back up to the level we were at originally.
|
|
|
|
+ // change_list.commit_traversal();
|
|
|
|
+ // }
|
|
}
|
|
}
|