+ })
+};
diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml
index 183ddb790..2e210dc64 100644
--- a/packages/core/Cargo.toml
+++ b/packages/core/Cargo.toml
@@ -29,3 +29,5 @@ typed-arena = "2.0.1"
toolshed = "0.8.1"
id-arena = "2.2.1"
thiserror = "1.0.23"
+fxhash = "0.2.1"
+longest-increasing-subsequence = "0.1.0"
diff --git a/packages/core/src/changelist.rs b/packages/core/src/changelist.rs
index 831be1ab9..382d49422 100644
--- a/packages/core/src/changelist.rs
+++ b/packages/core/src/changelist.rs
@@ -21,50 +21,50 @@
use crate::innerlude::{Listener, VirtualDom};
-/// Renderers need to implement the interpreter trait
+/// The `Edit` represents a single modifcation of the renderer tree.
///
///
///
///
///
-trait Inrerpreter {
- fn set_text(&mut self, text: &str);
- fn remove_self_and_next_siblings(&mut self);
- fn replace_with(&mut self);
- fn set_attribute(&mut self, name: &str, value: &str);
- fn remove_attribute(&mut self, name: &str);
- fn push_reverse_child(&mut self, n: u32);
- fn pop_push_child(&mut self, n: u32);
- fn pop(&mut self);
- fn append_child(&mut self);
- fn create_text_node(&mut self, text: &str);
- fn create_element(&mut self, tag_name: &str);
- fn new_event_listener(&mut self, event_type: &str, a: u32, b: u32);
- fn update_event_listener(&mut self, event_type: &str, a: u32, b: u32);
- fn remove_event_listener(&mut self, event_type: &str);
- fn create_element_ns(&mut self, tag_name: &str, ns: &str);
- fn save_children_to_temporaries(&mut self, temp: u32, start: u32, end: u32);
- // fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32);
- fn push_child(&mut self, n: u32);
- fn push_temporary(&mut self, temp: u32);
- fn insert_before(&mut self);
- fn pop_push_reverse_child(&mut self, n: u32);
- fn remove_child(&mut self, n: u32);
- fn set_class(&mut self, class_name: &str);
- // fn save_template(&mut self, id: CacheId);
- // fn push_template(&mut self, id: CacheId);
+///
+///
+///
+///
+pub enum Edit<'d> {
+ SetText { text: &'d str },
+ RemoveSelfAndNextSiblings {},
+ ReplaceWith,
+ SetAttribute { name: &'d str, value: &'d str },
+ RemoveAttribute { name: &'d str },
+ PushReverseChild { n: u32 },
+ PopPushChild { n: u32 },
+ Pop,
+ AppendChild,
+ CreateTextNode { text: &'d str },
+ CreateElement { tag_name: &'d str },
+ NewEventListener { event_type: &'d str, a: u32, b: u32 },
+ UpdateEventListener { event_type: &'d str, a: u32, b: u32 },
+ RemoveEventListener { event_type: &'d str },
+ CreateElementNs { tag_name: &'d str, ns: &'d str },
+ SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 },
+ PushChild { n: u32 },
+ PushTemporary { temp: u32 },
+ InsertBefore,
+ PopPushReverseChild { n: u32 },
+ RemoveChild { n: u32 },
+ SetClass { class_name: &'d str },
}
-pub struct ChangeList<'src> {
+pub struct EditList<'src> {
traversal: Traversal,
next_temporary: u32,
forcing_new_listeners: bool,
- emitter: &'src mut dyn Inrerpreter,
- domlock: &'src VirtualDom,
+ emitter: Vec
>,
}
/// Traversal methods.
-impl ChangeList<'_> {
+impl EditList<'_> {
pub fn go_down(&mut self) {
self.traversal.down();
}
@@ -102,32 +102,36 @@ impl ChangeList<'_> {
}
for mv in self.traversal.commit() {
- // match mv {
- // MoveTo::Parent => {
- // // debug!("emit: pop");
- // self.emitter.pop();
- // }
- // MoveTo::Child(n) => {
- // // debug!("emit: push_child({})", n);
- // self.emitter.push_child(n);
- // }
- // MoveTo::ReverseChild(n) => {
- // // debug!("emit: push_reverse_child({})", n);
- // self.emitter.push_reverse_child(n);
- // }
- // MoveTo::Sibling(n) => {
- // // debug!("emit: pop_push_child({})", n);
- // self.emitter.pop_push_child(n);
- // }
- // MoveTo::ReverseSibling(n) => {
- // // debug!("emit: pop_push_reverse_child({})", n);
- // self.emitter.pop_push_reverse_child(n);
- // }
- // MoveTo::TempChild(temp) => {
- // // debug!("emit: push_temporary({})", temp);
- // self.emitter.push_temporary(temp);
- // }
- // }
+ match mv {
+ MoveTo::Parent => {
+ // debug!("emit: pop");
+ self.emitter.push(Edit::Pop {});
+ // self.emitter.pop();
+ }
+ MoveTo::Child(n) => {
+ // debug!("emit: push_child({})", n);
+ self.emitter.push(Edit::PushChild { n });
+ }
+ MoveTo::ReverseChild(n) => {
+ // debug!("emit: push_reverse_child({})", n);
+ self.emitter.push(Edit::PushReverseChild { n });
+ // self.emitter.push_reverse_child(n);
+ }
+ MoveTo::Sibling(n) => {
+ // debug!("emit: pop_push_child({})", n);
+ self.emitter.push(Edit::PopPushChild { n });
+ // self.emitter.pop_push_child(n);
+ }
+ MoveTo::ReverseSibling(n) => {
+ // debug!("emit: pop_push_reverse_child({})", n);
+ self.emitter.push(Edit::PopPushReverseChild { n });
+ }
+ MoveTo::TempChild(temp) => {
+ // debug!("emit: push_temporary({})", temp);
+ self.emitter.push(Edit::PushTemporary { temp });
+ // self.emitter.push_temporary(temp);
+ }
+ }
}
}
@@ -136,7 +140,7 @@ impl ChangeList<'_> {
}
}
-impl ChangeList<'_> {
+impl<'a> EditList<'a> {
pub fn next_temporary(&self) -> u32 {
self.next_temporary
}
@@ -154,27 +158,33 @@ impl ChangeList<'_> {
// temp_base, start, end
// );
self.next_temporary = temp_base + (end - start) as u32;
- self.emitter
- .save_children_to_temporaries(temp_base, start as u32, end as u32);
+ self.emitter.push(Edit::SaveChildrenToTemporaries {
+ temp: temp_base,
+ start: start as u32,
+ end: end as u32,
+ });
temp_base
}
pub fn push_temporary(&mut self, temp: u32) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: push_temporary({})", temp);
- self.emitter.push_temporary(temp);
+ self.emitter.push(Edit::PushTemporary { temp });
+ // self.emitter.push_temporary(temp);
}
pub fn remove_child(&mut self, child: usize) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_child({})", child);
- self.emitter.remove_child(child as u32);
+ // self.emitter.remove_child(child as u32);
+ self.emitter.push(Edit::RemoveChild { n: child as u32 })
}
pub fn insert_before(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: insert_before()");
- self.emitter.insert_before();
+ // self.emitter.insert_before();
+ self.emitter.push(Edit::InsertBefore {})
}
pub fn ensure_string(&mut self, string: &str) -> StringKey {
@@ -182,45 +192,52 @@ impl ChangeList<'_> {
// self.strings.ensure_string(string, &self.emitter)
}
- pub fn set_text(&mut self, text: &str) {
+ pub fn set_text(&mut self, text: &'a str) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: set_text({:?})", text);
- self.emitter.set_text(text);
+ // self.emitter.set_text(text);
+ self.emitter.push(Edit::SetText { text });
// .set_text(text.as_ptr() as u32, text.len() as u32);
}
pub fn remove_self_and_next_siblings(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_self_and_next_siblings()");
- self.emitter.remove_self_and_next_siblings();
+ self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
+ // self.emitter.remove_self_and_next_siblings();
}
pub fn replace_with(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: replace_with()");
- self.emitter.replace_with();
+ self.emitter.push(Edit::ReplaceWith {});
+ // self.emitter.replace_with();
}
- pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
+ pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
debug_assert!(self.traversal_is_committed());
- todo!()
- // if name == "class" && !is_namespaced {
- // let class_id = self.ensure_string(value);
- // // debug!("emit: set_class({:?})", value);
- // self.emitter.set_class(class_id.into());
- // } else {
- // let name_id = self.ensure_string(name);
- // let value_id = self.ensure_string(value);
- // // debug!("emit: set_attribute({:?}, {:?})", name, value);
- // self.state
- // .emitter
- // .set_attribute(name_id.into(), value_id.into());
- // }
+ // todo!()
+ if name == "class" && !is_namespaced {
+ // let class_id = self.ensure_string(value);
+ // let class_id = self.ensure_string(value);
+ // debug!("emit: set_class({:?})", value);
+ // self.emitter.set_class(class_id.into());
+ self.emitter.push(Edit::SetClass { class_name: value });
+ } else {
+ self.emitter.push(Edit::SetAttribute { name, value });
+ // let name_id = self.ensure_string(name);
+ // let value_id = self.ensure_string(value);
+ // debug!("emit: set_attribute({:?}, {:?})", name, value);
+ // self.state
+ // .emitter
+ // .set_attribute(name_id.into(), value_id.into());
+ }
}
- pub fn remove_attribute(&mut self, name: &str) {
+ pub fn remove_attribute(&mut self, name: &'a str) {
// todo!("figure out how to get this working with ensure string");
- self.emitter.remove_attribute(name);
+ self.emitter.push(Edit::RemoveAttribute { name });
+ // self.emitter.remove_attribute(name);
// debug_assert!(self.traversal_is_committed());
// // debug!("emit: remove_attribute({:?})", name);
// let name_id = self.ensure_string(name);
@@ -230,29 +247,33 @@ impl ChangeList<'_> {
pub fn append_child(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: append_child()");
- self.emitter.append_child();
+ self.emitter.push(Edit::AppendChild {});
+ // self.emitter.append_child();
}
- pub fn create_text_node(&mut self, text: &str) {
+ pub fn create_text_node(&mut self, text: &'a str) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: create_text_node({:?})", text);
- self.emitter.create_text_node(text);
+ // self.emitter.create_text_node(text);
+ self.emitter.push(Edit::CreateTextNode { text });
}
- pub fn create_element(&mut self, tag_name: &str) {
+ pub fn create_element(&mut self, tag_name: &'a str) {
// debug_assert!(self.traversal_is_committed());
// debug!("emit: create_element({:?})", tag_name);
// let tag_name_id = self.ensure_string(tag_name);
- self.emitter.create_element(tag_name);
+ self.emitter.push(Edit::CreateElement { tag_name });
+ // self.emitter.create_element(tag_name);
// self.emitter.create_element(tag_name_id.into());
}
- pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
+ pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
// let tag_name_id = self.ensure_string(tag_name);
// let ns_id = self.ensure_string(ns);
- self.emitter.create_element_ns(tag_name, ns);
+ // self.emitter.create_element_ns(tag_name, ns);
+ self.emitter.push(Edit::CreateElementNs { tag_name, ns });
// self.emitter
// .create_element_ns(tag_name_id.into(), ns_id.into());
}
@@ -455,7 +476,9 @@ impl Traversal {
/// that have *not* been committed yet?
#[inline]
pub fn is_committed(&self) -> bool {
- self.uncommitted.is_empty()
+ // is_empty is not inlined?
+ // self.uncommitted.is_empty()
+ self.uncommitted.len() == 0
}
/// Commit this traversals moves and return the optimized path from the last
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index 1dcf2348c..648ffb692 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -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.
+///
+/// ----
+///
+/// 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;
-// 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,
-) {
- 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 {}
- }
+use crate::{
+ changelist::EditList,
+ innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
+ virtual_dom::LifecycleEvent,
+};
- // 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(_)) => {}
+use std::cmp::Ordering;
- (VNode::Component(_), VNode::Element(_)) => {}
- (VNode::Component(_), VNode::Text(_)) => {}
-
- // 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);
- // }
- // }
-
- // (&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();
- // }
-
- // (&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();
- // }
-
- // (
- // &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,
- // );
- // }
-
- // // 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,
- // );
- // }
- // }
-
- // // 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,
- // );
- // }
-
- // // 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();
- // }
- // }
+/// 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,
}
-#[cfg(predicate)]
-mod todo_diff {
+enum NeedToDiff {
+ PropsChanged,
+ Subscription,
+}
+
+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);
+ }
+ }
+
+ // 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();
+ }
+
+ // Definitely different, need to commit update
+ (VNode::Element(_), VNode::Text(_)) => {
+ self.change_list.commit_traversal();
+ self.create(new);
+
+ // 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;
+ }
+
+ self.diff_listeners(eold.listeners, enew.listeners);
+
+ self.diff_attr(eold.attributes, enew.attributes, enew.namespace.is_some());
+
+ 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
+ }
+
+ // 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();
+ // }
+ // }
+ }
+
+ // 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");
+ }
+
+ // 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`.
//
@@ -189,14 +217,9 @@ mod todo_diff {
// [... node]
//
// 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() {
- change_list.commit_traversal();
+ self.change_list.commit_traversal();
}
'outer1: for new_l in new {
@@ -204,28 +227,28 @@ mod todo_diff {
// Safety relies on removing `new_l` from the registry manually at
// the end of its lifetime. This happens below in the `'outer2`
// loop, and elsewhere in diffing when removing old dom trees.
- registry.add(new_l);
+ // registry.add(new_l);
}
for old_l in old {
if new_l.event == old_l.event {
- change_list.update_event_listener(new_l);
+ self.change_list.update_event_listener(new_l);
continue 'outer1;
}
}
- change_list.new_event_listener(new_l);
+ self.change_list.new_event_listener(new_l);
}
'outer2: for old_l in old {
- registry.remove(old_l);
+ // registry.remove(old_l);
for new_l in new {
if new_l.event == old_l.event {
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]
//
// 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,
) {
// Do O(n^2) passes to add/update and remove attributes, since
// there are almost always very few attributes.
'outer: for new_attr in new {
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 {
for old_attr in old {
if old_attr.name == new_attr.name {
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;
}
}
- 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]
//
// 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,
- ) {
+ fn diff_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
if new.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;
}
@@ -303,36 +325,29 @@ mod todo_diff {
if new.len() == 1 {
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 => {
// 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;
}
+
(_, _) => {}
}
}
if old.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;
}
@@ -350,11 +365,14 @@ mod todo_diff {
);
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 {
- 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]
//
// 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,
- ) {
+ fn diff_keyed_children(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) {
+ // let DiffState { change_list, queue } = &*state;
+
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();
for child in children {
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
// `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
// 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
// within which to re-order nodes with the same keys, remove old nodes with
// 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],
&new[shared_prefix_count..new_shared_suffix_start],
- cached_roots,
shared_prefix_count,
shared_suffix_count,
old_shared_suffix_start,
@@ -457,27 +470,10 @@ mod todo_diff {
let new_suffix = &new[new_shared_suffix_start..];
debug_assert_eq!(old_suffix.len(), new_suffix.len());
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
// the same order.
//
@@ -486,15 +482,8 @@ mod todo_diff {
// [... parent]
//
// 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,
- ) -> 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;
for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
@@ -502,36 +491,32 @@ mod todo_diff {
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;
}
// If that was all of the old children, then create and append the remaining
// new children and we're finished.
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;
}
// And if that was all of the new children, then remove all of the remaining
// old children and we're finished.
if shared_prefix_count == new.len() {
- 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;
}
- change_list.go_up();
+ self.change_list.go_up();
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.
fn diff_keyed_middle(
- cached_set: &CachedSet,
- change_list: &mut ChangeListBuilder,
- registry: &mut EventsRegistry,
- old: &[Node],
- mut new: &[Node],
- cached_roots: &mut FxHashSet,
+ &mut self,
+ old: &[VNode<'a>],
+ mut new: &[VNode<'a>],
shared_prefix_count: usize,
shared_suffix_count: usize,
old_shared_suffix_start: usize,
@@ -595,14 +577,16 @@ mod todo_diff {
// afresh.
if shared_suffix_count == 0 && shared_keys.is_empty() {
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 {
- 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;
}
@@ -619,8 +603,8 @@ mod todo_diff {
.unwrap_or(old.len());
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 + end,
);
@@ -643,9 +627,10 @@ mod todo_diff {
let mut removed_count = 0;
for (i, old_child) in old.iter().enumerate().rev() {
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;
}
}
@@ -688,47 +673,45 @@ mod todo_diff {
// shared suffix to the change list stack.
//
// [... 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]
} else {
// There is no shared suffix coming after these middle children.
// 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.
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];
if shared_keys.contains(&last.key()) {
let old_index = new_index_to_old_index[last_index];
let temp = old_index_to_temp[old_index];
// [... parent]
- change_list.go_down_to_temp_child(temp);
+ self.change_list.go_down_to_temp_child(temp);
// [... 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) {
// Don't move it, since it is already where it needs to be.
} else {
- change_list.commit_traversal();
+ self.change_list.commit_traversal();
// [... parent last]
- change_list.append_child();
+ self.change_list.append_child();
// [... parent]
- change_list.go_down_to_temp_child(temp);
+ self.change_list.go_down_to_temp_child(temp);
// [... parent last]
}
} else {
- change_list.commit_traversal();
+ self.change_list.commit_traversal();
// [... parent]
- create(cached_set, change_list, registry, last, cached_roots);
+ self.create(last);
+
// [... parent last]
- change_list.append_child();
+ self.change_list.append_child();
// [... parent]
- change_list.go_down_to_reverse_child(0);
+ self.change_list.go_down_to_reverse_child(0);
// [... parent last]
}
}
@@ -737,11 +720,11 @@ mod todo_diff {
let old_index = new_index_to_old_index[new_index];
if old_index == u32::MAX as usize {
debug_assert!(!shared_keys.contains(&new_child.key()));
- change_list.commit_traversal();
+ self.change_list.commit_traversal();
// [... parent successor]
- create(cached_set, change_list, registry, new_child, cached_roots);
+ self.create(new_child);
// [... parent successor new_child]
- change_list.insert_before();
+ self.change_list.insert_before();
// [... parent new_child]
} else {
debug_assert!(shared_keys.contains(&new_child.key()));
@@ -750,30 +733,23 @@ mod todo_diff {
if new_index_is_in_lis.contains(&new_index) {
// [... parent successor]
- change_list.go_to_temp_sibling(temp);
+ self.change_list.go_to_temp_sibling(temp);
// [... parent new_child]
} else {
- change_list.commit_traversal();
+ self.change_list.commit_traversal();
// [... parent successor]
- change_list.push_temporary(temp);
+ self.change_list.push_temporary(temp);
// [... parent successor new_child]
- change_list.insert_before();
+ self.change_list.insert_before();
// [... 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]
- change_list.go_up();
+ self.change_list.go_up();
// [... parent]
}
@@ -785,35 +761,26 @@ mod todo_diff {
//
// When this function exits, the change list stack remains the same.
fn diff_keyed_suffix(
- cached_set: &CachedSet,
- change_list: &mut ChangeListBuilder,
- registry: &mut EventsRegistry,
- old: &[Node],
- new: &[Node],
- cached_roots: &mut FxHashSet,
+ &mut self,
+ old: &[VNode<'a>],
+ new: &[VNode<'a>],
new_shared_suffix_start: usize,
) {
debug_assert_eq!(old.len(), new.len());
debug_assert!(!old.is_empty());
// [... parent]
- change_list.go_down();
+ self.change_list.go_down();
// [... parent new_child]
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]
- change_list.go_up();
+ self.change_list.go_up();
}
// Diff children that are not keyed.
@@ -824,84 +791,136 @@ mod todo_diff {
// [... parent]
//
// 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,
- ) {
+ 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.
debug_assert!(!new.is_empty());
debug_assert!(!old.is_empty());
// [... parent]
- change_list.go_down();
+ self.change_list.go_down();
// [... parent child]
for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
// [... parent prev_child]
- change_list.go_to_sibling(i);
+ self.change_list.go_to_sibling(i);
// [... 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()) {
Ordering::Greater => {
// [... parent prev_child]
- change_list.go_to_sibling(new.len());
+ self.change_list.go_to_sibling(new.len());
// [... 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]
}
Ordering::Less => {
// [... parent last_child]
- change_list.go_up();
+ self.change_list.go_up();
// [... 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 => {
// [... parent child]
- change_list.go_up();
+ self.change_list.go_up();
// [... parent]
}
}
}
- // Create the given children and append them to the parent node.
+ // ======================
+ // Support methods
+ // ======================
+
+ // Emit instructions to create the given virtual node.
//
- // The parent node must currently be on top of the change list stack:
+ // The change list stack may have any shape upon entering this function:
//
- // [... 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,
- ) {
- debug_assert!(change_list.traversal_is_committed());
- for child in new {
- create(cached_set, change_list, registry, child, cached_roots);
- change_list.append_child();
+ // When this function returns, the new node is on top of the change list stack:
+ //
+ // [... node]
+ 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);
+ }
+ VNode::Element(&VElement {
+ key: _,
+ tag_name,
+ listeners,
+ attributes,
+ children,
+ namespace,
+ }) => {
+ if let Some(namespace) = namespace {
+ self.change_list.create_element_ns(tag_name, namespace);
+ } else {
+ self.change_list.create_element(tag_name);
+ }
+
+ for l in listeners {
+ // unsafe {
+ // registry.add(l);
+ // }
+ self.change_list.new_event_listener(l);
+ }
+
+ for attr in attributes {
+ 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
+ // create-and-append the text node all at once via setting the
+ // parent's `textContent` in a single change list instruction than
+ // to emit three instructions to (1) create a text node, (2) set its
+ // text content, and finally (3) append the text node to this
+ // parent.
+ if children.len() == 1 {
+ if let VNode::Text(VText { text }) = children[0] {
+ self.change_list.set_text(text);
+ return;
+ }
+ }
+
+ for child in children {
+ self.create(child);
+ self.change_list.append_child();
+ }
+ }
+
+ /*
+ 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")
+ }
}
}
@@ -912,18 +931,29 @@ mod todo_diff {
// [... 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());
+ 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);
+ // registry.remove_subtree(child);
}
// Fast way to remove all children: set the node's textContent to an empty
// string.
- change_list.set_text("");
+ self.change_list.set_text("");
+ }
+
+ // 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();
+ }
}
// Remove the current child and all of its following siblings.
@@ -935,234 +965,156 @@ mod todo_diff {
// 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());
+ 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);
+ // registry.remove_subtree(child);
}
- change_list.remove_self_and_next_siblings();
- }
-
- // Emit instructions to create the given virtual node.
- //
- // The change list stack may have any shape upon entering this function:
- //
- // [...]
- //
- // When this function returns, the new node is on top of the change list stack:
- //
- // [... node]
- fn create(
- cached_set: &CachedSet,
- change_list: &mut ChangeListBuilder,
- registry: &mut EventsRegistry,
- node: &Node,
- cached_roots: &mut FxHashSet,
- ) {
- debug_assert!(change_list.traversal_is_committed());
- match node.kind {
- NodeKind::Text(TextNode { text }) => {
- change_list.create_text_node(text);
- }
- NodeKind::Element(&ElementNode {
- key: _,
- tag_name,
- listeners,
- attributes,
- children,
- namespace,
- }) => {
- if let Some(namespace) = namespace {
- change_list.create_element_ns(tag_name, namespace);
- } else {
- change_list.create_element(tag_name);
- }
-
- for l in listeners {
- unsafe {
- registry.add(l);
- }
- change_list.new_event_listener(l);
- }
-
- for attr in attributes {
- change_list.set_attribute(&attr.name, &attr.value, namespace.is_some());
- }
-
- // Fast path: if there is a single text child, it is faster to
- // create-and-append the text node all at once via setting the
- // parent's `textContent` in a single change list instruction than
- // to emit three instructions to (1) create a text node, (2) set its
- // text content, and finally (3) append the text node to this
- // parent.
- if children.len() == 1 {
- if let Node {
- kind: NodeKind::Text(TextNode { text }),
- } = children[0]
- {
- change_list.set_text(text);
- return;
- }
- }
-
- for child in children {
- create(cached_set, change_list, registry, child, cached_roots);
- 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);
- }
- }
- }
- }
-
- // 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]
- fn get_or_create_template<'a>(
- cached_set: &'a CachedSet,
- change_list: &mut ChangeListBuilder,
- registry: &mut EventsRegistry,
- cached_roots: &mut FxHashSet,
- 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)
- }
- }
-
- fn create_and_replace(
- cached_set: &CachedSet,
- change_list: &mut ChangeListBuilder,
- registry: &mut EventsRegistry,
- new_template: Option,
- old: &Node,
- new: &Node,
- cached_roots: &mut FxHashSet,
- ) {
- 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());
- }
-
- diff(
- cached_set,
- change_list,
- registry,
- template,
- new,
- cached_roots,
- );
-
- if let Some(old) = old_forcing {
- change_list.pop_force_new_listeners(old);
- }
-
- change_list.commit_traversal();
- } else {
- create(cached_set, change_list, registry, new, cached_roots);
- change_list.replace_with();
- }
- registry.remove_subtree(old);
- }
-
- fn create_with_template(
- cached_set: &CachedSet,
- change_list: &mut ChangeListBuilder,
- registry: &mut EventsRegistry,
- template_id: CacheId,
- node: &Node,
- cached_roots: &mut FxHashSet,
- ) {
- 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());
- }
-
- 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();
+ self.change_list.remove_self_and_next_siblings();
}
}
+
+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),
+}
+
+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,
+ // // 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)
+ // }
+ // }
+
+ // pub fn create_and_replace(
+ // cached_set: &CachedSet,
+ // change_list: &mut ChangeListBuilder,
+ // registry: &mut EventsRegistry,
+ // new_template: Option,
+ // old: &Node,
+ // new: &Node,
+ // cached_roots: &mut FxHashSet,
+ // ) {
+ // 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());
+ // }
+
+ // diff(
+ // cached_set,
+ // change_list,
+ // registry,
+ // template,
+ // new,
+ // cached_roots,
+ // );
+
+ // if let Some(old) = old_forcing {
+ // change_list.pop_force_new_listeners(old);
+ // }
+
+ // 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_with_template(
+ // cached_set: &CachedSet,
+ // change_list: &mut ChangeListBuilder,
+ // registry: &mut EventsRegistry,
+ // template_id: CacheId,
+ // node: &Node,
+ // cached_roots: &mut FxHashSet,
+ // ) {
+ // 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());
+ // }
+
+ // 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();
+ // }
+}
diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs
index 0537b95fa..123a63634 100644
--- a/packages/core/src/lib.rs
+++ b/packages/core/src/lib.rs
@@ -65,19 +65,19 @@
//! - dioxus-liveview (SSR + StringRenderer)
//!
-pub mod changelist;
-pub mod component;
-pub mod context;
-pub mod debug_renderer;
-pub mod diff;
-pub mod error;
-pub mod events;
-pub mod hooks;
-pub mod nodebuilder;
-pub mod nodes;
-pub mod scope;
-pub mod validation;
-pub mod virtual_dom;
+pub mod changelist; // An "edit phase" described by transitions and edit operations
+pub mod component; // Logic for extending FC
+pub mod context; // Logic for providing hook + context functionality to user components
+pub mod debug_renderer; // Test harness for validating that lifecycles and diffs work appropriately
+pub mod diff; // The diffing algorithm that builds the ChangeList
+pub mod error; // Error type we expose to the renderers
+pub mod events; // Manages the synthetic event API
+pub mod hooks; // Built-in hooks
+pub mod nodebuilder; // Logic for building VNodes with a direct syntax
+pub mod nodes; // Logic for the VNodes
+pub mod scope; // Logic for single components
+pub mod validation; // Logic for validating trees
+pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
pub mod builder {
pub use super::nodebuilder::*;
diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs
index ba71d5f96..acdb82d70 100644
--- a/packages/core/src/scope.rs
+++ b/packages/core/src/scope.rs
@@ -1,11 +1,11 @@
+use crate::context::hooks::Hook;
use crate::innerlude::*;
use crate::nodes::VNode;
-use crate::{context::hooks::Hook, diff::diff};
use bumpalo::Bump;
use generational_arena::Index;
use std::{
any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
- sync::atomic::AtomicUsize,
+ sync::atomic::AtomicUsize, todo,
};
/// Every component in Dioxus is represented by a `Scope`.
@@ -29,9 +29,13 @@ pub struct Scope {
pub frames: [Bump; 2],
// somehow build this vnode with a lifetime tied to self
- pub cur_node: *mut VNode<'static>,
+ // This root node has "static" lifetime, but it's really not static.
+ // It's goverened by the oldest of the two frames and is switched every time a new render occurs
+ // Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
+ // ! do not copy this reference are things WILL break !
+ pub root_node: *mut VNode<'static>,
- pub active_frame: u8,
+ pub active_frame: ActiveFrame,
// IE Which listeners need to be woken up?
pub listeners: Vec>,
@@ -41,6 +45,20 @@ pub struct Scope {
pub caller: *const i32,
}
+pub enum ActiveFrame {
+ First,
+ Second,
+}
+
+impl ActiveFrame {
+ fn next(&mut self) {
+ match self {
+ ActiveFrame::First => *self = ActiveFrame::Second,
+ ActiveFrame::Second => *self = ActiveFrame::First,
+ }
+ }
+}
+
impl Scope {
// create a new scope from a function
pub(crate) fn new(f: FC, parent: Option) -> Self {
@@ -49,15 +67,19 @@ impl Scope {
let hook_arena = typed_arena::Arena::new();
let hooks = RefCell::new(Vec::new());
+ // Capture the caller
let caller = f as *const i32;
+ // Create the two buffers the componetn will render into
+ // There will always be an "old" and "new"
let frames = [Bump::new(), Bump::new()];
let listeners = Vec::new();
- let active_frame = 1;
+ let active_frame = ActiveFrame::First;
let new = frames[0].alloc(VNode::Text(VText::new("")));
+
let cur_node = new as *mut _;
Self {
@@ -69,7 +91,7 @@ impl Scope {
listeners,
parent,
frames,
- cur_node,
+ root_node: cur_node,
}
}
@@ -78,7 +100,12 @@ impl Scope {
///
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
- let bump = &self.frames[0];
+ let bump = match self.active_frame {
+ // If the active frame is the first, then we need to bump into the second
+ ActiveFrame::First => &self.frames[1],
+ // If the active frame is the second, then we need to bump into the first
+ ActiveFrame::Second => &self.frames[0],
+ }; // n.b, there might be a better way of doing this active frame stuff - perhaps swapping
let ctx = Context {
scope: &*self,
@@ -102,13 +129,14 @@ impl Scope {
*/
let caller = unsafe { std::mem::transmute::<*const i32, FC>(self.caller) };
let new_nodes = caller(ctx, props);
- let old_nodes: &mut VNode<'static> = unsafe { &mut *self.cur_node };
+ let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
// TODO: Iterate through the new nodes
// move any listeners into ourself
// perform the diff, dumping into the mutable change list
// this doesnt perform any "diff compression" where an event and a re-render
- crate::diff::diff(old_nodes, &new_nodes);
+ // crate::diff::diff(old_nodes, &new_nodes);
+ todo!()
}
}
diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs
index e5120a9a7..fb51d4254 100644
--- a/packages/core/src/virtual_dom.rs
+++ b/packages/core/src/virtual_dom.rs
@@ -1,4 +1,4 @@
-use crate::{changelist::ChangeList, nodes::VNode};
+use crate::{changelist::EditList, nodes::VNode};
use crate::{events::EventTrigger, innerlude::*};
use any::Any;
use bumpalo::Bump;
@@ -23,10 +23,6 @@ pub struct VirtualDom {
/// The index of the root component.
base_scope: Index,
- ///
- ///
- ///
- ///
event_queue: Rc>>,
// Mark the root props with P, even though they're held by the root component
@@ -45,10 +41,7 @@ impl VirtualDom {
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
-}
-impl VirtualDom {
- // impl VirtualDom {
/// Start a new VirtualDom instance with a dependent props.
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
///
@@ -128,7 +121,7 @@ impl VirtualDom {
///
///
/// ```
- pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result> {
+ pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result> {
let EventTrigger {
component_id,
listener_id,
diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs
index 748bbf857..c8c55635a 100644
--- a/packages/web/src/lib.rs
+++ b/packages/web/src/lib.rs
@@ -2,7 +2,7 @@
//!
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser.
//!
-//! While `VNode` supports "to_string" directly, it renders child components as the RSX! macro tokens. For custom components,
+//! While it is possible to render a single component directly, it is not possible to render component trees. For these,
//! an external renderer is needed to progress the component lifecycles. The `WebsysRenderer` shows how to use the Virtual DOM
//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
//! `render` method.
@@ -16,7 +16,6 @@
//! ```
//!
//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
-//!
use dioxus_core::{
events::EventTrigger,
@@ -33,7 +32,6 @@ pub struct WebsysRenderer {
internal_dom: VirtualDom,
}
-/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
impl WebsysRenderer {
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
///
@@ -42,9 +40,6 @@ impl WebsysRenderer {
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
-}
-
-impl WebsysRenderer {
/// Create a new text-renderer instance from a functional component root.
/// Automatically progresses the creation of the VNode tree to completion.
///