Skip to content

Commit

Permalink
Partially undo #2673, different approach for the DerefMut impl of VLi…
Browse files Browse the repository at this point in the history
…st (#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
  • Loading branch information
WorldSEnder committed May 24, 2022
1 parent b29b453 commit b90c99a
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 69 deletions.
8 changes: 3 additions & 5 deletions examples/futures/src/markdown.rs
Expand Up @@ -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 => {}
Expand All @@ -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();
Expand Down
13 changes: 8 additions & 5 deletions packages/yew/src/dom_bundle/blist.rs
Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -496,7 +499,7 @@ mod feat_hydration {
node_ref,
BList {
rev_children: children,
fully_keyed: self.fully_keyed,
fully_keyed,
key: self.key,
},
)
Expand Down
128 changes: 69 additions & 59 deletions packages/yew/src/virtual_dom/vlist.rs
Expand Up @@ -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()
Expand All @@ -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
}
}

Expand All @@ -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);
}
Expand All @@ -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()),
}
}
}
Expand All @@ -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"
);
}
}

Expand Down

1 comment on commit b90c99a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yew master branch benchmarks (Lower is better)

Benchmark suite Current: b90c99a Previous: b29b453 Ratio
yew-struct-keyed 01_run1k 209.8605 174.806 1.20
yew-struct-keyed 02_replace1k 221.566 191.269 1.16
yew-struct-keyed 03_update10th1k_x16 324.3225 426.5925 0.76
yew-struct-keyed 04_select1k 52.919 60.132 0.88
yew-struct-keyed 05_swap1k 76.3895 86.7955 0.88
yew-struct-keyed 06_remove-one-1k 29.3365 29.3165 1.00
yew-struct-keyed 07_create10k 3471.836 3261.5505000000003 1.06
yew-struct-keyed 08_create1k-after1k_x2 511.691 453.2165 1.13
yew-struct-keyed 09_clear1k_x8 218.93 227.5145 0.96
yew-struct-keyed 21_ready-memory 1.4553260803222656 1.4553260803222656 1
yew-struct-keyed 22_run-memory 1.6948776245117188 1.695892333984375 1.00
yew-struct-keyed 23_update5-memory 1.6992073059082031 1.7000389099121094 1.00
yew-struct-keyed 24_run5-memory 1.7175521850585938 1.7169876098632812 1.00
yew-struct-keyed 25_run-clear-memory 1.3328704833984375 1.3335952758789062 1.00
yew-struct-keyed 31_startup-ci 1883.725 1837.6659999999997 1.03
yew-struct-keyed 32_startup-bt 31.98 38.636 0.83
yew-struct-keyed 33_startup-mainthreadcost 309.65600000000006 268.2079999999999 1.15
yew-struct-keyed 34_startup-totalbytes 331.59765625 331.59765625 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.