fix: switch to future based diffing
this commit removes the old manual fiber implementation in favor of a traditional recursion based approach. This should make the diffing algorithm easier to work on and eliminate various stack-based bugs in.
This commit is contained in:
1 parent
3edf3e367f
commit
3bb5c8142c
9 files changed
+1268
-322
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
struct_lit_width = 80
|
||||||
@@ -222,7 +222,7 @@ impl<'bump> DiffStack<'bump> {
|
|||||||
self.nodes_created_stack.pop().unwrap()
|
self.nodes_created_stack.pop().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_scope(&self) -> Option<ScopeId> {
|
pub fn current_scope(&self) -> Option<ScopeId> {
|
||||||
self.scope_stack.last().copied()
|
self.scope_stack.last().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,9 +314,10 @@ impl<'bump> DiffState<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MountType::Append => {
|
MountType::Append => {
|
||||||
self.mutations.edits.push(AppendChildren {
|
self.mutations.append_children(nodes_created as u32);
|
||||||
many: nodes_created as u32,
|
// self.mutations.edits.push(AppendChildren {
|
||||||
});
|
// many: nodes_created as u32,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
MountType::InsertAfter { other_node } => {
|
MountType::InsertAfter { other_node } => {
|
||||||
@@ -470,7 +471,8 @@ impl<'bump> DiffState<'bump> {
|
|||||||
self.stack.create_component(new_idx, nextnode);
|
self.stack.create_component(new_idx, nextnode);
|
||||||
|
|
||||||
// Finally, insert this scope as a seen node.
|
// Finally, insert this scope as a seen node.
|
||||||
self.mutations.dirty_scopes.insert(new_idx);
|
self.mutations.mark_dirty_scope(new_idx);
|
||||||
|
// self.mutations.dirty_scopes.insert(new_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================================
|
// =================================
|
||||||
@@ -639,9 +641,10 @@ impl<'bump> DiffState<'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if old.children.is_empty() && !new.children.is_empty() {
|
if old.children.is_empty() && !new.children.is_empty() {
|
||||||
self.mutations.edits.push(PushRoot {
|
self.mutations.push_root(root);
|
||||||
root: root.as_u64(),
|
// self.mutations.edits.push(PushRoot {
|
||||||
});
|
// root: root.as_u64(),
|
||||||
|
// });
|
||||||
self.stack.element_stack.push(root);
|
self.stack.element_stack.push(root);
|
||||||
self.stack.instructions.push(DiffInstruction::PopElement);
|
self.stack.instructions.push(DiffInstruction::PopElement);
|
||||||
self.stack.create_children(new.children, MountType::Append);
|
self.stack.create_children(new.children, MountType::Append);
|
||||||
@@ -771,7 +774,7 @@ impl<'bump> DiffState<'bump> {
|
|||||||
|
|
||||||
// this should auto drop the previous props
|
// this should auto drop the previous props
|
||||||
self.scopes.run_scope(scope_addr);
|
self.scopes.run_scope(scope_addr);
|
||||||
self.mutations.dirty_scopes.insert(scope_addr);
|
self.mutations.mark_dirty_scope(scope_addr);
|
||||||
|
|
||||||
self.diff_node(
|
self.diff_node(
|
||||||
self.scopes.wip_head(scope_addr),
|
self.scopes.wip_head(scope_addr),
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,8 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
pub(crate) mod diff;
|
// pub(crate) mod diff;
|
||||||
|
pub(crate) mod diff_async;
|
||||||
pub(crate) mod events;
|
pub(crate) mod events;
|
||||||
pub(crate) mod lazynodes;
|
pub(crate) mod lazynodes;
|
||||||
pub(crate) mod mutations;
|
pub(crate) mod mutations;
|
||||||
@@ -12,7 +13,8 @@ pub(crate) mod util;
|
|||||||
pub(crate) mod virtual_dom;
|
pub(crate) mod virtual_dom;
|
||||||
|
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
pub(crate) use crate::diff::*;
|
pub(crate) use crate::diff_async::*;
|
||||||
|
// pub(crate) use crate::diff::*;
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::lazynodes::*;
|
pub use crate::lazynodes::*;
|
||||||
pub use crate::mutations::*;
|
pub use crate::mutations::*;
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ impl<'a> Mutations<'a> {
|
|||||||
self.edits.push(InsertBefore { n, root });
|
self.edits.push(InsertBefore { n, root });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn append_children(&mut self, n: u32) {
|
||||||
|
self.edits.push(AppendChildren { many: n });
|
||||||
|
}
|
||||||
|
|
||||||
// Remove Nodes from the dom
|
// Remove Nodes from the dom
|
||||||
pub(crate) fn remove(&mut self, id: u64) {
|
pub(crate) fn remove(&mut self, id: u64) {
|
||||||
self.edits.push(Remove { root: id });
|
self.edits.push(Remove { root: id });
|
||||||
@@ -217,6 +221,10 @@ impl<'a> Mutations<'a> {
|
|||||||
let name = attribute.name;
|
let name = attribute.name;
|
||||||
self.edits.push(RemoveAttribute { name, root });
|
self.edits.push(RemoveAttribute { name, root });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
|
||||||
|
self.dirty_scopes.insert(scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// refs are only assigned once
|
// refs are only assigned once
|
||||||
|
|||||||
+18
-41
@@ -175,6 +175,7 @@ impl Debug for VNode<'_> {
|
|||||||
.field("key", &el.key)
|
.field("key", &el.key)
|
||||||
.field("attrs", &el.attributes)
|
.field("attrs", &el.attributes)
|
||||||
.field("children", &el.children)
|
.field("children", &el.children)
|
||||||
|
.field("id", &el.id)
|
||||||
.finish(),
|
.finish(),
|
||||||
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
|
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
|
||||||
VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id),
|
VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id),
|
||||||
@@ -365,9 +366,7 @@ pub struct EventHandler<'bump, T = ()> {
|
|||||||
|
|
||||||
impl<'a, T> Default for EventHandler<'a, T> {
|
impl<'a, T> Default for EventHandler<'a, T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self { callback: RefCell::new(None) }
|
||||||
callback: RefCell::new(None),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,10 +440,7 @@ pub struct NodeFactory<'a> {
|
|||||||
|
|
||||||
impl<'a> NodeFactory<'a> {
|
impl<'a> NodeFactory<'a> {
|
||||||
pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
|
pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
|
||||||
NodeFactory {
|
NodeFactory { scope, bump: &scope.wip_frame().bump }
|
||||||
scope,
|
|
||||||
bump: &scope.wip_frame().bump,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -454,11 +450,10 @@ impl<'a> NodeFactory<'a> {
|
|||||||
|
|
||||||
/// Directly pass in text blocks without the need to use the format_args macro.
|
/// Directly pass in text blocks without the need to use the format_args macro.
|
||||||
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
|
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
|
||||||
VNode::Text(self.bump.alloc(VText {
|
VNode::Text(
|
||||||
id: empty_cell(),
|
self.bump
|
||||||
text,
|
.alloc(VText { id: empty_cell(), text, is_static: true }),
|
||||||
is_static: true,
|
)
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
|
/// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
|
||||||
@@ -481,11 +476,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
||||||
let (text, is_static) = self.raw_text(args);
|
let (text, is_static) = self.raw_text(args);
|
||||||
|
|
||||||
VNode::Text(self.bump.alloc(VText {
|
VNode::Text(self.bump.alloc(VText { text, is_static, id: empty_cell() }))
|
||||||
text,
|
|
||||||
is_static,
|
|
||||||
id: empty_cell(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn element(
|
pub fn element(
|
||||||
@@ -543,13 +534,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
is_volatile: bool,
|
is_volatile: bool,
|
||||||
) -> Attribute<'a> {
|
) -> Attribute<'a> {
|
||||||
let (value, is_static) = self.raw_text(val);
|
let (value, is_static) = self.raw_text(val);
|
||||||
Attribute {
|
Attribute { name, value, is_static, namespace, is_volatile }
|
||||||
name,
|
|
||||||
value,
|
|
||||||
is_static,
|
|
||||||
namespace,
|
|
||||||
is_volatile,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn component<P>(
|
pub fn component<P>(
|
||||||
@@ -591,11 +576,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
|
pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
|
||||||
Listener {
|
Listener { event, mounted_node: Cell::new(None), callback }
|
||||||
event,
|
|
||||||
mounted_node: Cell::new(None),
|
|
||||||
callback,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fragment_root<'b, 'c>(
|
pub fn fragment_root<'b, 'c>(
|
||||||
@@ -611,10 +592,10 @@ impl<'a> NodeFactory<'a> {
|
|||||||
if nodes.is_empty() {
|
if nodes.is_empty() {
|
||||||
VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
|
VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
|
||||||
} else {
|
} else {
|
||||||
VNode::Fragment(self.bump.alloc(VFragment {
|
VNode::Fragment(
|
||||||
children: nodes.into_bump_slice(),
|
self.bump
|
||||||
key: None,
|
.alloc(VFragment { children: nodes.into_bump_slice(), key: None }),
|
||||||
}))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,10 +631,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
VNode::Fragment(self.bump.alloc(VFragment {
|
VNode::Fragment(self.bump.alloc(VFragment { children, key: None }))
|
||||||
children,
|
|
||||||
key: None,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,10 +654,9 @@ impl<'a> NodeFactory<'a> {
|
|||||||
} else {
|
} else {
|
||||||
let children = nodes.into_bump_slice();
|
let children = nodes.into_bump_slice();
|
||||||
|
|
||||||
Some(VNode::Fragment(self.bump.alloc(VFragment {
|
Some(VNode::Fragment(
|
||||||
children,
|
self.bump.alloc(VFragment { children, key: None }),
|
||||||
key: None,
|
))
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,8 @@ impl ScopeArena {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_garbage(&self, id: ElementId) {
|
pub fn collect_garbage(&self, id: ElementId) {
|
||||||
log::debug!("collecting garbage for {:?}", id);
|
// println!("collecting garbage for {:?}", id);
|
||||||
|
// log::debug!("collecting garbage for {:?}", id);
|
||||||
self.nodes.borrow_mut().remove(id.0);
|
self.nodes.borrow_mut().remove(id.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,11 +305,9 @@ impl ScopeArena {
|
|||||||
frame.node.set(unsafe { extend_vnode(node) });
|
frame.node.set(unsafe { extend_vnode(node) });
|
||||||
} else {
|
} else {
|
||||||
let frame = scope.wip_frame();
|
let frame = scope.wip_frame();
|
||||||
let node = frame
|
let node = frame.bump.alloc(VNode::Placeholder(
|
||||||
.bump
|
frame.bump.alloc(VPlaceholder { id: Default::default() }),
|
||||||
.alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
|
));
|
||||||
id: Default::default(),
|
|
||||||
})));
|
|
||||||
frame.node.set(unsafe { extend_vnode(node) });
|
frame.node.set(unsafe { extend_vnode(node) });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,10 +417,7 @@ pub struct Scope<'a, P = ()> {
|
|||||||
impl<P> Copy for Scope<'_, P> {}
|
impl<P> Copy for Scope<'_, P> {}
|
||||||
impl<P> Clone for Scope<'_, P> {
|
impl<P> Clone for Scope<'_, P> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self { scope: self.scope, props: self.props }
|
||||||
scope: self.scope,
|
|
||||||
props: self.props,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,10 +780,7 @@ impl ScopeState {
|
|||||||
/// }
|
/// }
|
||||||
///```
|
///```
|
||||||
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
|
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
|
||||||
Some(rsx.call(NodeFactory {
|
Some(rsx.call(NodeFactory { scope: self, bump: &self.wip_frame().bump }))
|
||||||
scope: self,
|
|
||||||
bump: &self.wip_frame().bump,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store a value between renders
|
/// Store a value between renders
|
||||||
@@ -894,10 +887,7 @@ impl ScopeState {
|
|||||||
self.shared_contexts.get_mut().clear();
|
self.shared_contexts.get_mut().clear();
|
||||||
|
|
||||||
// next: reset the node data
|
// next: reset the node data
|
||||||
let SelfReferentialItems {
|
let SelfReferentialItems { borrowed_props, listeners } = self.items.get_mut();
|
||||||
borrowed_props,
|
|
||||||
listeners,
|
|
||||||
} = self.items.get_mut();
|
|
||||||
borrowed_props.clear();
|
borrowed_props.clear();
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
self.frames[0].reset();
|
self.frames[0].reset();
|
||||||
@@ -955,11 +945,7 @@ pub(crate) struct TaskQueue {
|
|||||||
pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
|
pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
|
||||||
impl TaskQueue {
|
impl TaskQueue {
|
||||||
fn new(sender: UnboundedSender<SchedulerMsg>) -> Rc<Self> {
|
fn new(sender: UnboundedSender<SchedulerMsg>) -> Rc<Self> {
|
||||||
Rc::new(Self {
|
Rc::new(Self { tasks: RefCell::new(FxHashMap::default()), gen: Cell::new(0), sender })
|
||||||
tasks: RefCell::new(FxHashMap::default()),
|
|
||||||
gen: Cell::new(0),
|
|
||||||
sender,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn push_fut(&self, task: impl Future<Output = ()> + 'static) -> TaskId {
|
fn push_fut(&self, task: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
let pinned = Box::pin(task);
|
let pinned = Box::pin(task);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
||||||
|
|
||||||
|
use crate::diff_async::AsyncDiffState as DiffState;
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
use futures_util::{future::poll_fn, StreamExt};
|
use futures_util::{future::poll_fn, StreamExt};
|
||||||
@@ -453,7 +454,7 @@ impl VirtualDom {
|
|||||||
|
|
||||||
while !self.dirty_scopes.is_empty() {
|
while !self.dirty_scopes.is_empty() {
|
||||||
let scopes = &self.scopes;
|
let scopes = &self.scopes;
|
||||||
let mut diff_state = DiffState::new(scopes);
|
let mut diff_state = AsyncDiffState::new(scopes);
|
||||||
|
|
||||||
let mut ran_scopes = FxHashSet::default();
|
let mut ran_scopes = FxHashSet::default();
|
||||||
|
|
||||||
@@ -473,32 +474,33 @@ impl VirtualDom {
|
|||||||
|
|
||||||
self.scopes.run_scope(scopeid);
|
self.scopes.run_scope(scopeid);
|
||||||
|
|
||||||
let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
|
diff_state.diff_scope(scopeid);
|
||||||
diff_state.stack.push(DiffInstruction::Diff { new, old });
|
|
||||||
|
|
||||||
log::debug!("pushing scope {:?} onto scope stack", scopeid);
|
let AsyncDiffState { mutations, .. } = diff_state;
|
||||||
diff_state.stack.scope_stack.push(scopeid);
|
|
||||||
|
|
||||||
let scope = scopes.get_scope(scopeid).unwrap();
|
for scope in &mutations.dirty_scopes {
|
||||||
diff_state.stack.element_stack.push(scope.container);
|
self.dirty_scopes.remove(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
committed_mutations.push(mutations);
|
||||||
|
|
||||||
|
// if diff_state.work(&mut deadline) {
|
||||||
|
// let DiffState { mutations, .. } = diff_state;
|
||||||
|
|
||||||
|
// for scope in &mutations.dirty_scopes {
|
||||||
|
// self.dirty_scopes.remove(scope);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// committed_mutations.push(mutations);
|
||||||
|
// } else {
|
||||||
|
// // leave the work in an incomplete state
|
||||||
|
// //
|
||||||
|
// // todo: we should store the edits and re-apply them later
|
||||||
|
// // for now, we just dump the work completely (threadsafe)
|
||||||
|
// return committed_mutations;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if diff_state.work(&mut deadline) {
|
|
||||||
let DiffState { mutations, .. } = diff_state;
|
|
||||||
|
|
||||||
for scope in &mutations.dirty_scopes {
|
|
||||||
self.dirty_scopes.remove(scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
committed_mutations.push(mutations);
|
|
||||||
} else {
|
|
||||||
// leave the work in an incomplete state
|
|
||||||
//
|
|
||||||
// todo: we should store the edits and re-apply them later
|
|
||||||
// for now, we just dump the work completely (threadsafe)
|
|
||||||
return committed_mutations;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
committed_mutations
|
committed_mutations
|
||||||
@@ -526,13 +528,15 @@ impl VirtualDom {
|
|||||||
let mut diff_state = DiffState::new(&self.scopes);
|
let mut diff_state = DiffState::new(&self.scopes);
|
||||||
|
|
||||||
self.scopes.run_scope(scope_id);
|
self.scopes.run_scope(scope_id);
|
||||||
diff_state
|
|
||||||
.stack
|
|
||||||
.create_node(self.scopes.fin_head(scope_id), MountType::Append);
|
|
||||||
|
|
||||||
diff_state.stack.element_stack.push(ElementId(0));
|
diff_state.element_stack.push(ElementId(0));
|
||||||
diff_state.stack.scope_stack.push(scope_id);
|
diff_state.scope_stack.push(scope_id);
|
||||||
diff_state.work(|| false);
|
|
||||||
|
let node = self.scopes.fin_head(scope_id);
|
||||||
|
let created = diff_state.create_node(node);
|
||||||
|
|
||||||
|
diff_state.mutations.append_children(created as u32);
|
||||||
|
|
||||||
self.dirty_scopes.clear();
|
self.dirty_scopes.clear();
|
||||||
assert!(self.dirty_scopes.is_empty());
|
assert!(self.dirty_scopes.is_empty());
|
||||||
|
|
||||||
@@ -579,12 +583,11 @@ impl VirtualDom {
|
|||||||
);
|
);
|
||||||
|
|
||||||
diff_machine.force_diff = true;
|
diff_machine.force_diff = true;
|
||||||
diff_machine.stack.push(DiffInstruction::Diff { old, new });
|
diff_machine.scope_stack.push(scope_id);
|
||||||
diff_machine.stack.scope_stack.push(scope_id);
|
|
||||||
|
|
||||||
let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
|
let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
|
||||||
diff_machine.stack.element_stack.push(scope.container);
|
diff_machine.element_stack.push(scope.container);
|
||||||
diff_machine.work(|| false);
|
|
||||||
|
diff_machine.diff_node(old, new);
|
||||||
|
|
||||||
diff_machine.mutations
|
diff_machine.mutations
|
||||||
}
|
}
|
||||||
@@ -623,10 +626,10 @@ impl VirtualDom {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
||||||
let mut machine = DiffState::new(&self.scopes);
|
let mut machine = DiffState::new(&self.scopes);
|
||||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
machine.element_stack.push(ElementId(0));
|
||||||
machine.stack.element_stack.push(ElementId(0));
|
machine.scope_stack.push(ScopeId(0));
|
||||||
machine.stack.scope_stack.push(ScopeId(0));
|
machine.diff_node(old, new);
|
||||||
machine.work(|| false);
|
|
||||||
machine.mutations
|
machine.mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,11 +648,11 @@ impl VirtualDom {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
|
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
|
||||||
let mut machine = DiffState::new(&self.scopes);
|
let mut machine = DiffState::new(&self.scopes);
|
||||||
machine.stack.element_stack.push(ElementId(0));
|
machine.scope_stack.push(ScopeId(0));
|
||||||
machine
|
machine.element_stack.push(ElementId(0));
|
||||||
.stack
|
let node = self.render_vnodes(nodes);
|
||||||
.create_node(self.render_vnodes(nodes), MountType::Append);
|
let created = machine.create_node(node);
|
||||||
machine.work(|| false);
|
machine.mutations.append_children(created as u32);
|
||||||
machine.mutations
|
machine.mutations
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,16 +677,15 @@ impl VirtualDom {
|
|||||||
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
|
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
|
||||||
|
|
||||||
let mut create = DiffState::new(&self.scopes);
|
let mut create = DiffState::new(&self.scopes);
|
||||||
create.stack.scope_stack.push(ScopeId(0));
|
create.scope_stack.push(ScopeId(0));
|
||||||
create.stack.element_stack.push(ElementId(0));
|
create.element_stack.push(ElementId(0));
|
||||||
create.stack.create_node(old, MountType::Append);
|
let created = create.create_node(old);
|
||||||
create.work(|| false);
|
create.mutations.append_children(created as u32);
|
||||||
|
|
||||||
let mut edit = DiffState::new(&self.scopes);
|
let mut edit = DiffState::new(&self.scopes);
|
||||||
edit.stack.scope_stack.push(ScopeId(0));
|
edit.scope_stack.push(ScopeId(0));
|
||||||
edit.stack.element_stack.push(ElementId(0));
|
edit.element_stack.push(ElementId(0));
|
||||||
edit.stack.push(DiffInstruction::Diff { old, new });
|
edit.diff_node(old, new);
|
||||||
edit.work(|| false);
|
|
||||||
|
|
||||||
(create.mutations, edit.mutations)
|
(create.mutations, edit.mutations)
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-196
@@ -1,4 +1,5 @@
|
|||||||
#![allow(unused, non_upper_case_globals)]
|
#![allow(unused, non_upper_case_globals)]
|
||||||
|
|
||||||
//! Diffing Tests
|
//! Diffing Tests
|
||||||
//!
|
//!
|
||||||
//! These tests only verify that the diffing algorithm works properly for single components.
|
//! These tests only verify that the diffing algorithm works properly for single components.
|
||||||
@@ -31,26 +32,14 @@ fn html_and_rsx_generate_the_same_output() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { root: 2, text: "Hello world" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 2,
|
|
||||||
text: "Hello world"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(change.edits, [SetText { text: "Goodbye world", root: 2 },]);
|
||||||
change.edits,
|
|
||||||
[SetText {
|
|
||||||
text: "Goodbye world",
|
|
||||||
root: 2
|
|
||||||
},]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should result in 3 elements on the stack
|
/// Should result in 3 elements on the stack
|
||||||
@@ -67,32 +56,14 @@ fn fragments_create_properly() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { root: 2, text: "Hello a" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 2,
|
|
||||||
text: "Hello a"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 3, tag: "div" },
|
||||||
root: 3,
|
CreateTextNode { root: 4, text: "Hello b" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 4,
|
|
||||||
text: "Hello b"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 5,
|
CreateTextNode { root: 6, text: "Hello c" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
root: 6,
|
|
||||||
text: "Hello c"
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 3 },
|
AppendChildren { many: 3 },
|
||||||
]
|
]
|
||||||
@@ -116,10 +87,7 @@ fn empty_fragments_create_anchors() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 2, tag: "div" },
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
ReplaceWith { m: 1, root: 1 }
|
ReplaceWith { m: 1, root: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -138,29 +106,15 @@ fn empty_fragments_create_many_anchors() {
|
|||||||
create.edits,
|
create.edits,
|
||||||
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
|
[CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 2, tag: "div" },
|
||||||
root: 2,
|
CreateElement { root: 3, tag: "div" },
|
||||||
tag: "div"
|
CreateElement { root: 4, tag: "div" },
|
||||||
},
|
CreateElement { root: 5, tag: "div" },
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 3,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 4,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
ReplaceWith { m: 5, root: 1 }
|
ReplaceWith { m: 5, root: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -188,32 +142,14 @@ fn empty_fragments_create_anchors_with_many_children() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 2 },
|
||||||
tag: "div",
|
CreateTextNode { text: "hello: 0", root: 3 },
|
||||||
root: 2,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello: 0",
|
|
||||||
root: 3
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 4 },
|
||||||
tag: "div",
|
CreateTextNode { text: "hello: 1", root: 5 },
|
||||||
root: 4,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello: 1",
|
|
||||||
root: 5
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 6 },
|
||||||
tag: "div",
|
CreateTextNode { text: "hello: 2", root: 7 },
|
||||||
root: 6,
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello: 2",
|
|
||||||
root: 7
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
ReplaceWith { root: 1, m: 3 }
|
ReplaceWith { root: 1, m: 3 }
|
||||||
]
|
]
|
||||||
@@ -236,23 +172,11 @@ fn many_items_become_fragment() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateTextNode { text: "hello", root: 2 },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello",
|
|
||||||
root: 2
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
CreateElement {
|
CreateElement { root: 3, tag: "div" },
|
||||||
root: 3,
|
CreateTextNode { text: "hello", root: 4 },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateTextNode {
|
|
||||||
text: "hello",
|
|
||||||
root: 4
|
|
||||||
},
|
|
||||||
AppendChildren { many: 1 },
|
AppendChildren { many: 1 },
|
||||||
AppendChildren { many: 2 },
|
AppendChildren { many: 2 },
|
||||||
]
|
]
|
||||||
@@ -315,7 +239,7 @@ fn two_fragments_with_differrent_elements_are_differet() {
|
|||||||
// replace the divs with new h1s
|
// replace the divs with new h1s
|
||||||
CreateElement { tag: "h1", root: 7 },
|
CreateElement { tag: "h1", root: 7 },
|
||||||
ReplaceWith { root: 1, m: 1 },
|
ReplaceWith { root: 1, m: 1 },
|
||||||
CreateElement { tag: "h1", root: 8 },
|
CreateElement { tag: "h1", root: 1 }, // notice how 1 gets re-used
|
||||||
ReplaceWith { root: 2, m: 1 },
|
ReplaceWith { root: 2, m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -339,39 +263,27 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateElement { root: 2, tag: "div" },
|
||||||
tag: "div"
|
CreateElement { root: 3, tag: "div" },
|
||||||
},
|
CreateElement { root: 4, tag: "div" },
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 3,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 4,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement { root: 6, tag: "p" },
|
CreateElement { root: 6, tag: "p" },
|
||||||
AppendChildren { many: 6 },
|
AppendChildren { many: 6 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// note: key reuse is always the last node that got used
|
||||||
|
// slab maintains a linked list, essentially
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
Remove { root: 3 },
|
Remove { root: 3 },
|
||||||
Remove { root: 4 },
|
Remove { root: 4 },
|
||||||
Remove { root: 5 },
|
Remove { root: 5 },
|
||||||
CreateElement { root: 7, tag: "h1" },
|
CreateElement { root: 5, tag: "h1" }, // 3 gets reused
|
||||||
ReplaceWith { root: 1, m: 1 },
|
ReplaceWith { root: 1, m: 1 }, // 1 gets deleted
|
||||||
CreateElement { root: 8, tag: "h1" },
|
CreateElement { root: 1, tag: "h1" }, // 1 gets reused
|
||||||
ReplaceWith { root: 2, m: 1 },
|
ReplaceWith { root: 2, m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -395,14 +307,8 @@ fn two_fragments_with_same_elements_are_differet() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
create.edits,
|
create.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 1, tag: "div" },
|
||||||
root: 1,
|
CreateElement { root: 2, tag: "div" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 2,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement { root: 3, tag: "p" },
|
CreateElement { root: 3, tag: "p" },
|
||||||
AppendChildren { many: 3 },
|
AppendChildren { many: 3 },
|
||||||
]
|
]
|
||||||
@@ -410,18 +316,9 @@ fn two_fragments_with_same_elements_are_differet() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 4, tag: "div" },
|
||||||
root: 4,
|
CreateElement { root: 5, tag: "div" },
|
||||||
tag: "div"
|
CreateElement { root: 6, tag: "div" },
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertAfter { root: 2, n: 3 },
|
InsertAfter { root: 2, n: 3 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -628,14 +525,8 @@ fn keyed_diffing_additions() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 6,
|
CreateElement { root: 7, tag: "div" },
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
root: 7,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertAfter { n: 2, root: 5 }
|
InsertAfter { n: 2, root: 5 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -663,14 +554,8 @@ fn keyed_diffing_additions_and_moves_on_ends() {
|
|||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
// create 11, 12
|
// create 11, 12
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 5 },
|
||||||
tag: "div",
|
CreateElement { tag: "div", root: 6 },
|
||||||
root: 5
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 6
|
|
||||||
},
|
|
||||||
InsertAfter { root: 3, n: 2 },
|
InsertAfter { root: 3, n: 2 },
|
||||||
// move 7 to the front
|
// move 7 to the front
|
||||||
PushRoot { root: 4 },
|
PushRoot { root: 4 },
|
||||||
@@ -684,43 +569,32 @@ fn keyed_diffing_additions_and_moves_in_middle() {
|
|||||||
let dom = new_dom();
|
let dom = new_dom();
|
||||||
|
|
||||||
let left = rsx!({
|
let left = rsx!({
|
||||||
[/**/ 4, 5, 6, 7 /**/].iter().map(|f| {
|
[/**/ 1, 2, 3, 4 /**/].iter().map(|f| {
|
||||||
rsx! { div { key: "{f}" }}
|
rsx! { div { key: "{f}" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let right = rsx!({
|
let right = rsx!({
|
||||||
[/**/ 7, 4, 13, 17, 5, 11, 12, 6 /**/].iter().map(|f| {
|
[/**/ 4, 1, 7, 8, 2, 5, 6, 3 /**/].iter().map(|f| {
|
||||||
rsx! { div { key: "{f}" }}
|
rsx! { div { key: "{f}" }}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// LIS: 4, 5, 6
|
// LIS: 4, 5, 6
|
||||||
let (_, change) = dom.diff_lazynodes(left, right);
|
let (_, change) = dom.diff_lazynodes(left, right);
|
||||||
log::debug!("{:#?}", change);
|
log::debug!("{:#?}", change);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
change.edits,
|
change.edits,
|
||||||
[
|
[
|
||||||
// create 13, 17
|
// create 5, 6
|
||||||
CreateElement {
|
CreateElement { tag: "div", root: 5 },
|
||||||
tag: "div",
|
CreateElement { tag: "div", root: 6 },
|
||||||
root: 5
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 6
|
|
||||||
},
|
|
||||||
InsertBefore { root: 2, n: 2 },
|
|
||||||
// create 11, 12
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 7
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: "div",
|
|
||||||
root: 8
|
|
||||||
},
|
|
||||||
InsertBefore { root: 3, n: 2 },
|
InsertBefore { root: 3, n: 2 },
|
||||||
|
// create 7, 8
|
||||||
|
CreateElement { tag: "div", root: 7 },
|
||||||
|
CreateElement { tag: "div", root: 8 },
|
||||||
|
InsertBefore { root: 2, n: 2 },
|
||||||
// move 7
|
// move 7
|
||||||
PushRoot { root: 4 },
|
PushRoot { root: 4 },
|
||||||
InsertBefore { root: 1, n: 1 }
|
InsertBefore { root: 1, n: 1 }
|
||||||
@@ -756,16 +630,10 @@ fn controlled_keyed_diffing_out_of_order() {
|
|||||||
// remove 7
|
// remove 7
|
||||||
|
|
||||||
// create 9 and insert before 6
|
// create 9 and insert before 6
|
||||||
CreateElement {
|
CreateElement { root: 5, tag: "div" },
|
||||||
root: 5,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertBefore { n: 1, root: 3 },
|
InsertBefore { n: 1, root: 3 },
|
||||||
// create 0 and insert before 5
|
// create 0 and insert before 5
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertBefore { n: 1, root: 2 },
|
InsertBefore { n: 1, root: 2 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -792,10 +660,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
changes.edits,
|
changes.edits,
|
||||||
[
|
[
|
||||||
CreateElement {
|
CreateElement { root: 6, tag: "div" },
|
||||||
root: 6,
|
|
||||||
tag: "div"
|
|
||||||
},
|
|
||||||
InsertBefore { n: 1, root: 3 },
|
InsertBefore { n: 1, root: 3 },
|
||||||
PushRoot { root: 4 },
|
PushRoot { root: 4 },
|
||||||
InsertBefore { n: 1, root: 1 },
|
InsertBefore { n: 1, root: 1 },
|
||||||
|
|||||||
Reference in new issue
Block a user