Partially undo #2673, different approach for the DerefMut impl of VList (#2692)

* partially undo #2673

VList again has a DerefMut implementation
the internal fully_keyed state now has an "indeterminate" variant
instead of being a bool, this recomputes it during reconciliation

* add Copy impl to FullyKeyedState
This commit is contained in:
WorldSEnder 2022-05-24 23:05:37 +02:00 committed by GitHub
parent b29b4535b7
commit b90c99af0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 80 additions and 69 deletions

View File

@ -54,12 +54,10 @@ pub fn render_markdown(src: &str) -> Html {
top = pre; top = pre;
} else if let Tag::Table(aligns) = tag { } else if let Tag::Table(aligns) = tag {
if let Some(top_children) = top.children_mut() { if let Some(top_children) = top.children_mut() {
for r in top_children.children_mut().iter_mut() { for r in top_children.iter_mut() {
if let VNode::VTag(ref mut vtag) = r { if let VNode::VTag(ref mut vtag) = r {
if let Some(vtag_children) = vtag.children_mut() { if let Some(vtag_children) = vtag.children_mut() {
for (i, c) in for (i, c) in vtag_children.iter_mut().enumerate() {
vtag_children.children_mut().iter_mut().enumerate()
{
if let VNode::VTag(ref mut vtag) = c { if let VNode::VTag(ref mut vtag) = c {
match aligns[i] { match aligns[i] {
Alignment::None => {} Alignment::None => {}
@ -75,7 +73,7 @@ pub fn render_markdown(src: &str) -> Html {
} }
} else if let Tag::TableHead = tag { } else if let Tag::TableHead = tag {
if let Some(top_children) = top.children_mut() { if let Some(top_children) = top.children_mut() {
for c in top_children.children_mut().iter_mut() { for c in top_children.iter_mut() {
if let VNode::VTag(ref mut vtag) = c { if let VNode::VTag(ref mut vtag) = c {
// TODO // TODO
// vtag.tag = "th".into(); // vtag.tag = "th".into();

View File

@ -444,6 +444,7 @@ impl Reconcilable for VList {
self.add_child(VText::new("").into()); self.add_child(VText::new("").into());
} }
let fully_keyed = self.fully_keyed();
let lefts = self.children; let lefts = self.children;
let rights = &mut blist.rev_children; let rights = &mut blist.rev_children;
test_log!("lefts: {:?}", lefts); test_log!("lefts: {:?}", lefts);
@ -452,12 +453,12 @@ impl Reconcilable for VList {
if let Some(additional) = lefts.len().checked_sub(rights.len()) { if let Some(additional) = lefts.len().checked_sub(rights.len()) {
rights.reserve_exact(additional); rights.reserve_exact(additional);
} }
let first = if self.fully_keyed && blist.fully_keyed { let first = if fully_keyed && blist.fully_keyed {
BList::apply_keyed(root, parent_scope, parent, next_sibling, lefts, rights) BList::apply_keyed(root, parent_scope, parent, next_sibling, lefts, rights)
} else { } else {
BList::apply_unkeyed(root, parent_scope, parent, next_sibling, lefts, rights) BList::apply_unkeyed(root, parent_scope, parent, next_sibling, lefts, rights)
}; };
blist.fully_keyed = self.fully_keyed; blist.fully_keyed = fully_keyed;
blist.key = self.key; blist.key = self.key;
test_log!("result: {:?}", rights); test_log!("result: {:?}", rights);
first first
@ -478,9 +479,11 @@ mod feat_hydration {
fragment: &mut Fragment, fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) { ) -> (NodeRef, Self::Bundle) {
let node_ref = NodeRef::default(); let node_ref = NodeRef::default();
let mut children = Vec::with_capacity(self.children.len()); let fully_keyed = self.fully_keyed();
let vchildren = self.children;
let mut children = Vec::with_capacity(vchildren.len());
for (index, child) in self.children.into_iter().enumerate() { for (index, child) in vchildren.into_iter().enumerate() {
let (child_node_ref, child) = child.hydrate(root, parent_scope, parent, fragment); let (child_node_ref, child) = child.hydrate(root, parent_scope, parent, fragment);
if index == 0 { if index == 0 {
@ -496,7 +499,7 @@ mod feat_hydration {
node_ref, node_ref,
BList { BList {
rev_children: children, rev_children: children,
fully_keyed: self.fully_keyed, fully_keyed,
key: self.key, key: self.key,
}, },
) )

View File

@ -3,18 +3,31 @@ use std::ops::{Deref, DerefMut};
use super::{Key, VNode}; use super::{Key, VNode};
#[derive(Clone, Copy, Debug, PartialEq)]
enum FullyKeyedState {
KnownFullyKeyed,
KnownMissingKeys,
Unknown,
}
/// This struct represents a fragment of the Virtual DOM tree. /// This struct represents a fragment of the Virtual DOM tree.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug)]
pub struct VList { pub struct VList {
/// The list of child [VNode]s /// The list of child [VNode]s
pub(crate) children: Vec<VNode>, pub(crate) children: Vec<VNode>,
/// All [VNode]s in the VList have keys /// All [VNode]s in the VList have keys
pub(crate) fully_keyed: bool, fully_keyed: FullyKeyedState,
pub key: Option<Key>, pub key: Option<Key>,
} }
impl PartialEq for VList {
fn eq(&self, other: &Self) -> bool {
self.children == other.children && self.key == other.key
}
}
impl Default for VList { impl Default for VList {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
@ -29,42 +42,10 @@ impl Deref for VList {
} }
} }
/// Mutable children of a [VList]. impl DerefMut for VList {
///
/// This struct has a `DerefMut` implementations into [`Vec<VNode>`](std::vec::Vec).
/// Prefer to use immutable access, since this re-checks if all nodes have keys when dropped.
pub struct ChildrenMut<'a> {
children: &'a mut Vec<VNode>,
fully_keyed: &'a mut bool,
}
impl<'a> Deref for ChildrenMut<'a> {
type Target = Vec<VNode>;
fn deref(&self) -> &Self::Target {
self.children
}
}
impl<'a> DerefMut for ChildrenMut<'a> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
*self.fully_keyed = false; self.fully_keyed = FullyKeyedState::Unknown;
self.children &mut self.children
}
}
impl<'a> Drop for ChildrenMut<'a> {
fn drop(&mut self) {
if !*self.fully_keyed {
// Caller might have changed the keys of the VList or add unkeyed children.
*self.fully_keyed = self.children.iter().all(|ch| ch.has_key());
}
}
}
impl<'a> std::fmt::Debug for ChildrenMut<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ChildrenMut").field(&self.children).finish()
} }
} }
@ -74,23 +55,29 @@ impl VList {
Self { Self {
children: Vec::new(), children: Vec::new(),
key: None, key: None,
fully_keyed: true, fully_keyed: FullyKeyedState::KnownFullyKeyed,
} }
} }
/// Creates a new [VList] instance with children. /// Creates a new [VList] instance with children.
pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self { pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
VList { let mut vlist = VList {
fully_keyed: children.iter().all(|ch| ch.has_key()), fully_keyed: FullyKeyedState::Unknown,
children, children,
key, key,
} };
vlist.fully_keyed = if vlist.fully_keyed() {
FullyKeyedState::KnownFullyKeyed
} else {
FullyKeyedState::KnownMissingKeys
};
vlist
} }
/// Add [VNode] child. /// Add [VNode] child.
pub fn add_child(&mut self, child: VNode) { pub fn add_child(&mut self, child: VNode) {
if self.fully_keyed && !child.has_key() { if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
self.fully_keyed = false; self.fully_keyed = FullyKeyedState::KnownMissingKeys;
} }
self.children.push(child); self.children.push(child);
} }
@ -105,11 +92,23 @@ impl VList {
} }
} }
/// Get a mutable list of children in this vlist. /// Recheck, if the all the children have keys.
pub fn children_mut(&mut self) -> ChildrenMut<'_> { ///
ChildrenMut { /// You can run this, after modifying the child list through the [DerefMut] implementation of
children: &mut self.children, /// [VList], to precompute an internally kept flag, which speeds up reconciliation later.
fully_keyed: &mut self.fully_keyed, pub fn recheck_fully_keyed(&mut self) {
self.fully_keyed = if self.fully_keyed() {
FullyKeyedState::KnownFullyKeyed
} else {
FullyKeyedState::KnownMissingKeys
};
}
pub(crate) fn fully_keyed(&self) -> bool {
match self.fully_keyed {
FullyKeyedState::KnownFullyKeyed => true,
FullyKeyedState::KnownMissingKeys => false,
FullyKeyedState::Unknown => self.children.iter().all(|c| c.has_key()),
} }
} }
} }
@ -122,25 +121,36 @@ mod test {
#[test] #[test]
fn mutably_change_children() { fn mutably_change_children() {
let mut vlist = VList::new(); let mut vlist = VList::new();
assert!(vlist.fully_keyed, "should start fully keyed"); assert_eq!(
vlist.fully_keyed,
FullyKeyedState::KnownFullyKeyed,
"should start fully keyed"
);
// add a child that is keyed // add a child that is keyed
let mut children = vlist.children_mut(); vlist.add_child(VNode::VTag({
children.push(VNode::VTag({
let mut tag = VTag::new("a"); let mut tag = VTag::new("a");
tag.key = Some(42u32.into()); tag.key = Some(42u32.into());
Box::new(tag) tag.into()
})); }));
drop(children); assert_eq!(
assert!(vlist.fully_keyed, "should still be fully keyed"); vlist.fully_keyed,
FullyKeyedState::KnownFullyKeyed,
"should still be fully keyed"
);
assert_eq!(vlist.len(), 1, "should contain 1 child"); assert_eq!(vlist.len(), 1, "should contain 1 child");
// now add a child that is not keyed // now add a child that is not keyed
let mut children = vlist.children_mut(); vlist.add_child(VNode::VText(VText::new("lorem ipsum")));
children.push(VNode::VText(VText::new("lorem ipsum"))); assert_eq!(
drop(children); vlist.fully_keyed,
assert!( FullyKeyedState::KnownMissingKeys,
!vlist.fully_keyed,
"should not be fully keyed, text tags have no key" "should not be fully keyed, text tags have no key"
); );
let _: &mut [VNode] = &mut vlist; // Use deref mut
assert_eq!(
vlist.fully_keyed,
FullyKeyedState::Unknown,
"key state should be unknown, since it was potentially modified through children"
);
} }
} }