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

View File

@ -444,6 +444,7 @@ impl Reconcilable for VList {
self.add_child(VText::new("").into());
}
let fully_keyed = self.fully_keyed();
let lefts = self.children;
let rights = &mut blist.rev_children;
test_log!("lefts: {:?}", lefts);
@ -452,12 +453,12 @@ impl Reconcilable for VList {
if let Some(additional) = lefts.len().checked_sub(rights.len()) {
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)
} else {
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;
test_log!("result: {:?}", rights);
first
@ -478,9 +479,11 @@ mod feat_hydration {
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) {
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);
if index == 0 {
@ -496,7 +499,7 @@ mod feat_hydration {
node_ref,
BList {
rev_children: children,
fully_keyed: self.fully_keyed,
fully_keyed,
key: self.key,
},
)

View File

@ -3,18 +3,31 @@ use std::ops::{Deref, DerefMut};
use super::{Key, VNode};
#[derive(Clone, Copy, Debug, PartialEq)]
enum FullyKeyedState {
KnownFullyKeyed,
KnownMissingKeys,
Unknown,
}
/// This struct represents a fragment of the Virtual DOM tree.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
pub struct VList {
/// The list of child [VNode]s
pub(crate) children: Vec<VNode>,
/// All [VNode]s in the VList have keys
pub(crate) fully_keyed: bool,
fully_keyed: FullyKeyedState,
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 {
fn default() -> Self {
Self::new()
@ -29,42 +42,10 @@ impl Deref for VList {
}
}
/// Mutable children of a [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> {
impl DerefMut for VList {
fn deref_mut(&mut self) -> &mut Self::Target {
*self.fully_keyed = false;
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()
self.fully_keyed = FullyKeyedState::Unknown;
&mut self.children
}
}
@ -74,23 +55,29 @@ impl VList {
Self {
children: Vec::new(),
key: None,
fully_keyed: true,
fully_keyed: FullyKeyedState::KnownFullyKeyed,
}
}
/// Creates a new [VList] instance with children.
pub fn with_children(children: Vec<VNode>, key: Option<Key>) -> Self {
VList {
fully_keyed: children.iter().all(|ch| ch.has_key()),
let mut vlist = VList {
fully_keyed: FullyKeyedState::Unknown,
children,
key,
}
};
vlist.fully_keyed = if vlist.fully_keyed() {
FullyKeyedState::KnownFullyKeyed
} else {
FullyKeyedState::KnownMissingKeys
};
vlist
}
/// Add [VNode] child.
pub fn add_child(&mut self, child: VNode) {
if self.fully_keyed && !child.has_key() {
self.fully_keyed = false;
if self.fully_keyed == FullyKeyedState::KnownFullyKeyed && !child.has_key() {
self.fully_keyed = FullyKeyedState::KnownMissingKeys;
}
self.children.push(child);
}
@ -105,11 +92,23 @@ impl VList {
}
}
/// Get a mutable list of children in this vlist.
pub fn children_mut(&mut self) -> ChildrenMut<'_> {
ChildrenMut {
children: &mut self.children,
fully_keyed: &mut self.fully_keyed,
/// Recheck, if the all the children have keys.
///
/// You can run this, after modifying the child list through the [DerefMut] implementation of
/// [VList], to precompute an internally kept flag, which speeds up reconciliation later.
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]
fn mutably_change_children() {
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
let mut children = vlist.children_mut();
children.push(VNode::VTag({
vlist.add_child(VNode::VTag({
let mut tag = VTag::new("a");
tag.key = Some(42u32.into());
Box::new(tag)
tag.into()
}));
drop(children);
assert!(vlist.fully_keyed, "should still be fully keyed");
assert_eq!(
vlist.fully_keyed,
FullyKeyedState::KnownFullyKeyed,
"should still be fully keyed"
);
assert_eq!(vlist.len(), 1, "should contain 1 child");
// now add a child that is not keyed
let mut children = vlist.children_mut();
children.push(VNode::VText(VText::new("lorem ipsum")));
drop(children);
assert!(
!vlist.fully_keyed,
vlist.add_child(VNode::VText(VText::new("lorem ipsum")));
assert_eq!(
vlist.fully_keyed,
FullyKeyedState::KnownMissingKeys,
"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"
);
}
}