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:
Jonathan Kelley committed 2022-01-30 17:47:58 -05:00
1 parent 3edf3e367f
commit 3bb5c8142c
9 files changed
+1268 -322

No files matched your search

+12 -9
View File
@@ -222,7 +222,7 @@ impl<'bump> DiffStack<'bump> {
self.nodes_created_stack.pop().unwrap()
}
fn current_scope(&self) -> Option<ScopeId> {
pub fn current_scope(&self) -> Option<ScopeId> {
self.scope_stack.last().copied()
}
@@ -314,9 +314,10 @@ impl<'bump> DiffState<'bump> {
}
MountType::Append => {
self.mutations.edits.push(AppendChildren {
many: nodes_created as u32,
});
self.mutations.append_children(nodes_created as u32);
// self.mutations.edits.push(AppendChildren {
// many: nodes_created as u32,
// });
}
MountType::InsertAfter { other_node } => {
@@ -470,7 +471,8 @@ impl<'bump> DiffState<'bump> {
self.stack.create_component(new_idx, nextnode);
// 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() {
self.mutations.edits.push(PushRoot {
root: root.as_u64(),
});
self.mutations.push_root(root);
// self.mutations.edits.push(PushRoot {
// root: root.as_u64(),
// });
self.stack.element_stack.push(root);
self.stack.instructions.push(DiffInstruction::PopElement);
self.stack.create_children(new.children, MountType::Append);
@@ -771,7 +774,7 @@ impl<'bump> DiffState<'bump> {
// this should auto drop the previous props
self.scopes.run_scope(scope_addr);
self.mutations.dirty_scopes.insert(scope_addr);
self.mutations.mark_dirty_scope(scope_addr);
self.diff_node(
self.scopes.wip_head(scope_addr),
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -1,7 +1,8 @@
#![allow(non_snake_case)]
#![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 lazynodes;
pub(crate) mod mutations;
@@ -12,7 +13,8 @@ pub(crate) mod util;
pub(crate) mod virtual_dom;
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::lazynodes::*;
pub use crate::mutations::*;
+8
View File
@@ -143,6 +143,10 @@ impl<'a> Mutations<'a> {
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
pub(crate) fn remove(&mut self, id: u64) {
self.edits.push(Remove { root: id });
@@ -217,6 +221,10 @@ impl<'a> Mutations<'a> {
let name = attribute.name;
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
+18 -41
View File
@@ -175,6 +175,7 @@ impl Debug for VNode<'_> {
.field("key", &el.key)
.field("attrs", &el.attributes)
.field("children", &el.children)
.field("id", &el.id)
.finish(),
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
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> {
fn default() -> Self {
Self {
callback: RefCell::new(None),
}
Self { callback: RefCell::new(None) }
}
}
@@ -441,10 +440,7 @@ pub struct NodeFactory<'a> {
impl<'a> NodeFactory<'a> {
pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
NodeFactory {
scope,
bump: &scope.wip_frame().bump,
}
NodeFactory { scope, bump: &scope.wip_frame().bump }
}
#[inline]
@@ -454,11 +450,10 @@ impl<'a> NodeFactory<'a> {
/// Directly pass in text blocks without the need to use the format_args macro.
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
VNode::Text(self.bump.alloc(VText {
id: empty_cell(),
text,
is_static: true,
}))
VNode::Text(
self.bump
.alloc(VText { id: empty_cell(), text, is_static: true }),
)
}
/// 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> {
let (text, is_static) = self.raw_text(args);
VNode::Text(self.bump.alloc(VText {
text,
is_static,
id: empty_cell(),
}))
VNode::Text(self.bump.alloc(VText { text, is_static, id: empty_cell() }))
}
pub fn element(
@@ -543,13 +534,7 @@ impl<'a> NodeFactory<'a> {
is_volatile: bool,
) -> Attribute<'a> {
let (value, is_static) = self.raw_text(val);
Attribute {
name,
value,
is_static,
namespace,
is_volatile,
}
Attribute { name, value, is_static, namespace, is_volatile }
}
pub fn component<P>(
@@ -591,11 +576,7 @@ impl<'a> NodeFactory<'a> {
}
pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
Listener {
event,
mounted_node: Cell::new(None),
callback,
}
Listener { event, mounted_node: Cell::new(None), callback }
}
pub fn fragment_root<'b, 'c>(
@@ -611,10 +592,10 @@ impl<'a> NodeFactory<'a> {
if nodes.is_empty() {
VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
} else {
VNode::Fragment(self.bump.alloc(VFragment {
children: nodes.into_bump_slice(),
key: None,
}))
VNode::Fragment(
self.bump
.alloc(VFragment { children: nodes.into_bump_slice(), key: None }),
)
}
}
@@ -650,10 +631,7 @@ impl<'a> NodeFactory<'a> {
);
}
VNode::Fragment(self.bump.alloc(VFragment {
children,
key: None,
}))
VNode::Fragment(self.bump.alloc(VFragment { children, key: None }))
}
}
@@ -676,10 +654,9 @@ impl<'a> NodeFactory<'a> {
} else {
let children = nodes.into_bump_slice();
Some(VNode::Fragment(self.bump.alloc(VFragment {
children,
key: None,
})))
Some(VNode::Fragment(
self.bump.alloc(VFragment { children, key: None }),
))
}
}
+9 -23
View File
@@ -203,7 +203,8 @@ impl ScopeArena {
}
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);
}
@@ -304,11 +305,9 @@ impl ScopeArena {
frame.node.set(unsafe { extend_vnode(node) });
} else {
let frame = scope.wip_frame();
let node = frame
.bump
.alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
id: Default::default(),
})));
let node = frame.bump.alloc(VNode::Placeholder(
frame.bump.alloc(VPlaceholder { id: Default::default() }),
));
frame.node.set(unsafe { extend_vnode(node) });
}
@@ -418,10 +417,7 @@ pub struct Scope<'a, P = ()> {
impl<P> Copy for Scope<'_, P> {}
impl<P> Clone for Scope<'_, P> {
fn clone(&self) -> Self {
Self {
scope: self.scope,
props: self.props,
}
Self { scope: self.scope, props: self.props }
}
}
@@ -784,10 +780,7 @@ impl ScopeState {
/// }
///```
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
Some(rsx.call(NodeFactory {
scope: self,
bump: &self.wip_frame().bump,
}))
Some(rsx.call(NodeFactory { scope: self, bump: &self.wip_frame().bump }))
}
/// Store a value between renders
@@ -894,10 +887,7 @@ impl ScopeState {
self.shared_contexts.get_mut().clear();
// next: reset the node data
let SelfReferentialItems {
borrowed_props,
listeners,
} = self.items.get_mut();
let SelfReferentialItems { borrowed_props, listeners } = self.items.get_mut();
borrowed_props.clear();
listeners.clear();
self.frames[0].reset();
@@ -955,11 +945,7 @@ pub(crate) struct TaskQueue {
pub(crate) type InnerTask = Pin<Box<dyn Future<Output = ()>>>;
impl TaskQueue {
fn new(sender: UnboundedSender<SchedulerMsg>) -> Rc<Self> {
Rc::new(Self {
tasks: RefCell::new(FxHashMap::default()),
gen: Cell::new(0),
sender,
})
Rc::new(Self { tasks: RefCell::new(FxHashMap::default()), gen: Cell::new(0), sender })
}
fn push_fut(&self, task: impl Future<Output = ()> + 'static) -> TaskId {
let pinned = Box::pin(task);
+53 -51
View File
@@ -2,6 +2,7 @@
//!
//! 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 futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_util::{future::poll_fn, StreamExt};
@@ -453,7 +454,7 @@ impl VirtualDom {
while !self.dirty_scopes.is_empty() {
let scopes = &self.scopes;
let mut diff_state = DiffState::new(scopes);
let mut diff_state = AsyncDiffState::new(scopes);
let mut ran_scopes = FxHashSet::default();
@@ -473,32 +474,33 @@ impl VirtualDom {
self.scopes.run_scope(scopeid);
let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
diff_state.stack.push(DiffInstruction::Diff { new, old });
diff_state.diff_scope(scopeid);
log::debug!("pushing scope {:?} onto scope stack", scopeid);
diff_state.stack.scope_stack.push(scopeid);
let AsyncDiffState { mutations, .. } = diff_state;
let scope = scopes.get_scope(scopeid).unwrap();
diff_state.stack.element_stack.push(scope.container);
for scope in &mutations.dirty_scopes {
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
@@ -526,13 +528,15 @@ impl VirtualDom {
let mut diff_state = DiffState::new(&self.scopes);
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.stack.scope_stack.push(scope_id);
diff_state.work(|| false);
diff_state.element_stack.push(ElementId(0));
diff_state.scope_stack.push(scope_id);
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();
assert!(self.dirty_scopes.is_empty());
@@ -579,12 +583,11 @@ impl VirtualDom {
);
diff_machine.force_diff = true;
diff_machine.stack.push(DiffInstruction::Diff { old, new });
diff_machine.stack.scope_stack.push(scope_id);
diff_machine.scope_stack.push(scope_id);
let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
diff_machine.stack.element_stack.push(scope.container);
diff_machine.work(|| false);
diff_machine.element_stack.push(scope.container);
diff_machine.diff_node(old, new);
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> {
let mut machine = DiffState::new(&self.scopes);
machine.stack.push(DiffInstruction::Diff { new, old });
machine.stack.element_stack.push(ElementId(0));
machine.stack.scope_stack.push(ScopeId(0));
machine.work(|| false);
machine.element_stack.push(ElementId(0));
machine.scope_stack.push(ScopeId(0));
machine.diff_node(old, new);
machine.mutations
}
@@ -645,11 +648,11 @@ impl VirtualDom {
/// ```
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes);
machine.stack.element_stack.push(ElementId(0));
machine
.stack
.create_node(self.render_vnodes(nodes), MountType::Append);
machine.work(|| false);
machine.scope_stack.push(ScopeId(0));
machine.element_stack.push(ElementId(0));
let node = self.render_vnodes(nodes);
let created = machine.create_node(node);
machine.mutations.append_children(created as u32);
machine.mutations
}
@@ -674,16 +677,15 @@ impl VirtualDom {
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
let mut create = DiffState::new(&self.scopes);
create.stack.scope_stack.push(ScopeId(0));
create.stack.element_stack.push(ElementId(0));
create.stack.create_node(old, MountType::Append);
create.work(|| false);
create.scope_stack.push(ScopeId(0));
create.element_stack.push(ElementId(0));
let created = create.create_node(old);
create.mutations.append_children(created as u32);
let mut edit = DiffState::new(&self.scopes);
edit.stack.scope_stack.push(ScopeId(0));
edit.stack.element_stack.push(ElementId(0));
edit.stack.push(DiffInstruction::Diff { old, new });
edit.work(|| false);
edit.scope_stack.push(ScopeId(0));
edit.element_stack.push(ElementId(0));
edit.diff_node(old, new);
(create.mutations, edit.mutations)
}