Skip to content

Commit 666e6ce

Browse files
committed
vlist: use more general reverse itteration for keyed list comparison
1 parent 1639635 commit 666e6ce

File tree

2 files changed

+43
-79
lines changed

2 files changed

+43
-79
lines changed

yew/src/virtual_dom/vlist.rs

+31-73
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! This module contains fragments implementation.
2-
use super::{Key, VDiff, VNode, VTag};
2+
use super::{Key, VDiff, VNode, VText};
33
use crate::html::{AnyScope, NodeRef};
44
use cfg_if::cfg_if;
55
use std::collections::HashMap;
@@ -160,53 +160,18 @@ impl VList {
160160
next_sibling
161161
}
162162

163-
/// Diff and patch fully keyed child lists.
164-
///
165-
/// Optimized for node addition or removal from either end of the list and small changes in the
166-
/// middle.
163+
/// Diff and patch fully keyed child lists
167164
fn apply_keyed(
168165
parent_scope: &AnyScope,
169166
parent: &Element,
170167
mut next_sibling: NodeRef,
171168
lefts: &mut [VNode],
172-
mut rights: Vec<VNode>,
169+
rights: Vec<VNode>,
173170
) -> NodeRef {
174-
macro_rules! map_keys {
175-
($src:expr) => {
176-
$src.iter()
177-
.map(|v| v.key().expect("unkeyed child in fully keyed list"))
178-
.collect::<Vec<Key>>()
179-
};
180-
}
181-
let lefts_keys = map_keys!(lefts);
182-
let rights_keys = map_keys!(rights);
183-
184-
/// Find the first differing key in 2 iterators
185-
fn diff_i<'a, 'b>(
186-
a: impl Iterator<Item = &'a Key>,
187-
b: impl Iterator<Item = &'b Key>,
188-
) -> usize {
189-
a.zip(b).take_while(|(a, b)| a == b).count()
190-
}
191-
192-
// Find first key mismatch from the front
193-
let from_start = diff_i(lefts_keys.iter(), rights_keys.iter());
194-
195-
if from_start == std::cmp::min(lefts.len(), rights.len()) {
196-
// No key changes
197-
return Self::apply_unkeyed(parent_scope, parent, next_sibling, lefts, rights);
198-
}
199-
200-
// Find first key mismatch from the back
201-
let from_end = diff_i(
202-
lefts_keys[from_start..].iter().rev(),
203-
rights_keys[from_start..].iter().rev(),
204-
);
205-
206171
macro_rules! apply {
207172
($l:expr, $r:expr) => {
208173
test_log!("patching: {:?} -> {:?}", $r, $l);
209-
apply!(std::mem::take($r).into() => $l);
174+
apply!($r.into() => $l);
210175
};
211176
($l:expr) => {
212177
test_log!("adding: {:?}", $l);
@@ -221,29 +186,33 @@ impl VList {
221186
};
222187
}
223188

224-
// Diff matching children at the end
225-
let lefts_to = lefts_keys.len() - from_end;
226-
let rights_to = rights_keys.len() - from_end;
227-
for (l, r) in lefts[lefts_to..]
228-
.iter_mut()
229-
.rev()
230-
.zip(rights[rights_to..].iter_mut().rev())
231-
{
232-
apply!(l, r);
189+
// Perform backwards iteration, until a key mismatch or depleted iterator
190+
let mut l_it = lefts.iter_mut().rev().peekable();
191+
let mut r_it = rights.into_iter().rev().peekable();
192+
loop {
193+
let l_next = l_it.peek();
194+
let r_next = r_it.peek();
195+
196+
match (l_next, r_next) {
197+
(Some(l_next), Some(r_next)) if l_next.key_ref() == r_next.key_ref() => {
198+
apply!(l_it.next().unwrap(), r_it.next().unwrap());
199+
}
200+
_ => break,
201+
}
233202
}
234203

235-
// Diff mismatched children in the middle
236-
let mut rights_diff: HashMap<Key, &mut VNode> = rights_keys[from_start..rights_to]
237-
.iter()
238-
.zip(rights[from_start..rights_to].iter_mut())
239-
.map(|(k, v)| (k.clone(), v))
204+
// Collect the rest of rights into a map for quick lookup
205+
let mut r_diff: HashMap<Key, VNode> = r_it
206+
.map(|n| (n.key().expect("unkeyed child in fully keyed VList"), n))
240207
.collect();
241-
for (l_k, l) in lefts_keys[from_start..lefts_to]
242-
.iter()
243-
.rev()
244-
.zip(lefts[from_start..lefts_to].iter_mut().rev())
245-
{
246-
match rights_diff.remove(l_k) {
208+
209+
// Continue iteration of lefts
210+
for l in l_it {
211+
match r_diff.remove(
212+
l.key_ref()
213+
.as_ref()
214+
.expect("unkeyed child in fully keyed VList"),
215+
) {
247216
// Reorder and diff any existing children
248217
Some(r) => {
249218
test_log!("moving as next: {:?}", r);
@@ -258,20 +227,11 @@ impl VList {
258227
}
259228

260229
// Remove any extra rights
261-
for (_, r) in rights_diff.drain() {
230+
for (_, mut r) in r_diff.drain() {
262231
test_log!("removing: {:?}", r);
263232
r.detach(parent);
264233
}
265234

266-
// Diff matching children at the start
267-
for (l, r) in lefts[..from_start]
268-
.iter_mut()
269-
.rev()
270-
.zip(rights[..from_start].iter_mut().rev())
271-
{
272-
apply!(l, r);
273-
}
274-
275235
next_sibling
276236
}
277237
}
@@ -302,10 +262,8 @@ impl VDiff for VList {
302262
if self.children.is_empty() {
303263
// Without a placeholder the next element becomes first
304264
// and corrupts the order of rendering
305-
// We use empty span element to stake out a place
306-
let mut placeholder = VTag::new("span");
307-
placeholder.key = Some("__placeholder".into());
308-
self.children = vec![placeholder.into()];
265+
// We use empty text element to stake out a place
266+
self.add_child(VText::new("").into());
309267
}
310268

311269
let lefts = &mut self.children;

yew/src/virtual_dom/vnode.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub enum VNode {
3333
}
3434

3535
impl VNode {
36+
/// Returns a copy of the Key associated with the VNode
3637
pub fn key(&self) -> Option<Key> {
3738
match self {
3839
VNode::VComp(vcomp) => vcomp.key.clone(),
@@ -43,16 +44,21 @@ impl VNode {
4344
}
4445
}
4546

46-
/// Returns, if the VNode has a key without needlessly cloning the key
47-
pub fn has_key(&self) -> bool {
47+
/// Returns a reference of the Key associated with the VNode
48+
pub(crate) fn key_ref(&self) -> &Option<Key> {
4849
match self {
49-
VNode::VComp(vcomp) => vcomp.key.is_some(),
50-
VNode::VList(vlist) => vlist.key.is_some(),
51-
VNode::VRef(_) | VNode::VText(_) => false,
52-
VNode::VTag(vtag) => vtag.key.is_some(),
50+
VNode::VComp(vcomp) => &vcomp.key,
51+
VNode::VList(vlist) => &vlist.key,
52+
VNode::VTag(vtag) => &vtag.key,
53+
VNode::VRef(_) | VNode::VText(_) => &None,
5354
}
5455
}
5556

57+
/// Returns, if the VNode has a key without needlessly cloning the key
58+
pub(crate) fn has_key(&self) -> bool {
59+
self.key_ref().is_some()
60+
}
61+
5662
/// Returns the first DOM node that is used to designate the position of the virtual DOM node.
5763
pub(crate) fn first_node(&self) -> Node {
5864
match self {

0 commit comments

Comments
 (0)