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::innerlude::{Mutations, VComponent, VFragment, VText};
use crate::innerlude::{VComponent, VFragment, VText};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
use crate::nodes::VNode;
@@ -7,33 +7,24 @@ use crate::nodes::{DynamicNode, TemplateNode};
use crate::virtual_dom::VirtualDom;
use crate::{AttributeValue, ScopeId, SuspenseContext, TemplateAttribute};
impl VirtualDom {
impl<'b: 'static> VirtualDom {
/// 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.
pub(crate) fn create_scope<'a>(
&mut self,
scope: ScopeId,
mutations: &mut Mutations<'a>,
template: &'a VNode<'a>,
) -> usize {
pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
self.scope_stack.push(scope);
let out = self.create(mutations, template);
let out = self.create(template);
self.scope_stack.pop();
out
}
/// Create this template and write its mutations
pub(crate) fn create<'a>(
&mut self,
mutations: &mut Mutations<'a>,
template: &'a VNode<'a>,
) -> usize {
pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize {
// The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways
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
@@ -47,7 +38,7 @@ impl VirtualDom {
for (root_idx, root) in template.template.roots.iter().enumerate() {
on_stack += match root {
TemplateNode::Element { .. } | TemplateNode::Text(_) => {
mutations.push(LoadTemplate {
self.mutations.push(LoadTemplate {
name: template.template.id,
index: root_idx,
});
@@ -56,17 +47,27 @@ impl VirtualDom {
TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => {
match &template.dynamic_nodes[*id] {
DynamicNode::Fragment { .. } | DynamicNode::Component { .. } => self
.create_dynamic_node(
mutations,
DynamicNode::Fragment(frag) => match frag {
VFragment::Empty(slot) => {
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.dynamic_nodes[*id],
*id,
),
},
DynamicNode::Component { .. } => {
self.create_dynamic_node(template, &template.dynamic_nodes[*id], *id)
}
DynamicNode::Text(VText { id: slot, value }) => {
let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
mutations.push(CreateTextNode { value, id });
self.mutations.push(CreateTextNode { value, id });
1
}
}
@@ -79,7 +80,7 @@ impl VirtualDom {
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
{
let id = self.next_element(template, template.template.attr_paths[attr_id]);
mutations.push(AssignId {
self.mutations.push(AssignId {
path: &path[1..],
id,
});
@@ -89,18 +90,18 @@ impl VirtualDom {
attribute.mounted_element.set(id);
match &attribute.value {
AttributeValue::Text(value) => mutations.push(SetAttribute {
AttributeValue::Text(value) => self.mutations.push(SetAttribute {
name: attribute.name,
value: *value,
ns: attribute.namespace,
id,
}),
AttributeValue::Bool(value) => mutations.push(SetBoolAttribute {
AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
name: attribute.name,
value: *value,
id,
}),
AttributeValue::Listener(_) => mutations.push(NewEventListener {
AttributeValue::Listener(_) => self.mutations.push(NewEventListener {
// all listeners start with "on"
event_name: &attribute.name[2..],
scope: cur_scope,
@@ -140,9 +141,9 @@ impl VirtualDom {
if let (Some(start), Some(end)) = (start, end) {
for idx in start..=end {
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 {
mutations.push(ReplacePlaceholder {
self.mutations.push(ReplacePlaceholder {
m,
path: &template.template.node_paths[idx][1..],
});
@@ -155,12 +156,12 @@ impl VirtualDom {
}
/// 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 {
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,
m: template.template.roots.len(),
});
@@ -169,22 +170,27 @@ impl VirtualDom {
.insert(template.template.id, template.template);
}
pub(crate) fn create_static_node<'a>(
pub(crate) fn create_static_node(
&mut self,
mutations: &mut Vec<Mutation<'a>>,
template: &'a VNode<'a>,
node: &'a TemplateNode<'static>,
template: &'b VNode<'b>,
node: &'b TemplateNode<'static>,
) {
match *node {
// Todo: create the children's template
TemplateNode::Dynamic(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::DynamicText { .. } => mutations.push(CreateStaticText {
value: "placeholder",
}),
TemplateNode::Text(value) => self
.mutations
.template_mutations
.push(CreateStaticText { value }),
TemplateNode::DynamicText { .. } => self
.mutations
.template_mutations
.push(CreateStaticText { value: "d" }),
TemplateNode::Element {
attrs,
@@ -195,26 +201,28 @@ impl VirtualDom {
} => {
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,
namespace,
id,
});
mutations.extend(attrs.into_iter().filter_map(|attr| match attr {
TemplateAttribute::Static {
name,
value,
namespace,
..
} => Some(SetAttribute {
name,
value,
id,
ns: *namespace,
}),
_ => None,
}));
self.mutations
.template_mutations
.extend(attrs.into_iter().filter_map(|attr| match attr {
TemplateAttribute::Static {
name,
value,
namespace,
..
} => Some(SetAttribute {
name,
value,
id,
ns: *namespace,
}),
_ => None,
}));
if children.is_empty() && inner_opt {
return;
@@ -222,33 +230,33 @@ impl VirtualDom {
children
.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,
mutations: &mut Mutations<'a>,
template: &'a VNode<'a>,
node: &'a DynamicNode<'a>,
template: &'b VNode<'b>,
node: &'b DynamicNode<'b>,
idx: usize,
) -> usize {
use DynamicNode::*;
match node {
Text(text) => self.create_dynamic_text(mutations, template, text, idx),
Fragment(frag) => self.create_fragment(frag, template, idx, mutations),
Component(component) => self.create_component_node(mutations, template, component, idx),
Text(text) => self.create_dynamic_text(template, text, idx),
Fragment(frag) => self.create_fragment(frag, template, idx),
Component(component) => self.create_component_node(template, component, idx),
}
}
fn create_dynamic_text<'a>(
fn create_dynamic_text(
&mut self,
mutations: &mut Mutations<'a>,
template: &VNode<'a>,
text: &VText<'a>,
template: &'b VNode<'b>,
text: &'b VText<'b>,
idx: usize,
) -> usize {
// Allocate a dynamic element reference for this text node
@@ -258,7 +266,7 @@ impl VirtualDom {
text.id.set(new_id);
// Add the mutation to the list
mutations.push(HydrateText {
self.mutations.push(HydrateText {
id: new_id,
path: &template.template.node_paths[idx][1..],
value: text.value,
@@ -268,17 +276,16 @@ impl VirtualDom {
0
}
pub(crate) fn create_fragment<'a>(
pub(crate) fn create_fragment(
&mut self,
frag: &'a VFragment<'a>,
template: &'a VNode<'a>,
frag: &'b VFragment<'b>,
template: &'b VNode<'b>,
idx: usize,
mutations: &mut Mutations<'a>,
) -> usize {
match frag {
VFragment::NonEmpty(nodes) => nodes
.iter()
.fold(0, |acc, child| acc + self.create(mutations, child)),
VFragment::NonEmpty(nodes) => {
nodes.iter().fold(0, |acc, child| acc + self.create(child))
}
VFragment::Empty(slot) => {
// Allocate a dynamic element reference for this text node
@@ -288,7 +295,7 @@ impl VirtualDom {
slot.set(id);
// Assign the ID to the existing node in the template
mutations.push(AssignId {
self.mutations.push(AssignId {
path: &template.template.node_paths[idx][1..],
id,
});
@@ -299,11 +306,10 @@ impl VirtualDom {
}
}
fn create_component_node<'a>(
fn create_component_node(
&mut self,
mutations: &mut Mutations<'a>,
template: &'a VNode<'a>,
component: &'a VComponent<'a>,
template: &'b VNode<'b>,
component: &'b VComponent<'b>,
idx: usize,
) -> usize {
let props = component.props.replace(None).unwrap();
@@ -319,25 +325,19 @@ impl VirtualDom {
use RenderReturn::*;
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"),
Async(_) => self.mount_component_placeholder(template, idx, scope, mutations),
Async(_) => self.mount_component_placeholder(template, idx, scope),
}
}
fn mount_component<'a>(
&mut self,
mutations: &mut Mutations<'a>,
scope: ScopeId,
template: &'a VNode<'a>,
idx: usize,
) -> usize {
fn mount_component(&mut self, scope: ScopeId, template: &'b VNode<'b>, 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
// is encountered
let mutations_to_this_point = mutations.len();
let mutations_to_this_point = self.mutations.len();
// 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 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
let split_off = unsafe {
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);
@@ -371,7 +371,7 @@ impl VirtualDom {
.extend(self.collected_leaves.drain(..));
// Now assign the placeholder in the DOM
mutations.push(AssignId {
self.mutations.push(AssignId {
id: new_id,
path: &template.template.node_paths[idx][1..],
});
@@ -387,7 +387,6 @@ impl VirtualDom {
template: &VNode,
idx: usize,
scope: ScopeId,
mutations: &mut Mutations,
) -> usize {
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));
// 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,
path: &template.template.node_paths[idx][1..],
});
+357 -406
View File
@@ -1,4 +1,5 @@
use std::any::Any;
use std::cell::Cell;
use crate::factory::RenderReturn;
use crate::innerlude::{Mutations, VComponent, VFragment, VText};
@@ -21,8 +22,8 @@ use crate::{
use fxhash::{FxHashMap, FxHashSet};
use slab::Slab;
impl<'b> VirtualDom {
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
impl<'b: 'static> VirtualDom {
pub fn diff_scope(&mut self, scope: ScopeId) {
let scope_state = &mut self.scopes[scope.0];
// Load the old and new bump arenas
@@ -46,21 +47,16 @@ impl<'b> VirtualDom {
unsafe {
let cur_arena = cur_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();
}
fn diff_maybe_node(
&mut self,
m: &mut Mutations<'b>,
left: &'b RenderReturn<'b>,
right: &'b RenderReturn<'b>,
) {
fn diff_maybe_node(&mut self, left: &'b RenderReturn<'b>, right: &'b RenderReturn<'b>) {
use RenderReturn::{Async, Sync};
match (left, right) {
// 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"),
// // remove old with placeholder
@@ -83,14 +79,9 @@ impl<'b> VirtualDom {
}
}
pub fn diff_vnode(
&mut self,
muts: &mut Mutations<'b>,
left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>,
) {
pub fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
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
@@ -107,7 +98,7 @@ impl<'b> VirtualDom {
// todo: add more types of attribute values
match right_attr.value {
AttributeValue::Text(text) => {
muts.push(Mutation::SetAttribute {
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
name: left_attr.name,
value: text,
@@ -126,30 +117,15 @@ impl<'b> VirtualDom {
.zip(right_template.dynamic_nodes.iter())
{
match (left_node, right_node) {
(Text(left), Text(right)) => self.diff_vtext(muts, left, right),
(Fragment(left), Fragment(right)) => self.diff_vfragment(muts, left, right),
(Component(left), Component(right)) => self.diff_vcomponent(muts, left, right),
_ => self.replace(muts, left_template, right_template, left_node, right_node),
(Text(left), Text(right)) => self.diff_vtext(left, right),
(Fragment(left), Fragment(right)) => self.diff_vfragment(left, right),
(Component(left), Component(right)) => self.diff_vcomponent(left, right),
_ => self.replace(left_template, right_template, left_node, right_node),
};
}
}
fn replace(
&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>,
) {
fn diff_vcomponent(&mut self, 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
// this codepath is through "light_diff", but we check there that the pointers are the same
assert_eq!(left.render_fn, right.render_fn);
@@ -177,7 +153,7 @@ impl<'b> VirtualDom {
// Now run the component and diff it
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.
@@ -218,16 +194,11 @@ impl<'b> VirtualDom {
/// Component { ..props }
/// }
/// ```
fn light_diff_templates(
&mut self,
muts: &mut Mutations<'b>,
left: &'b VNode<'b>,
right: &'b VNode<'b>,
) {
fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
if let Some(components) = matching_components(left, right) {
components
.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
/// 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());
if left.value != right.value {
muts.push(Mutation::SetText {
self.mutations.push(Mutation::SetText {
id: left.id.get(),
value: right.value,
});
}
}
fn diff_vfragment(
&mut self,
muts: &mut Mutations<'b>,
left: &'b VFragment<'b>,
right: &'b VFragment<'b>,
) {
fn diff_vfragment(&mut self, left: &'b VFragment<'b>, right: &'b VFragment<'b>) {
use VFragment::*;
match (left, right) {
(Empty(l), Empty(r)) => r.set(l.get()),
(Empty(l), NonEmpty(r)) => self.replace_placeholder_with_nodes(muts, l, r),
(NonEmpty(l), Empty(r)) => self.replace_nodes_with_placeholder(muts, l, r),
(NonEmpty(old), NonEmpty(new)) => self.diff_non_empty_fragment(new, old, muts),
(Empty(l), NonEmpty(r)) => self.replace_placeholder_with_nodes(l, r),
(NonEmpty(l), Empty(r)) => self.replace_nodes_with_placeholder(l, r),
(NonEmpty(old), NonEmpty(new)) => self.diff_non_empty_fragment(new, old),
}
}
fn replace_placeholder_with_nodes(
&mut self,
muts: &mut Mutations<'b>,
l: &'b std::cell::Cell<ElementId>,
r: &'b [VNode<'b>],
) {
let created = r
.iter()
.fold(0, |acc, child| acc + self.create(muts, child));
muts.push(Mutation::ReplaceWith {
fn replace_placeholder_with_nodes(&mut self, l: &'b Cell<ElementId>, r: &'b [VNode<'b>]) {
let created = r.iter().fold(0, |acc, child| acc + self.create(child));
self.mutations.push(Mutation::ReplaceWith {
id: l.get(),
m: created,
})
}
fn replace_nodes_with_placeholder(
&mut self,
muts: &mut Mutations<'b>,
l: &'b [VNode<'b>],
r: &'b std::cell::Cell<ElementId>,
) {
fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell<ElementId>) {
//
// Remove the old nodes, except for one
self.remove_nodes(muts, &l[1..]);
self.remove_nodes(&l[1..]);
}
fn diff_non_empty_fragment(
&mut self,
new: &'b [VNode<'b>],
old: &'b [VNode<'b>],
muts: &mut Mutations<'b>,
) {
fn diff_non_empty_fragment(&mut self, new: &'b [VNode<'b>], old: &'b [VNode<'b>]) {
let new_is_keyed = new[0].key.is_some();
let old_is_keyed = old[0].key.is_some();
debug_assert!(
@@ -304,9 +254,9 @@ impl<'b> VirtualDom {
"all siblings must be keyed or all siblings must be non-keyed"
);
if new_is_keyed && old_is_keyed {
// self.diff_keyed_children(muts, old, new);
self.diff_keyed_children(old, new);
} else {
self.diff_non_keyed_children(muts, old, new);
self.diff_non_keyed_children(old, new);
}
}
@@ -318,12 +268,7 @@ impl<'b> VirtualDom {
// [... parent]
//
// the change list stack is in the same state when this function returns.
fn diff_non_keyed_children(
&mut self,
muts: &mut Mutations<'b>,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
) {
fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
use std::cmp::Ordering;
// Handled these cases in `diff_children` before calling this function.
@@ -331,379 +276,376 @@ impl<'b> VirtualDom {
debug_assert!(!old.is_empty());
match old.len().cmp(&new.len()) {
Ordering::Greater => self.remove_nodes(muts, &old[new.len()..]),
Ordering::Less => {
self.create_and_insert_after(muts, &new[old.len()..], old.last().unwrap())
}
Ordering::Greater => self.remove_nodes(&old[new.len()..]),
Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
Ordering::Equal => {}
}
for (new, old) in new.iter().zip(old.iter()) {
self.diff_vnode(muts, old, new);
self.diff_node(old, new);
}
}
// // Diffing "keyed" children.
// //
// // 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
// // transition animation that makes the virtual DOM diffing algorithm
// // observable. By specifying keys for nodes, we know which virtual DOM nodes
// // must reuse (or not reuse) the same physical DOM nodes.
// //
// // 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
// // list instructions that will be executed later, rather than applying the
// // 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
// //
// // The stack is empty upon entry.
// fn diff_keyed_children(
// &mut self,
// muts: &mut Mutations<'b>,
// old: &'b [VNode<'b>],
// new: &'b [VNode<'b>],
// ) {
// if cfg!(debug_assertions) {
// let mut keys = fxhash::FxHashSet::default();
// let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
// keys.clear();
// for child in children {
// let key = child.key;
// debug_assert!(
// key.is_some(),
// "if any sibling is keyed, all siblings must be keyed"
// );
// keys.insert(key);
// }
// debug_assert_eq!(
// children.len(),
// keys.len(),
// "keyed siblings must each have a unique key"
// );
// };
// assert_unique_keys(old);
// assert_unique_keys(new);
// }
// Diffing "keyed" children.
//
// 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
// transition animation that makes the virtual DOM diffing algorithm
// observable. By specifying keys for nodes, we know which virtual DOM nodes
// must reuse (or not reuse) the same physical DOM nodes.
//
// 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
// list instructions that will be executed later, rather than applying the
// 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
//
// The stack is empty upon entry.
fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
if cfg!(debug_assertions) {
let mut keys = fxhash::FxHashSet::default();
let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
keys.clear();
for child in children {
let key = child.key;
debug_assert!(
key.is_some(),
"if any sibling is keyed, all siblings must be keyed"
);
keys.insert(key);
}
debug_assert_eq!(
children.len(),
keys.len(),
"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
// // children.
// //
// // `shared_prefix_count` is the count of how many nodes at the start of
// // `new` and `old` share the same keys.
// let (left_offset, right_offset) = match self.diff_keyed_ends(muts, old, new) {
// Some(count) => count,
// None => return,
// };
// First up, we diff all the nodes with the same key at the beginning of the
// children.
//
// `shared_prefix_count` is the count of how many nodes at the start of
// `new` and `old` share the same keys.
let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
Some(count) => count,
None => return,
};
// // Ok, we now hopefully have a smaller range of children in the middle
// // within which to re-order nodes with the same keys, remove old nodes with
// // now-unused keys, and create new nodes with fresh keys.
// Ok, we now hopefully have a smaller range of children in the middle
// within which to re-order nodes with the same keys, remove old nodes with
// now-unused keys, and create new nodes with fresh keys.
// let old_middle = &old[left_offset..(old.len() - right_offset)];
// let new_middle = &new[left_offset..(new.len() - right_offset)];
let old_middle = &old[left_offset..(old.len() - right_offset)];
let new_middle = &new[left_offset..(new.len() - right_offset)];
// debug_assert!(
// !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
// "keyed children must have the same number of children"
// );
debug_assert!(
!((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
"keyed children must have the same number of children"
);
// if new_middle.is_empty() {
// // remove the old elements
// self.remove_nodes(muts, old_middle);
// } else if old_middle.is_empty() {
// // 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
// if left_offset == 0 {
// // insert at the beginning of the old list
// let foothold = &old[old.len() - right_offset];
// self.create_and_insert_before(muts, new_middle, foothold);
// } else if right_offset == 0 {
// // insert at the end the old list
// let foothold = old.last().unwrap();
// self.create_and_insert_after(muts, new_middle, foothold);
// } else {
// // inserting in the middle
// let foothold = &old[left_offset - 1];
// self.create_and_insert_after(muts, new_middle, foothold);
// }
// } else {
// self.diff_keyed_middle(muts, old_middle, new_middle);
// }
// }
if new_middle.is_empty() {
// remove the old elements
self.remove_nodes(old_middle);
} else if old_middle.is_empty() {
// 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
if left_offset == 0 {
// insert at the beginning of the old list
let foothold = &old[old.len() - right_offset];
self.create_and_insert_before(new_middle, foothold);
} else if right_offset == 0 {
// insert at the end the old list
let foothold = old.last().unwrap();
self.create_and_insert_after(new_middle, foothold);
} else {
// inserting in the middle
let foothold = &old[left_offset - 1];
self.create_and_insert_after(new_middle, foothold);
}
} else {
self.diff_keyed_middle(old_middle, new_middle);
}
}
// /// 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.
// ///
// /// If there is no offset, then this function returns None and the diffing is complete.
// fn diff_keyed_ends(
// &mut self,
// muts: &mut Mutations<'b>,
// old: &'b [VNode<'b>],
// new: &'b [VNode<'b>],
// ) -> Option<(usize, usize)> {
// let mut left_offset = 0;
/// 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.
///
/// If there is no offset, then this function returns None and the diffing is complete.
fn diff_keyed_ends(
&mut self,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
) -> Option<(usize, usize)> {
let mut left_offset = 0;
// for (old, new) in old.iter().zip(new.iter()) {
// // abort early if we finally run into nodes with different keys
// if old.key != new.key {
// break;
// }
// self.diff_node(muts, old, new);
// left_offset += 1;
// }
for (old, new) in old.iter().zip(new.iter()) {
// abort early if we finally run into nodes with different keys
if old.key != new.key {
break;
}
self.diff_node(old, new);
left_offset += 1;
}
// // If that was all of the old children, then create and append the remaining
// // new children and we're finished.
// if left_offset == old.len() {
// self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
// return None;
// }
// If that was all of the old children, then create and append the remaining
// new children and we're finished.
if left_offset == old.len() {
self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
return None;
}
// // And if that was all of the new children, then remove all of the remaining
// // old children and we're finished.
// if left_offset == new.len() {
// self.remove_nodes(muts, &old[left_offset..]);
// return None;
// }
// And if that was all of the new children, then remove all of the remaining
// old children and we're finished.
if left_offset == new.len() {
self.remove_nodes(&old[left_offset..]);
return None;
}
// // if the shared prefix is less than either length, then we need to walk backwards
// let mut right_offset = 0;
// for (old, new) in old.iter().rev().zip(new.iter().rev()) {
// // abort early if we finally run into nodes with different keys
// if old.key != new.key {
// break;
// }
// self.diff_node(muts, old, new);
// right_offset += 1;
// }
// if the shared prefix is less than either length, then we need to walk backwards
let mut right_offset = 0;
for (old, new) in old.iter().rev().zip(new.iter().rev()) {
// abort early if we finally run into nodes with different keys
if old.key != new.key {
break;
}
self.diff_node(old, new);
right_offset += 1;
}
// Some((left_offset, right_offset))
// }
Some((left_offset, right_offset))
}
// // The most-general, expensive code path for keyed children diffing.
// //
// // We find the longest subsequence within `old` of children that are relatively
// // 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
// // this subsequence will remain in place, minimizing the number of DOM moves we
// // will have to do.
// //
// // 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.
// //
// // Upon exit from this function, it will be restored to that same self.
// #[allow(clippy::too_many_lines)]
// fn diff_keyed_middle(
// &mut self,
// muts: &mut Mutations<'b>,
// old: &'b [VNode<'b>],
// new: &'b [VNode<'b>],
// ) {
// /*
// 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
// The most-general, expensive code path for keyed children diffing.
//
// We find the longest subsequence within `old` of children that are relatively
// 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
// this subsequence will remain in place, minimizing the number of DOM moves we
// will have to do.
//
// 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.
//
// Upon exit from this function, it will be restored to that same self.
#[allow(clippy::too_many_lines)]
fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
/*
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
// - this indicates the longest list of new children that won't need to be moved.
4. Compute the LIS of this list
- this indicates the longest list of new children that won't need to be moved.
// 5. Identify which nodes need to be removed
// 6. Identify which nodes will need to be diffed
5. Identify which nodes need to be removed
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.
// - if the item already existed, just move it to the right place.
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.
// 8. Finally, generate instructions to remove any old children.
// 9. Generate instructions to finally diff children that are the same between both
// */
// // 0. Debug sanity checks
// // 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.last().map(|i| i.key), old.last().map(|i| i.key));
8. Finally, generate instructions to remove any old children.
9. Generate instructions to finally diff children that are the same between both
*/
// 0. Debug sanity checks
// 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.last().map(|i| i.key), old.last().map(|i| i.key));
// // 1. Map the old keys into a numerical ordering based on indices.
// // 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).
// let old_key_to_old_index = old
// .iter()
// .enumerate()
// .map(|(i, o)| (o.key.unwrap(), i))
// .collect::<FxHashMap<_, _>>();
// 1. Map the old keys into a numerical ordering based on indices.
// 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).
let old_key_to_old_index = old
.iter()
.enumerate()
.map(|(i, o)| (o.key.unwrap(), i))
.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.
// let new_index_to_old_index = new
// .iter()
// .map(|node| {
// let key = node.key.unwrap();
// if let Some(&index) = old_key_to_old_index.get(&key) {
// shared_keys.insert(key);
// index
// } else {
// u32::MAX as usize
// }
// })
// .collect::<Vec<_>>();
// 3. Map each new key to the old key, carrying over the old index.
let new_index_to_old_index = new
.iter()
.map(|node| {
let key = node.key.unwrap();
if let Some(&index) = old_key_to_old_index.get(&key) {
shared_keys.insert(key);
index
} else {
u32::MAX as usize
}
})
.collect::<Vec<_>>();
// // 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.
// if shared_keys.is_empty() {
// if let Some(first_old) = old.get(0) {
// self.remove_nodes(muts, &old[1..]);
// let nodes_created = self.create_children(new);
// self.replace_inner(first_old, nodes_created);
// } else {
// // I think this is wrong - why are we appending?
// // only valid of the if there are no trailing elements
// self.create_and_append_children(new);
// }
// return;
// }
// 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.
if shared_keys.is_empty() {
if let Some(first_old) = old.get(0) {
self.remove_nodes(&old[1..]);
let nodes_created = self.create_children(new);
self.replace_node_with_on_stack(first_old, nodes_created);
} else {
// I think this is wrong - why are we appending?
// only valid of the if there are no trailing elements
// self.create_and_append_children(new);
// // remove any old children that are not shared
// // todo: make this an iterator
// for child in old {
// let key = child.key.unwrap();
// if !shared_keys.contains(&key) {
// todo!("remove node");
// // self.remove_nodes(muts, [child]);
// }
// }
todo!()
}
return;
}
// // 4. Compute the LIS of this list
// let mut lis_sequence = Vec::default();
// lis_sequence.reserve(new_index_to_old_index.len());
// remove any old children that are not shared
// todo: make this an iterator
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()];
// let mut starts = vec![0; new_index_to_old_index.len()];
// 4. Compute the LIS of this list
let mut lis_sequence = Vec::default();
lis_sequence.reserve(new_index_to_old_index.len());
// longest_increasing_subsequence::lis_with(
// &new_index_to_old_index,
// &mut lis_sequence,
// |a, b| a < b,
// &mut predecessors,
// &mut starts,
// );
let mut predecessors = vec![0; new_index_to_old_index.len()];
let mut starts = vec![0; new_index_to_old_index.len()];
// // the lis comes out backwards, I think. can't quite tell.
// lis_sequence.sort_unstable();
longest_increasing_subsequence::lis_with(
&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)
// if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
// lis_sequence.pop();
// }
// the lis comes out backwards, I think. can't quite tell.
lis_sequence.sort_unstable();
// for idx in &lis_sequence {
// self.diff_node(muts, &old[new_index_to_old_index[*idx]], &new[*idx]);
// }
// 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)
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 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);
// }
// }
let mut nodes_created = 0;
// self.mutations.insert_after(
// self.find_last_element(&new[last]).unwrap(),
// nodes_created as u32,
// );
// nodes_created = 0;
// }
// add mount instruction for the first items not covered by the lis
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(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 mut lis_iter = lis_sequence.iter().rev();
// 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(muts, new_node);
// } else {
// self.diff_node(muts, &old[old_index], new_node);
// nodes_created += self.push_all_real_nodes(new_node);
// }
// }
let id = self.find_last_element(&new[last]);
self.mutations.push(Mutation::InsertAfter {
id,
m: nodes_created,
});
nodes_created = 0;
}
// self.mutations.insert_before(
// self.find_first_element(&new[last]).unwrap(),
// nodes_created as u32,
// );
// for each spacing, generate a mount instruction
let mut lis_iter = lis_sequence.iter().rev();
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;
// }
// last = *next;
// }
let id = self.find_first_element(&new[last]);
self.mutations.push(Mutation::InsertBefore {
id,
m: nodes_created,
});
// // add mount instruction for the last items not covered by the lis
// let first_lis = *lis_sequence.first().unwrap();
// 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_node(new_node);
// } else {
// self.diff_node(muts, &old[old_index], new_node);
// nodes_created += self.push_all_real_nodes(new_node);
// }
// }
nodes_created = 0;
}
last = *next;
}
// self.mutations.insert_before(
// self.find_first_element(&new[first_lis]).unwrap(),
// nodes_created as u32,
// );
// }
// }
// add mount instruction for the last items not covered by the lis
let first_lis = *lis_sequence.first().unwrap();
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
/// 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
fn push_elements_onto_stack(&mut self, node: &VNode) -> usize {
fn push_all_real_nodes(&mut self, node: &VNode) -> usize {
todo!()
}
pub(crate) fn create_and_insert_before(
&self,
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 create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
todo!()
}
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() {
TemplateNode::Element { .. } => todo!(),
TemplateNode::Text(t) => todo!(),
@@ -711,6 +653,15 @@ impl<'b> VirtualDom {
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>(
+1
View File
@@ -17,6 +17,7 @@ mod scheduler;
mod scope_arena;
mod scopes;
mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::arena::*;
pub use crate::dirty_scope::*;
+20 -2
View File
@@ -8,9 +8,9 @@ pub struct Mutations<'a> {
}
impl<'a> Mutations<'a> {
pub fn new(subtree: usize) -> Self {
pub fn new() -> Self {
Self {
subtree,
subtree: 0,
edits: Vec::new(),
template_mutations: Vec::new(),
}
@@ -88,6 +88,24 @@ pub enum Mutation<'a> {
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 {
name: &'static str,
m: usize,
+1 -1
View File
@@ -41,7 +41,7 @@ impl SuspenseBoundary {
Rc::new(Self {
id,
waiting_on: Default::default(),
mutations: RefCell::new(Mutations::new(0)),
mutations: RefCell::new(Mutations::new()),
placeholder: Cell::new(None),
created_on_stack: Cell::new(0),
onresolve: None,
+9 -8
View File
@@ -78,14 +78,15 @@ impl VirtualDom {
let template: &VNode = unsafe { std::mem::transmute(template) };
let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) };
let place_holder_id = scope.placeholder.get().unwrap();
self.scope_stack.push(scope_id);
let created = self.create(mutations, template);
self.scope_stack.pop();
mutations.push(Mutation::ReplaceWith {
id: place_holder_id,
m: created,
});
todo!();
// let place_holder_id = scope.placeholder.get().unwrap();
// self.scope_stack.push(scope_id);
// let created = self.create(mutations, template);
// self.scope_stack.pop();
// mutations.push(Mutation::ReplaceWith {
// id: place_holder_id,
// m: created,
// });
// for leaf in self.collected_leaves.drain(..) {
// 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) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
pub(crate) mutations: Mutations<'static>,
}
impl VirtualDom {
@@ -237,6 +239,7 @@ impl VirtualDom {
dirty_scopes: BTreeSet::new(),
collected_leaves: Vec::new(),
finished_fibers: Vec::new(),
mutations: Mutations::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.
///
/// The mutations item expects the RealDom's stack to be the root of the application.
@@ -467,21 +478,19 @@ impl VirtualDom {
///
/// apply_edits(edits);
/// ```
pub fn rebuild(&mut self) -> Mutations {
let mut mutations = Mutations::new(0);
pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
// Rebuilding implies we append the created elements to the root
RenderReturn::Sync(Ok(node)) => {
let m = self.create_scope(ScopeId(0), &mut mutations, node);
mutations.push(Mutation::AppendChildren { m });
let m = self.create_scope(ScopeId(0), node);
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
RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),
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
@@ -513,19 +522,19 @@ impl VirtualDom {
) -> Mutations<'a> {
pin_mut!(deadline);
let mut mutations = Mutations::new(0);
loop {
// first, unload any complete suspense trees
for finished_fiber in self.finished_fibers.drain(..) {
let scope = &mut self.scopes[finished_fiber.0];
let context = scope.has_context::<SuspenseContext>().unwrap();
mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..));
mutations.extend(context.mutations.borrow_mut().drain(..));
self.mutations
.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?
mutations.push(Mutation::ReplaceWith {
self.mutations.push(Mutation::ReplaceWith {
id: context.placeholder.get().unwrap(),
m: 1,
})
@@ -542,9 +551,9 @@ impl VirtualDom {
}
// 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
// No placeholder necessary since this is a diff
@@ -559,7 +568,7 @@ impl VirtualDom {
boundary_mut
.mutations
.borrow_mut()
.extend(mutations.split_off(mutations_to_this_point));
.extend(self.mutations.split_off(mutations_to_this_point));
// Attach suspended leaves
boundary
@@ -569,22 +578,28 @@ impl VirtualDom {
}
}
// Wait for suspense, or a deadline
if self.dirty_scopes.is_empty() {
// If there's no pending suspense, then we have no reason to wait
if self.scheduler.leaves.borrow().is_empty() {
return mutations;
}
// If there's more work, then just continue, plenty of work to do
if !self.dirty_scopes.is_empty() {
continue;
}
// Poll the suspense leaves in the meantime
let work = self.wait_for_work();
pin_mut!(work);
// If there's no pending suspense, then we have no reason to wait for anything
if self.scheduler.leaves.borrow().is_empty() {
return self.finalize();
}
// 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, work).await {
return mutations;
}
// Poll the suspense leaves in the meantime
let mut work = self.wait_for_work();
// 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
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 }
);
}