chore: tweak fragments and tests

This commit is contained in:
Jonathan Kelley committed 2022-11-22 19:59:56 -08:00
1 parent 0e5a59f9ed
commit 4bfd936e12
9 files changed
+638 -539

No files matched your search

+93 -94
View File
@@ -1,5 +1,5 @@
use crate::factory::RenderReturn; use crate::factory::RenderReturn;
use crate::innerlude::{Mutations, VComponent, VFragment, VText}; use crate::innerlude::{VComponent, VFragment, VText};
use crate::mutations::Mutation; use crate::mutations::Mutation;
use crate::mutations::Mutation::*; use crate::mutations::Mutation::*;
use crate::nodes::VNode; use crate::nodes::VNode;
@@ -7,33 +7,24 @@ use crate::nodes::{DynamicNode, TemplateNode};
use crate::virtual_dom::VirtualDom; use crate::virtual_dom::VirtualDom;
use crate::{AttributeValue, ScopeId, SuspenseContext, TemplateAttribute}; use crate::{AttributeValue, ScopeId, SuspenseContext, TemplateAttribute};
impl VirtualDom { impl<'b: 'static> VirtualDom {
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
/// ///
/// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
pub(crate) fn create_scope<'a>( pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
&mut self,
scope: ScopeId,
mutations: &mut Mutations<'a>,
template: &'a VNode<'a>,
) -> usize {
self.scope_stack.push(scope); self.scope_stack.push(scope);
let out = self.create(mutations, template); let out = self.create(template);
self.scope_stack.pop(); self.scope_stack.pop();
out out
} }
/// Create this template and write its mutations /// Create this template and write its mutations
pub(crate) fn create<'a>( pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize {
&mut self,
mutations: &mut Mutations<'a>,
template: &'a VNode<'a>,
) -> usize {
// The best renderers will have templates prehydrated and registered // The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways // Just in case, let's create the template using instructions anyways
if !self.templates.contains_key(&template.template.id) { if !self.templates.contains_key(&template.template.id) {
self.register_template(template, mutations); self.register_template(template);
} }
// Walk the roots, creating nodes and assigning IDs // Walk the roots, creating nodes and assigning IDs
@@ -47,7 +38,7 @@ impl VirtualDom {
for (root_idx, root) in template.template.roots.iter().enumerate() { for (root_idx, root) in template.template.roots.iter().enumerate() {
on_stack += match root { on_stack += match root {
TemplateNode::Element { .. } | TemplateNode::Text(_) => { TemplateNode::Element { .. } | TemplateNode::Text(_) => {
mutations.push(LoadTemplate { self.mutations.push(LoadTemplate {
name: template.template.id, name: template.template.id,
index: root_idx, index: root_idx,
}); });
@@ -56,17 +47,27 @@ impl VirtualDom {
TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => { TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => {
match &template.dynamic_nodes[*id] { match &template.dynamic_nodes[*id] {
DynamicNode::Fragment { .. } | DynamicNode::Component { .. } => self DynamicNode::Fragment(frag) => match frag {
.create_dynamic_node( VFragment::Empty(slot) => {
mutations, let id =
self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
self.mutations.push(CreatePlaceholder { id });
1
}
VFragment::NonEmpty(_) => self.create_dynamic_node(
template, template,
&template.dynamic_nodes[*id], &template.dynamic_nodes[*id],
*id, *id,
), ),
},
DynamicNode::Component { .. } => {
self.create_dynamic_node(template, &template.dynamic_nodes[*id], *id)
}
DynamicNode::Text(VText { id: slot, value }) => { DynamicNode::Text(VText { id: slot, value }) => {
let id = self.next_element(template, template.template.node_paths[*id]); let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id); slot.set(id);
mutations.push(CreateTextNode { value, id }); self.mutations.push(CreateTextNode { value, id });
1 1
} }
} }
@@ -79,7 +80,7 @@ impl VirtualDom {
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8) dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
{ {
let id = self.next_element(template, template.template.attr_paths[attr_id]); let id = self.next_element(template, template.template.attr_paths[attr_id]);
mutations.push(AssignId { self.mutations.push(AssignId {
path: &path[1..], path: &path[1..],
id, id,
}); });
@@ -89,18 +90,18 @@ impl VirtualDom {
attribute.mounted_element.set(id); attribute.mounted_element.set(id);
match &attribute.value { match &attribute.value {
AttributeValue::Text(value) => mutations.push(SetAttribute { AttributeValue::Text(value) => self.mutations.push(SetAttribute {
name: attribute.name, name: attribute.name,
value: *value, value: *value,
ns: attribute.namespace, ns: attribute.namespace,
id, id,
}), }),
AttributeValue::Bool(value) => mutations.push(SetBoolAttribute { AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
name: attribute.name, name: attribute.name,
value: *value, value: *value,
id, id,
}), }),
AttributeValue::Listener(_) => mutations.push(NewEventListener { AttributeValue::Listener(_) => self.mutations.push(NewEventListener {
// all listeners start with "on" // all listeners start with "on"
event_name: &attribute.name[2..], event_name: &attribute.name[2..],
scope: cur_scope, scope: cur_scope,
@@ -140,9 +141,9 @@ impl VirtualDom {
if let (Some(start), Some(end)) = (start, end) { if let (Some(start), Some(end)) = (start, end) {
for idx in start..=end { for idx in start..=end {
let node = &template.dynamic_nodes[idx]; let node = &template.dynamic_nodes[idx];
let m = self.create_dynamic_node(mutations, template, node, idx); let m = self.create_dynamic_node(template, node, idx);
if m > 0 { if m > 0 {
mutations.push(ReplacePlaceholder { self.mutations.push(ReplacePlaceholder {
m, m,
path: &template.template.node_paths[idx][1..], path: &template.template.node_paths[idx][1..],
}); });
@@ -155,12 +156,12 @@ impl VirtualDom {
} }
/// Insert a new template into the VirtualDom's template registry /// Insert a new template into the VirtualDom's template registry
fn register_template<'a>(&mut self, template: &'a VNode<'a>, mutations: &mut Mutations<'a>) { fn register_template(&mut self, template: &'b VNode<'b>) {
for node in template.template.roots { for node in template.template.roots {
self.create_static_node(&mut mutations.template_mutations, template, node); self.create_static_node(template, node);
} }
mutations.template_mutations.push(SaveTemplate { self.mutations.template_mutations.push(SaveTemplate {
name: template.template.id, name: template.template.id,
m: template.template.roots.len(), m: template.template.roots.len(),
}); });
@@ -169,22 +170,27 @@ impl VirtualDom {
.insert(template.template.id, template.template); .insert(template.template.id, template.template);
} }
pub(crate) fn create_static_node<'a>( pub(crate) fn create_static_node(
&mut self, &mut self,
mutations: &mut Vec<Mutation<'a>>, template: &'b VNode<'b>,
template: &'a VNode<'a>, node: &'b TemplateNode<'static>,
node: &'a TemplateNode<'static>,
) { ) {
match *node { match *node {
// Todo: create the children's template // Todo: create the children's template
TemplateNode::Dynamic(idx) => { TemplateNode::Dynamic(idx) => {
let id = self.next_element(template, template.template.node_paths[idx]); let id = self.next_element(template, template.template.node_paths[idx]);
mutations.push(CreatePlaceholder { id }) self.mutations
.template_mutations
.push(CreatePlaceholder { id })
} }
TemplateNode::Text(value) => mutations.push(CreateStaticText { value }), TemplateNode::Text(value) => self
TemplateNode::DynamicText { .. } => mutations.push(CreateStaticText { .mutations
value: "placeholder", .template_mutations
}), .push(CreateStaticText { value }),
TemplateNode::DynamicText { .. } => self
.mutations
.template_mutations
.push(CreateStaticText { value: "d" }),
TemplateNode::Element { TemplateNode::Element {
attrs, attrs,
@@ -195,26 +201,28 @@ impl VirtualDom {
} => { } => {
let id = self.next_element(template, &[]); // never gets referenced, empty path is fine, I think? let id = self.next_element(template, &[]); // never gets referenced, empty path is fine, I think?
mutations.push(CreateElement { self.mutations.template_mutations.push(CreateElement {
name: tag, name: tag,
namespace, namespace,
id, id,
}); });
mutations.extend(attrs.into_iter().filter_map(|attr| match attr { self.mutations
TemplateAttribute::Static { .template_mutations
name, .extend(attrs.into_iter().filter_map(|attr| match attr {
value, TemplateAttribute::Static {
namespace, name,
.. value,
} => Some(SetAttribute { namespace,
name, ..
value, } => Some(SetAttribute {
id, name,
ns: *namespace, value,
}), id,
_ => None, ns: *namespace,
})); }),
_ => None,
}));
if children.is_empty() && inner_opt { if children.is_empty() && inner_opt {
return; return;
@@ -222,33 +230,33 @@ impl VirtualDom {
children children
.into_iter() .into_iter()
.for_each(|child| self.create_static_node(mutations, template, child)); .for_each(|child| self.create_static_node(template, child));
mutations.push(AppendChildren { m: children.len() }) self.mutations
.template_mutations
.push(AppendChildren { m: children.len() })
} }
} }
} }
pub(crate) fn create_dynamic_node<'a>( pub(crate) fn create_dynamic_node(
&mut self, &mut self,
mutations: &mut Mutations<'a>, template: &'b VNode<'b>,
template: &'a VNode<'a>, node: &'b DynamicNode<'b>,
node: &'a DynamicNode<'a>,
idx: usize, idx: usize,
) -> usize { ) -> usize {
use DynamicNode::*; use DynamicNode::*;
match node { match node {
Text(text) => self.create_dynamic_text(mutations, template, text, idx), Text(text) => self.create_dynamic_text(template, text, idx),
Fragment(frag) => self.create_fragment(frag, template, idx, mutations), Fragment(frag) => self.create_fragment(frag, template, idx),
Component(component) => self.create_component_node(mutations, template, component, idx), Component(component) => self.create_component_node(template, component, idx),
} }
} }
fn create_dynamic_text<'a>( fn create_dynamic_text(
&mut self, &mut self,
mutations: &mut Mutations<'a>, template: &'b VNode<'b>,
template: &VNode<'a>, text: &'b VText<'b>,
text: &VText<'a>,
idx: usize, idx: usize,
) -> usize { ) -> usize {
// Allocate a dynamic element reference for this text node // Allocate a dynamic element reference for this text node
@@ -258,7 +266,7 @@ impl VirtualDom {
text.id.set(new_id); text.id.set(new_id);
// Add the mutation to the list // Add the mutation to the list
mutations.push(HydrateText { self.mutations.push(HydrateText {
id: new_id, id: new_id,
path: &template.template.node_paths[idx][1..], path: &template.template.node_paths[idx][1..],
value: text.value, value: text.value,
@@ -268,17 +276,16 @@ impl VirtualDom {
0 0
} }
pub(crate) fn create_fragment<'a>( pub(crate) fn create_fragment(
&mut self, &mut self,
frag: &'a VFragment<'a>, frag: &'b VFragment<'b>,
template: &'a VNode<'a>, template: &'b VNode<'b>,
idx: usize, idx: usize,
mutations: &mut Mutations<'a>,
) -> usize { ) -> usize {
match frag { match frag {
VFragment::NonEmpty(nodes) => nodes VFragment::NonEmpty(nodes) => {
.iter() nodes.iter().fold(0, |acc, child| acc + self.create(child))
.fold(0, |acc, child| acc + self.create(mutations, child)), }
VFragment::Empty(slot) => { VFragment::Empty(slot) => {
// Allocate a dynamic element reference for this text node // Allocate a dynamic element reference for this text node
@@ -288,7 +295,7 @@ impl VirtualDom {
slot.set(id); slot.set(id);
// Assign the ID to the existing node in the template // Assign the ID to the existing node in the template
mutations.push(AssignId { self.mutations.push(AssignId {
path: &template.template.node_paths[idx][1..], path: &template.template.node_paths[idx][1..],
id, id,
}); });
@@ -299,11 +306,10 @@ impl VirtualDom {
} }
} }
fn create_component_node<'a>( fn create_component_node(
&mut self, &mut self,
mutations: &mut Mutations<'a>, template: &'b VNode<'b>,
template: &'a VNode<'a>, component: &'b VComponent<'b>,
component: &'a VComponent<'a>,
idx: usize, idx: usize,
) -> usize { ) -> usize {
let props = component.props.replace(None).unwrap(); let props = component.props.replace(None).unwrap();
@@ -319,25 +325,19 @@ impl VirtualDom {
use RenderReturn::*; use RenderReturn::*;
match return_nodes { match return_nodes {
Sync(Ok(t)) => self.mount_component(mutations, scope, t, idx), Sync(Ok(t)) => self.mount_component(scope, t, idx),
Sync(Err(_e)) => todo!("Propogate error upwards"), Sync(Err(_e)) => todo!("Propogate error upwards"),
Async(_) => self.mount_component_placeholder(template, idx, scope, mutations), Async(_) => self.mount_component_placeholder(template, idx, scope),
} }
} }
fn mount_component<'a>( fn mount_component(&mut self, scope: ScopeId, template: &'b VNode<'b>, idx: usize) -> usize {
&mut self,
mutations: &mut Mutations<'a>,
scope: ScopeId,
template: &'a VNode<'a>,
idx: usize,
) -> usize {
// Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary
// is encountered // is encountered
let mutations_to_this_point = mutations.len(); let mutations_to_this_point = self.mutations.len();
// Create the component's root element // Create the component's root element
let created = self.create_scope(scope, mutations, template); let created = self.create_scope(scope, template);
// If there are no suspense leaves below us, then just don't bother checking anything suspense related // If there are no suspense leaves below us, then just don't bother checking anything suspense related
if self.collected_leaves.is_empty() { if self.collected_leaves.is_empty() {
@@ -360,7 +360,7 @@ impl VirtualDom {
// Note that we break off dynamic mutations only - since static mutations aren't rendered immediately // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately
let split_off = unsafe { let split_off = unsafe {
std::mem::transmute::<Vec<Mutation>, Vec<Mutation>>( std::mem::transmute::<Vec<Mutation>, Vec<Mutation>>(
mutations.split_off(mutations_to_this_point), self.mutations.split_off(mutations_to_this_point),
) )
}; };
boundary.mutations.borrow_mut().edits.extend(split_off); boundary.mutations.borrow_mut().edits.extend(split_off);
@@ -371,7 +371,7 @@ impl VirtualDom {
.extend(self.collected_leaves.drain(..)); .extend(self.collected_leaves.drain(..));
// Now assign the placeholder in the DOM // Now assign the placeholder in the DOM
mutations.push(AssignId { self.mutations.push(AssignId {
id: new_id, id: new_id,
path: &template.template.node_paths[idx][1..], path: &template.template.node_paths[idx][1..],
}); });
@@ -387,7 +387,6 @@ impl VirtualDom {
template: &VNode, template: &VNode,
idx: usize, idx: usize,
scope: ScopeId, scope: ScopeId,
mutations: &mut Mutations,
) -> usize { ) -> usize {
let new_id = self.next_element(template, template.template.node_paths[idx]); let new_id = self.next_element(template, template.template.node_paths[idx]);
@@ -395,7 +394,7 @@ impl VirtualDom {
self.scopes[scope.0].placeholder.set(Some(new_id)); self.scopes[scope.0].placeholder.set(Some(new_id));
// Since the placeholder is already in the DOM, we don't create any new nodes // Since the placeholder is already in the DOM, we don't create any new nodes
mutations.push(AssignId { self.mutations.push(AssignId {
id: new_id, id: new_id,
path: &template.template.node_paths[idx][1..], path: &template.template.node_paths[idx][1..],
}); });
+357 -406
View File
@@ -1,4 +1,5 @@
use std::any::Any; use std::any::Any;
use std::cell::Cell;
use crate::factory::RenderReturn; use crate::factory::RenderReturn;
use crate::innerlude::{Mutations, VComponent, VFragment, VText}; use crate::innerlude::{Mutations, VComponent, VFragment, VText};
@@ -21,8 +22,8 @@ use crate::{
use fxhash::{FxHashMap, FxHashSet}; use fxhash::{FxHashMap, FxHashSet};
use slab::Slab; use slab::Slab;
impl<'b> VirtualDom { impl<'b: 'static> VirtualDom {
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) { pub fn diff_scope(&mut self, scope: ScopeId) {
let scope_state = &mut self.scopes[scope.0]; let scope_state = &mut self.scopes[scope.0];
// Load the old and new bump arenas // Load the old and new bump arenas
@@ -46,21 +47,16 @@ impl<'b> VirtualDom {
unsafe { unsafe {
let cur_arena = cur_arena.load_node(); let cur_arena = cur_arena.load_node();
let prev_arena = prev_arena.load_node(); let prev_arena = prev_arena.load_node();
self.diff_maybe_node(mutations, prev_arena, cur_arena); self.diff_maybe_node(prev_arena, cur_arena);
} }
self.scope_stack.pop(); self.scope_stack.pop();
} }
fn diff_maybe_node( fn diff_maybe_node(&mut self, left: &'b RenderReturn<'b>, right: &'b RenderReturn<'b>) {
&mut self,
m: &mut Mutations<'b>,
left: &'b RenderReturn<'b>,
right: &'b RenderReturn<'b>,
) {
use RenderReturn::{Async, Sync}; use RenderReturn::{Async, Sync};
match (left, right) { match (left, right) {
// diff // diff
(Sync(Ok(l)), Sync(Ok(r))) => self.diff_vnode(m, l, r), (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
_ => todo!("handle diffing nonstandard nodes"), _ => todo!("handle diffing nonstandard nodes"),
// // remove old with placeholder // // remove old with placeholder
@@ -83,14 +79,9 @@ impl<'b> VirtualDom {
} }
} }
pub fn diff_vnode( pub fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
&mut self,
muts: &mut Mutations<'b>,
left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>,
) {
if left_template.template.id != right_template.template.id { if left_template.template.id != right_template.template.id {
return self.light_diff_templates(muts, left_template, right_template); return self.light_diff_templates(left_template, right_template);
} }
for (left_attr, right_attr) in left_template for (left_attr, right_attr) in left_template
@@ -107,7 +98,7 @@ impl<'b> VirtualDom {
// todo: add more types of attribute values // todo: add more types of attribute values
match right_attr.value { match right_attr.value {
AttributeValue::Text(text) => { AttributeValue::Text(text) => {
muts.push(Mutation::SetAttribute { self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(), id: left_attr.mounted_element.get(),
name: left_attr.name, name: left_attr.name,
value: text, value: text,
@@ -126,30 +117,15 @@ impl<'b> VirtualDom {
.zip(right_template.dynamic_nodes.iter()) .zip(right_template.dynamic_nodes.iter())
{ {
match (left_node, right_node) { match (left_node, right_node) {
(Text(left), Text(right)) => self.diff_vtext(muts, left, right), (Text(left), Text(right)) => self.diff_vtext(left, right),
(Fragment(left), Fragment(right)) => self.diff_vfragment(muts, left, right), (Fragment(left), Fragment(right)) => self.diff_vfragment(left, right),
(Component(left), Component(right)) => self.diff_vcomponent(muts, left, right), (Component(left), Component(right)) => self.diff_vcomponent(left, right),
_ => self.replace(muts, left_template, right_template, left_node, right_node), _ => self.replace(left_template, right_template, left_node, right_node),
}; };
} }
} }
fn replace( fn diff_vcomponent(&mut self, left: &'b VComponent<'b>, right: &'b VComponent<'b>) {
&mut self,
muts: &mut Mutations<'b>,
left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>,
left: &'b DynamicNode<'b>,
right: &'b DynamicNode<'b>,
) {
}
fn diff_vcomponent(
&mut self,
muts: &mut Mutations<'b>,
left: &'b VComponent<'b>,
right: &'b VComponent<'b>,
) {
// Due to how templates work, we should never get two different components. The only way we could enter // Due to how templates work, we should never get two different components. The only way we could enter
// this codepath is through "light_diff", but we check there that the pointers are the same // this codepath is through "light_diff", but we check there that the pointers are the same
assert_eq!(left.render_fn, right.render_fn); assert_eq!(left.render_fn, right.render_fn);
@@ -177,7 +153,7 @@ impl<'b> VirtualDom {
// Now run the component and diff it // Now run the component and diff it
self.run_scope(scope_id); self.run_scope(scope_id);
self.diff_scope(muts, scope_id); self.diff_scope(scope_id);
} }
/// Lightly diff the two templates, checking only their roots. /// Lightly diff the two templates, checking only their roots.
@@ -218,16 +194,11 @@ impl<'b> VirtualDom {
/// Component { ..props } /// Component { ..props }
/// } /// }
/// ``` /// ```
fn light_diff_templates( fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
&mut self,
muts: &mut Mutations<'b>,
left: &'b VNode<'b>,
right: &'b VNode<'b>,
) {
if let Some(components) = matching_components(left, right) { if let Some(components) = matching_components(left, right) {
components components
.into_iter() .into_iter()
.for_each(|(l, r)| self.diff_vcomponent(muts, l, r)) .for_each(|(l, r)| self.diff_vcomponent(l, r))
} }
} }
@@ -235,64 +206,43 @@ impl<'b> VirtualDom {
/// ///
/// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
/// different. /// different.
fn diff_vtext(&mut self, muts: &mut Mutations<'b>, left: &'b VText<'b>, right: &'b VText<'b>) { fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
right.id.set(left.id.get()); right.id.set(left.id.get());
if left.value != right.value { if left.value != right.value {
muts.push(Mutation::SetText { self.mutations.push(Mutation::SetText {
id: left.id.get(), id: left.id.get(),
value: right.value, value: right.value,
}); });
} }
} }
fn diff_vfragment( fn diff_vfragment(&mut self, left: &'b VFragment<'b>, right: &'b VFragment<'b>) {
&mut self,
muts: &mut Mutations<'b>,
left: &'b VFragment<'b>,
right: &'b VFragment<'b>,
) {
use VFragment::*; use VFragment::*;
match (left, right) { match (left, right) {
(Empty(l), Empty(r)) => r.set(l.get()), (Empty(l), Empty(r)) => r.set(l.get()),
(Empty(l), NonEmpty(r)) => self.replace_placeholder_with_nodes(muts, l, r), (Empty(l), NonEmpty(r)) => self.replace_placeholder_with_nodes(l, r),
(NonEmpty(l), Empty(r)) => self.replace_nodes_with_placeholder(muts, l, r), (NonEmpty(l), Empty(r)) => self.replace_nodes_with_placeholder(l, r),
(NonEmpty(old), NonEmpty(new)) => self.diff_non_empty_fragment(new, old, muts), (NonEmpty(old), NonEmpty(new)) => self.diff_non_empty_fragment(new, old),
} }
} }
fn replace_placeholder_with_nodes( fn replace_placeholder_with_nodes(&mut self, l: &'b Cell<ElementId>, r: &'b [VNode<'b>]) {
&mut self, let created = r.iter().fold(0, |acc, child| acc + self.create(child));
muts: &mut Mutations<'b>,
l: &'b std::cell::Cell<ElementId>, self.mutations.push(Mutation::ReplaceWith {
r: &'b [VNode<'b>],
) {
let created = r
.iter()
.fold(0, |acc, child| acc + self.create(muts, child));
muts.push(Mutation::ReplaceWith {
id: l.get(), id: l.get(),
m: created, m: created,
}) })
} }
fn replace_nodes_with_placeholder( fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell<ElementId>) {
&mut self,
muts: &mut Mutations<'b>,
l: &'b [VNode<'b>],
r: &'b std::cell::Cell<ElementId>,
) {
// //
// Remove the old nodes, except for one // Remove the old nodes, except for one
self.remove_nodes(muts, &l[1..]); self.remove_nodes(&l[1..]);
} }
fn diff_non_empty_fragment( fn diff_non_empty_fragment(&mut self, new: &'b [VNode<'b>], old: &'b [VNode<'b>]) {
&mut self,
new: &'b [VNode<'b>],
old: &'b [VNode<'b>],
muts: &mut Mutations<'b>,
) {
let new_is_keyed = new[0].key.is_some(); let new_is_keyed = new[0].key.is_some();
let old_is_keyed = old[0].key.is_some(); let old_is_keyed = old[0].key.is_some();
debug_assert!( debug_assert!(
@@ -304,9 +254,9 @@ impl<'b> VirtualDom {
"all siblings must be keyed or all siblings must be non-keyed" "all siblings must be keyed or all siblings must be non-keyed"
); );
if new_is_keyed && old_is_keyed { if new_is_keyed && old_is_keyed {
// self.diff_keyed_children(muts, old, new); self.diff_keyed_children(old, new);
} else { } else {
self.diff_non_keyed_children(muts, old, new); self.diff_non_keyed_children(old, new);
} }
} }
@@ -318,12 +268,7 @@ impl<'b> VirtualDom {
// [... parent] // [... parent]
// //
// the change list stack is in the same state when this function returns. // the change list stack is in the same state when this function returns.
fn diff_non_keyed_children( fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
&mut self,
muts: &mut Mutations<'b>,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
) {
use std::cmp::Ordering; use std::cmp::Ordering;
// Handled these cases in `diff_children` before calling this function. // Handled these cases in `diff_children` before calling this function.
@@ -331,379 +276,376 @@ impl<'b> VirtualDom {
debug_assert!(!old.is_empty()); debug_assert!(!old.is_empty());
match old.len().cmp(&new.len()) { match old.len().cmp(&new.len()) {
Ordering::Greater => self.remove_nodes(muts, &old[new.len()..]), Ordering::Greater => self.remove_nodes(&old[new.len()..]),
Ordering::Less => { Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
self.create_and_insert_after(muts, &new[old.len()..], old.last().unwrap())
}
Ordering::Equal => {} Ordering::Equal => {}
} }
for (new, old) in new.iter().zip(old.iter()) { for (new, old) in new.iter().zip(old.iter()) {
self.diff_vnode(muts, old, new); self.diff_node(old, new);
} }
} }
// // Diffing "keyed" children. // Diffing "keyed" children.
// // //
// // With keyed children, we care about whether we delete, move, or create nodes // With keyed children, we care about whether we delete, move, or create nodes
// // versus mutate existing nodes in place. Presumably there is some sort of CSS // versus mutate existing nodes in place. Presumably there is some sort of CSS
// // transition animation that makes the virtual DOM diffing algorithm // transition animation that makes the virtual DOM diffing algorithm
// // observable. By specifying keys for nodes, we know which virtual DOM nodes // observable. By specifying keys for nodes, we know which virtual DOM nodes
// // must reuse (or not reuse) the same physical DOM nodes. // must reuse (or not reuse) the same physical DOM nodes.
// // //
// // This is loosely based on Inferno's keyed patching implementation. However, we // This is loosely based on Inferno's keyed patching implementation. However, we
// // have to modify the algorithm since we are compiling the diff down into change // have to modify the algorithm since we are compiling the diff down into change
// // list instructions that will be executed later, rather than applying the // list instructions that will be executed later, rather than applying the
// // changes to the DOM directly as we compare virtual DOMs. // changes to the DOM directly as we compare virtual DOMs.
// // //
// // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
// // //
// // The stack is empty upon entry. // The stack is empty upon entry.
// fn diff_keyed_children( fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
// &mut self, if cfg!(debug_assertions) {
// muts: &mut Mutations<'b>, let mut keys = fxhash::FxHashSet::default();
// old: &'b [VNode<'b>], let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
// new: &'b [VNode<'b>], keys.clear();
// ) { for child in children {
// if cfg!(debug_assertions) { let key = child.key;
// let mut keys = fxhash::FxHashSet::default(); debug_assert!(
// let mut assert_unique_keys = |children: &'b [VNode<'b>]| { key.is_some(),
// keys.clear(); "if any sibling is keyed, all siblings must be keyed"
// for child in children { );
// let key = child.key; keys.insert(key);
// debug_assert!( }
// key.is_some(), debug_assert_eq!(
// "if any sibling is keyed, all siblings must be keyed" children.len(),
// ); keys.len(),
// keys.insert(key); "keyed siblings must each have a unique key"
// } );
// debug_assert_eq!( };
// children.len(), assert_unique_keys(old);
// keys.len(), assert_unique_keys(new);
// "keyed siblings must each have a unique key" }
// );
// };
// assert_unique_keys(old);
// assert_unique_keys(new);
// }
// // First up, we diff all the nodes with the same key at the beginning of the // First up, we diff all the nodes with the same key at the beginning of the
// // children. // children.
// // //
// // `shared_prefix_count` is the count of how many nodes at the start of // `shared_prefix_count` is the count of how many nodes at the start of
// // `new` and `old` share the same keys. // `new` and `old` share the same keys.
// let (left_offset, right_offset) = match self.diff_keyed_ends(muts, old, new) { let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
// Some(count) => count, Some(count) => count,
// None => return, None => return,
// }; };
// // Ok, we now hopefully have a smaller range of children in the middle // Ok, we now hopefully have a smaller range of children in the middle
// // within which to re-order nodes with the same keys, remove old nodes with // within which to re-order nodes with the same keys, remove old nodes with
// // now-unused keys, and create new nodes with fresh keys. // now-unused keys, and create new nodes with fresh keys.
// let old_middle = &old[left_offset..(old.len() - right_offset)]; let old_middle = &old[left_offset..(old.len() - right_offset)];
// let new_middle = &new[left_offset..(new.len() - right_offset)]; let new_middle = &new[left_offset..(new.len() - right_offset)];
// debug_assert!( debug_assert!(
// !((old_middle.len() == new_middle.len()) && old_middle.is_empty()), !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
// "keyed children must have the same number of children" "keyed children must have the same number of children"
// ); );
// if new_middle.is_empty() { if new_middle.is_empty() {
// // remove the old elements // remove the old elements
// self.remove_nodes(muts, old_middle); self.remove_nodes(old_middle);
// } else if old_middle.is_empty() { } else if old_middle.is_empty() {
// // there were no old elements, so just create the new elements // there were no old elements, so just create the new elements
// // we need to find the right "foothold" though - we shouldn't use the "append" at all // we need to find the right "foothold" though - we shouldn't use the "append" at all
// if left_offset == 0 { if left_offset == 0 {
// // insert at the beginning of the old list // insert at the beginning of the old list
// let foothold = &old[old.len() - right_offset]; let foothold = &old[old.len() - right_offset];
// self.create_and_insert_before(muts, new_middle, foothold); self.create_and_insert_before(new_middle, foothold);
// } else if right_offset == 0 { } else if right_offset == 0 {
// // insert at the end the old list // insert at the end the old list
// let foothold = old.last().unwrap(); let foothold = old.last().unwrap();
// self.create_and_insert_after(muts, new_middle, foothold); self.create_and_insert_after(new_middle, foothold);
// } else { } else {
// // inserting in the middle // inserting in the middle
// let foothold = &old[left_offset - 1]; let foothold = &old[left_offset - 1];
// self.create_and_insert_after(muts, new_middle, foothold); self.create_and_insert_after(new_middle, foothold);
// } }
// } else { } else {
// self.diff_keyed_middle(muts, old_middle, new_middle); self.diff_keyed_middle(old_middle, new_middle);
// } }
// } }
// /// Diff both ends of the children that share keys. /// Diff both ends of the children that share keys.
// /// ///
// /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing. /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
// /// ///
// /// If there is no offset, then this function returns None and the diffing is complete. /// If there is no offset, then this function returns None and the diffing is complete.
// fn diff_keyed_ends( fn diff_keyed_ends(
// &mut self, &mut self,
// muts: &mut Mutations<'b>, old: &'b [VNode<'b>],
// old: &'b [VNode<'b>], new: &'b [VNode<'b>],
// new: &'b [VNode<'b>], ) -> Option<(usize, usize)> {
// ) -> Option<(usize, usize)> { let mut left_offset = 0;
// let mut left_offset = 0;
// for (old, new) in old.iter().zip(new.iter()) { for (old, new) in old.iter().zip(new.iter()) {
// // abort early if we finally run into nodes with different keys // abort early if we finally run into nodes with different keys
// if old.key != new.key { if old.key != new.key {
// break; break;
// } }
// self.diff_node(muts, old, new); self.diff_node(old, new);
// left_offset += 1; left_offset += 1;
// } }
// // If that was all of the old children, then create and append the remaining // If that was all of the old children, then create and append the remaining
// // new children and we're finished. // new children and we're finished.
// if left_offset == old.len() { if left_offset == old.len() {
// self.create_and_insert_after(&new[left_offset..], old.last().unwrap()); self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
// return None; return None;
// } }
// // And if that was all of the new children, then remove all of the remaining // And if that was all of the new children, then remove all of the remaining
// // old children and we're finished. // old children and we're finished.
// if left_offset == new.len() { if left_offset == new.len() {
// self.remove_nodes(muts, &old[left_offset..]); self.remove_nodes(&old[left_offset..]);
// return None; return None;
// } }
// // if the shared prefix is less than either length, then we need to walk backwards // if the shared prefix is less than either length, then we need to walk backwards
// let mut right_offset = 0; let mut right_offset = 0;
// for (old, new) in old.iter().rev().zip(new.iter().rev()) { for (old, new) in old.iter().rev().zip(new.iter().rev()) {
// // abort early if we finally run into nodes with different keys // abort early if we finally run into nodes with different keys
// if old.key != new.key { if old.key != new.key {
// break; break;
// } }
// self.diff_node(muts, old, new); self.diff_node(old, new);
// right_offset += 1; right_offset += 1;
// } }
// Some((left_offset, right_offset)) Some((left_offset, right_offset))
// } }
// // The most-general, expensive code path for keyed children diffing. // The most-general, expensive code path for keyed children diffing.
// // //
// // We find the longest subsequence within `old` of children that are relatively // We find the longest subsequence within `old` of children that are relatively
// // ordered the same way in `new` (via finding a longest-increasing-subsequence // ordered the same way in `new` (via finding a longest-increasing-subsequence
// // of the old child's index within `new`). The children that are elements of // of the old child's index within `new`). The children that are elements of
// // this subsequence will remain in place, minimizing the number of DOM moves we // this subsequence will remain in place, minimizing the number of DOM moves we
// // will have to do. // will have to do.
// // //
// // Upon entry to this function, the change list stack must be empty. // Upon entry to this function, the change list stack must be empty.
// // //
// // This function will load the appropriate nodes onto the stack and do diffing in place. // This function will load the appropriate nodes onto the stack and do diffing in place.
// // //
// // Upon exit from this function, it will be restored to that same self. // Upon exit from this function, it will be restored to that same self.
// #[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
// fn diff_keyed_middle( fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
// &mut self, /*
// muts: &mut Mutations<'b>, 1. Map the old keys into a numerical ordering based on indices.
// old: &'b [VNode<'b>], 2. Create a map of old key to its index
// new: &'b [VNode<'b>], 3. Map each new key to the old key, carrying over the old index.
// ) { - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
// /* - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
// 1. Map the old keys into a numerical ordering based on indices.
// 2. Create a map of old key to its index
// 3. Map each new key to the old key, carrying over the old index.
// - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
// - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
// now, we should have a list of integers that indicates where in the old list the new items map to. now, we should have a list of integers that indicates where in the old list the new items map to.
// 4. Compute the LIS of this list 4. Compute the LIS of this list
// - this indicates the longest list of new children that won't need to be moved. - this indicates the longest list of new children that won't need to be moved.
// 5. Identify which nodes need to be removed 5. Identify which nodes need to be removed
// 6. Identify which nodes will need to be diffed 6. Identify which nodes will need to be diffed
// 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS. 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.
// - if the item already existed, just move it to the right place. - if the item already existed, just move it to the right place.
// 8. Finally, generate instructions to remove any old children. 8. Finally, generate instructions to remove any old children.
// 9. Generate instructions to finally diff children that are the same between both 9. Generate instructions to finally diff children that are the same between both
// */ */
// // 0. Debug sanity checks // 0. Debug sanity checks
// // Should have already diffed the shared-key prefixes and suffixes. // Should have already diffed the shared-key prefixes and suffixes.
// debug_assert_ne!(new.first().map(|i| i.key), old.first().map(|i| i.key)); debug_assert_ne!(new.first().map(|i| i.key), old.first().map(|i| i.key));
// debug_assert_ne!(new.last().map(|i| i.key), old.last().map(|i| i.key)); debug_assert_ne!(new.last().map(|i| i.key), old.last().map(|i| i.key));
// // 1. Map the old keys into a numerical ordering based on indices. // 1. Map the old keys into a numerical ordering based on indices.
// // 2. Create a map of old key to its index // 2. Create a map of old key to its index
// // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3). // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
// let old_key_to_old_index = old let old_key_to_old_index = old
// .iter() .iter()
// .enumerate() .enumerate()
// .map(|(i, o)| (o.key.unwrap(), i)) .map(|(i, o)| (o.key.unwrap(), i))
// .collect::<FxHashMap<_, _>>(); .collect::<FxHashMap<_, _>>();
// let mut shared_keys = FxHashSet::default(); let mut shared_keys = FxHashSet::default();
// // 3. Map each new key to the old key, carrying over the old index. // 3. Map each new key to the old key, carrying over the old index.
// let new_index_to_old_index = new let new_index_to_old_index = new
// .iter() .iter()
// .map(|node| { .map(|node| {
// let key = node.key.unwrap(); let key = node.key.unwrap();
// if let Some(&index) = old_key_to_old_index.get(&key) { if let Some(&index) = old_key_to_old_index.get(&key) {
// shared_keys.insert(key); shared_keys.insert(key);
// index index
// } else { } else {
// u32::MAX as usize u32::MAX as usize
// } }
// }) })
// .collect::<Vec<_>>(); .collect::<Vec<_>>();
// // If none of the old keys are reused by the new children, then we remove all the remaining old children and // If none of the old keys are reused by the new children, then we remove all the remaining old children and
// // create the new children afresh. // create the new children afresh.
// if shared_keys.is_empty() { if shared_keys.is_empty() {
// if let Some(first_old) = old.get(0) { if let Some(first_old) = old.get(0) {
// self.remove_nodes(muts, &old[1..]); self.remove_nodes(&old[1..]);
// let nodes_created = self.create_children(new); let nodes_created = self.create_children(new);
// self.replace_inner(first_old, nodes_created); self.replace_node_with_on_stack(first_old, nodes_created);
// } else { } else {
// // I think this is wrong - why are we appending? // I think this is wrong - why are we appending?
// // only valid of the if there are no trailing elements // only valid of the if there are no trailing elements
// self.create_and_append_children(new); // self.create_and_append_children(new);
// }
// return;
// }
// // remove any old children that are not shared todo!()
// // todo: make this an iterator }
// for child in old { return;
// let key = child.key.unwrap(); }
// if !shared_keys.contains(&key) {
// todo!("remove node");
// // self.remove_nodes(muts, [child]);
// }
// }
// // 4. Compute the LIS of this list // remove any old children that are not shared
// let mut lis_sequence = Vec::default(); // todo: make this an iterator
// lis_sequence.reserve(new_index_to_old_index.len()); for child in old {
let key = child.key.unwrap();
if !shared_keys.contains(&key) {
todo!("remove node");
// self.remove_nodes( [child]);
}
}
// let mut predecessors = vec![0; new_index_to_old_index.len()]; // 4. Compute the LIS of this list
// let mut starts = vec![0; new_index_to_old_index.len()]; let mut lis_sequence = Vec::default();
lis_sequence.reserve(new_index_to_old_index.len());
// longest_increasing_subsequence::lis_with( let mut predecessors = vec![0; new_index_to_old_index.len()];
// &new_index_to_old_index, let mut starts = vec![0; new_index_to_old_index.len()];
// &mut lis_sequence,
// |a, b| a < b,
// &mut predecessors,
// &mut starts,
// );
// // the lis comes out backwards, I think. can't quite tell. longest_increasing_subsequence::lis_with(
// lis_sequence.sort_unstable(); &new_index_to_old_index,
&mut lis_sequence,
|a, b| a < b,
&mut predecessors,
&mut starts,
);
// // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS) // the lis comes out backwards, I think. can't quite tell.
// if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) { lis_sequence.sort_unstable();
// lis_sequence.pop();
// }
// for idx in &lis_sequence { // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
// self.diff_node(muts, &old[new_index_to_old_index[*idx]], &new[*idx]); if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
// } lis_sequence.pop();
}
// let mut nodes_created = 0; for idx in &lis_sequence {
self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
}
// // add mount instruction for the first items not covered by the lis let mut nodes_created = 0;
// let last = *lis_sequence.last().unwrap();
// if last < (new.len() - 1) {
// for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
// let new_idx = idx + last + 1;
// let old_index = new_index_to_old_index[new_idx];
// if old_index == u32::MAX as usize {
// nodes_created += self.create(muts, new_node);
// } else {
// self.diff_node(muts, &old[old_index], new_node);
// nodes_created += self.push_all_real_nodes(new_node);
// }
// }
// self.mutations.insert_after( // add mount instruction for the first items not covered by the lis
// self.find_last_element(&new[last]).unwrap(), let last = *lis_sequence.last().unwrap();
// nodes_created as u32, if last < (new.len() - 1) {
// ); for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
// nodes_created = 0; let new_idx = idx + last + 1;
// } let old_index = new_index_to_old_index[new_idx];
if old_index == u32::MAX as usize {
nodes_created += self.create(new_node);
} else {
self.diff_node(&old[old_index], new_node);
nodes_created += self.push_all_real_nodes(new_node);
}
}
// // for each spacing, generate a mount instruction let id = self.find_last_element(&new[last]);
// let mut lis_iter = lis_sequence.iter().rev(); self.mutations.push(Mutation::InsertAfter {
// let mut last = *lis_iter.next().unwrap(); id,
// for next in lis_iter { m: nodes_created,
// if last - next > 1 { });
// for (idx, new_node) in new[(next + 1)..last].iter().enumerate() { nodes_created = 0;
// let new_idx = idx + next + 1; }
// let old_index = new_index_to_old_index[new_idx];
// if old_index == u32::MAX as usize {
// nodes_created += self.create(muts, new_node);
// } else {
// self.diff_node(muts, &old[old_index], new_node);
// nodes_created += self.push_all_real_nodes(new_node);
// }
// }
// self.mutations.insert_before( // for each spacing, generate a mount instruction
// self.find_first_element(&new[last]).unwrap(), let mut lis_iter = lis_sequence.iter().rev();
// nodes_created as u32, let mut last = *lis_iter.next().unwrap();
// ); for next in lis_iter {
if last - next > 1 {
for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
let new_idx = idx + next + 1;
let old_index = new_index_to_old_index[new_idx];
if old_index == u32::MAX as usize {
nodes_created += self.create(new_node);
} else {
self.diff_node(&old[old_index], new_node);
nodes_created += self.push_all_real_nodes(new_node);
}
}
// nodes_created = 0; let id = self.find_first_element(&new[last]);
// } self.mutations.push(Mutation::InsertBefore {
// last = *next; id,
// } m: nodes_created,
});
// // add mount instruction for the last items not covered by the lis nodes_created = 0;
// let first_lis = *lis_sequence.first().unwrap(); }
// if first_lis > 0 { last = *next;
// for (idx, new_node) in new[..first_lis].iter().enumerate() { }
// let old_index = new_index_to_old_index[idx];
// if old_index == u32::MAX as usize {
// nodes_created += self.create_node(new_node);
// } else {
// self.diff_node(muts, &old[old_index], new_node);
// nodes_created += self.push_all_real_nodes(new_node);
// }
// }
// self.mutations.insert_before( // add mount instruction for the last items not covered by the lis
// self.find_first_element(&new[first_lis]).unwrap(), let first_lis = *lis_sequence.first().unwrap();
// nodes_created as u32, if first_lis > 0 {
// ); for (idx, new_node) in new[..first_lis].iter().enumerate() {
// } let old_index = new_index_to_old_index[idx];
// } if old_index == u32::MAX as usize {
nodes_created += self.create(new_node);
} else {
self.diff_node(&old[old_index], new_node);
nodes_created += self.push_all_real_nodes(new_node);
}
}
let id = self.find_first_element(&new[first_lis]);
self.mutations.push(Mutation::InsertBefore {
id,
m: nodes_created,
});
}
}
fn insert_after(&mut self, node: &'b VNode<'b>, nodes_created: usize) {}
fn insert_before(&mut self, node: &'b VNode<'b>, nodes_created: usize) {}
fn replace_node_with_on_stack(&mut self, old: &'b VNode<'b>, m: usize) {
todo!()
}
/// Remove these nodes from the dom /// Remove these nodes from the dom
/// Wont generate mutations for the inner nodes /// Wont generate mutations for the inner nodes
fn remove_nodes(&mut self, muts: &mut Mutations<'b>, nodes: &'b [VNode<'b>]) { fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) {
// //
} }
/// Push all the real nodes on the stack /// Push all the real nodes on the stack
fn push_elements_onto_stack(&mut self, node: &VNode) -> usize { fn push_all_real_nodes(&mut self, node: &VNode) -> usize {
todo!() todo!()
} }
pub(crate) fn create_and_insert_before( fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
&self, todo!()
mutations: &mut Mutations<'b>,
new: &[VNode],
after: &VNode,
) {
let id = self.get_last_real_node(after);
}
pub(crate) fn create_and_insert_after(
&self,
mutations: &mut Mutations<'b>,
new: &[VNode],
after: &VNode,
) {
let id = self.get_last_real_node(after);
} }
fn get_last_real_node(&self, node: &VNode) -> ElementId { pub(crate) fn create_and_insert_before(&self, new: &[VNode], after: &VNode) {
let id = self.find_last_element(after);
}
pub(crate) fn create_and_insert_after(&self, new: &[VNode], after: &VNode) {
let id = self.find_last_element(after);
}
fn find_first_element(&self, node: &VNode) -> ElementId {
todo!()
}
fn find_last_element(&self, node: &VNode) -> ElementId {
match node.template.roots.last().unwrap() { match node.template.roots.last().unwrap() {
TemplateNode::Element { .. } => todo!(), TemplateNode::Element { .. } => todo!(),
TemplateNode::Text(t) => todo!(), TemplateNode::Text(t) => todo!(),
@@ -711,6 +653,15 @@ impl<'b> VirtualDom {
TemplateNode::DynamicText(_) => todo!(), TemplateNode::DynamicText(_) => todo!(),
} }
} }
fn replace(
&mut self,
left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>,
left: &'b DynamicNode<'b>,
right: &'b DynamicNode<'b>,
) {
}
} }
fn matching_components<'a>( fn matching_components<'a>(
+1
View File
@@ -17,6 +17,7 @@ mod scheduler;
mod scope_arena; mod scope_arena;
mod scopes; mod scopes;
mod virtual_dom; mod virtual_dom;
pub(crate) mod innerlude { pub(crate) mod innerlude {
pub use crate::arena::*; pub use crate::arena::*;
pub use crate::dirty_scope::*; pub use crate::dirty_scope::*;
+20 -2
View File
@@ -8,9 +8,9 @@ pub struct Mutations<'a> {
} }
impl<'a> Mutations<'a> { impl<'a> Mutations<'a> {
pub fn new(subtree: usize) -> Self { pub fn new() -> Self {
Self { Self {
subtree, subtree: 0,
edits: Vec::new(), edits: Vec::new(),
template_mutations: Vec::new(), template_mutations: Vec::new(),
} }
@@ -88,6 +88,24 @@ pub enum Mutation<'a> {
path: &'static [u8], path: &'static [u8],
}, },
/// Insert a number of nodes after a given node.
InsertAfter {
/// The ID of the node to insert after.
id: ElementId,
/// The ids of the nodes to insert after the target node.
m: usize,
},
/// Insert a number of nodes before a given node.
InsertBefore {
/// The ID of the node to insert before.
id: ElementId,
/// The ids of the nodes to insert before the target node.
m: usize,
},
SaveTemplate { SaveTemplate {
name: &'static str, name: &'static str,
m: usize, m: usize,
+1 -1
View File
@@ -41,7 +41,7 @@ impl SuspenseBoundary {
Rc::new(Self { Rc::new(Self {
id, id,
waiting_on: Default::default(), waiting_on: Default::default(),
mutations: RefCell::new(Mutations::new(0)), mutations: RefCell::new(Mutations::new()),
placeholder: Cell::new(None), placeholder: Cell::new(None),
created_on_stack: Cell::new(0), created_on_stack: Cell::new(0),
onresolve: None, onresolve: None,
+9 -8
View File
@@ -78,14 +78,15 @@ impl VirtualDom {
let template: &VNode = unsafe { std::mem::transmute(template) }; let template: &VNode = unsafe { std::mem::transmute(template) };
let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) }; let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) };
let place_holder_id = scope.placeholder.get().unwrap(); todo!();
self.scope_stack.push(scope_id); // let place_holder_id = scope.placeholder.get().unwrap();
let created = self.create(mutations, template); // self.scope_stack.push(scope_id);
self.scope_stack.pop(); // let created = self.create(mutations, template);
mutations.push(Mutation::ReplaceWith { // self.scope_stack.pop();
id: place_holder_id, // mutations.push(Mutation::ReplaceWith {
m: created, // id: place_holder_id,
}); // m: created,
// });
// for leaf in self.collected_leaves.drain(..) { // for leaf in self.collected_leaves.drain(..) {
// fiber.waiting_on.borrow_mut().insert(leaf); // fiber.waiting_on.borrow_mut().insert(leaf);
+43 -28
View File
@@ -165,6 +165,8 @@ pub struct VirtualDom {
pub(crate) finished_fibers: Vec<ScopeId>, pub(crate) finished_fibers: Vec<ScopeId>,
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>, pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
pub(crate) mutations: Mutations<'static>,
} }
impl VirtualDom { impl VirtualDom {
@@ -237,6 +239,7 @@ impl VirtualDom {
dirty_scopes: BTreeSet::new(), dirty_scopes: BTreeSet::new(),
collected_leaves: Vec::new(), collected_leaves: Vec::new(),
finished_fibers: Vec::new(), finished_fibers: Vec::new(),
mutations: Mutations::new(),
}; };
let root = dom.new_scope(Box::leak(Box::new(VProps::new( let root = dom.new_scope(Box::leak(Box::new(VProps::new(
@@ -447,6 +450,14 @@ impl VirtualDom {
} }
} }
/// Swap the current mutations with a new
fn finalize<'a>(&'a mut self) -> Mutations<'a> {
// todo: make this a routine
let mut out = Mutations::new();
std::mem::swap(&mut self.mutations, &mut out);
out
}
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
/// ///
/// The mutations item expects the RealDom's stack to be the root of the application. /// The mutations item expects the RealDom's stack to be the root of the application.
@@ -467,21 +478,19 @@ impl VirtualDom {
/// ///
/// apply_edits(edits); /// apply_edits(edits);
/// ``` /// ```
pub fn rebuild(&mut self) -> Mutations { pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
let mut mutations = Mutations::new(0);
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } { match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
// Rebuilding implies we append the created elements to the root // Rebuilding implies we append the created elements to the root
RenderReturn::Sync(Ok(node)) => { RenderReturn::Sync(Ok(node)) => {
let m = self.create_scope(ScopeId(0), &mut mutations, node); let m = self.create_scope(ScopeId(0), node);
mutations.push(Mutation::AppendChildren { m }); self.mutations.push(Mutation::AppendChildren { m });
} }
// If an error occurs, we should try to render the default error component and context where the error occured // If an error occurs, we should try to render the default error component and context where the error occured
RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e), RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"), RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
} }
mutations self.finalize()
} }
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
@@ -513,19 +522,19 @@ impl VirtualDom {
) -> Mutations<'a> { ) -> Mutations<'a> {
pin_mut!(deadline); pin_mut!(deadline);
let mut mutations = Mutations::new(0);
loop { loop {
// first, unload any complete suspense trees // first, unload any complete suspense trees
for finished_fiber in self.finished_fibers.drain(..) { for finished_fiber in self.finished_fibers.drain(..) {
let scope = &mut self.scopes[finished_fiber.0]; let scope = &mut self.scopes[finished_fiber.0];
let context = scope.has_context::<SuspenseContext>().unwrap(); let context = scope.has_context::<SuspenseContext>().unwrap();
mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..)); self.mutations
mutations.extend(context.mutations.borrow_mut().drain(..)); .extend(context.mutations.borrow_mut().template_mutations.drain(..));
self.mutations
.extend(context.mutations.borrow_mut().drain(..));
// TODO: count how many nodes are on the stack? // TODO: count how many nodes are on the stack?
mutations.push(Mutation::ReplaceWith { self.mutations.push(Mutation::ReplaceWith {
id: context.placeholder.get().unwrap(), id: context.placeholder.get().unwrap(),
m: 1, m: 1,
}) })
@@ -542,9 +551,9 @@ impl VirtualDom {
} }
// Save the current mutations length so we can split them into boundary // Save the current mutations length so we can split them into boundary
let mutations_to_this_point = mutations.len(); let mutations_to_this_point = self.mutations.len();
self.diff_scope(&mut mutations, dirty.id); self.diff_scope(dirty.id);
// If suspended leaves are present, then we should find the boundary for this scope and attach things // If suspended leaves are present, then we should find the boundary for this scope and attach things
// No placeholder necessary since this is a diff // No placeholder necessary since this is a diff
@@ -559,7 +568,7 @@ impl VirtualDom {
boundary_mut boundary_mut
.mutations .mutations
.borrow_mut() .borrow_mut()
.extend(mutations.split_off(mutations_to_this_point)); .extend(self.mutations.split_off(mutations_to_this_point));
// Attach suspended leaves // Attach suspended leaves
boundary boundary
@@ -569,22 +578,28 @@ impl VirtualDom {
} }
} }
// Wait for suspense, or a deadline // If there's more work, then just continue, plenty of work to do
if self.dirty_scopes.is_empty() { if !self.dirty_scopes.is_empty() {
// If there's no pending suspense, then we have no reason to wait continue;
if self.scheduler.leaves.borrow().is_empty() { }
return mutations;
}
// Poll the suspense leaves in the meantime // If there's no pending suspense, then we have no reason to wait for anything
let work = self.wait_for_work(); if self.scheduler.leaves.borrow().is_empty() {
pin_mut!(work); return self.finalize();
}
// If the deadline is exceded (left) then we should return the mutations we have // Poll the suspense leaves in the meantime
use futures_util::future::{select, Either}; let mut work = self.wait_for_work();
if let Either::Left((_, _)) = select(&mut deadline, work).await {
return mutations; // safety: this is okay since we don't touch the original future
} let pinned = unsafe { std::pin::Pin::new_unchecked(&mut work) };
// If the deadline is exceded (left) then we should return the mutations we have
use futures_util::future::{select, Either};
if let Either::Left((_, _)) = select(&mut deadline, pinned).await {
// release the borrowed
drop(work);
return self.finalize();
} }
} }
} }
+10
View File
@@ -18,3 +18,13 @@ fn root_node_isnt_null() {
// todo: there should also be a default error boundary // todo: there should also be a default error boundary
assert!(scope.has_context::<SuspenseContext>().is_some()); assert!(scope.has_context::<SuspenseContext>().is_some());
} }
#[test]
fn elements() {
let mut dom = VirtualDom::new(|cx| {
//
cx.render(rsx!( div { "Hello world!" } ))
});
let muts = dom.rebuild();
}
+104
View File
@@ -0,0 +1,104 @@
//! Do we create fragments properly across complex boundaries?
use dioxus::core::Mutation::*;
use dioxus::prelude::*;
use dioxus_core::ElementId;
#[test]
fn empty_fragment_creates_nothing() {
fn app(cx: Scope) -> Element {
cx.render(rsx!(()))
}
let mut vdom = VirtualDom::new(app);
let edits = vdom.rebuild();
assert_eq!(
edits.edits,
[
CreatePlaceholder { id: ElementId(2) },
AppendChildren { m: 1 }
]
);
}
#[test]
fn root_fragments_work() {
let mut vdom = VirtualDom::new(|cx| {
cx.render(rsx!(
div { "hello" }
div { "goodbye" }
))
});
assert_eq!(
vdom.rebuild().edits.last().unwrap(),
&AppendChildren { m: 2 }
);
}
#[test]
fn fragments_nested() {
let mut vdom = VirtualDom::new(|cx| {
cx.render(rsx!(
div { "hello" }
div { "goodbye" }
rsx! {
div { "hello" }
div { "goodbye" }
rsx! {
div { "hello" }
div { "goodbye" }
rsx! {
div { "hello" }
div { "goodbye" }
}
}
}
))
});
assert_eq!(
vdom.rebuild().edits.last().unwrap(),
&AppendChildren { m: 8 }
);
}
#[test]
fn fragments_across_components() {
fn app(cx: Scope) -> Element {
cx.render(rsx! {
demo_child {}
demo_child {}
demo_child {}
demo_child {}
})
}
fn demo_child(cx: Scope) -> Element {
let world = "world";
cx.render(rsx! {
"hellO!"
world
})
}
assert_eq!(
VirtualDom::new(app).rebuild().edits.last().unwrap(),
&AppendChildren { m: 8 }
);
}
#[test]
fn list_fragments() {
fn app(cx: Scope) -> Element {
cx.render(rsx!(
h1 {"hello"}
(0..6).map(|f| rsx!( span { "{f}" }))
))
}
assert_eq!(
VirtualDom::new(app).rebuild().edits.last().unwrap(),
&AppendChildren { m: 7 }
);
}