Skip to content

Commit 751fed2

Browse files
committed
slightly optimize list reconciler, add doccomments
1 parent f576c36 commit 751fed2

File tree

1 file changed

+62
-45
lines changed

1 file changed

+62
-45
lines changed

packages/yew/src/dom_bundle/blist.rs

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ impl<'s> ElementWriter<'s> {
5353
)
5454
}
5555

56+
fn shift(&self, bundle: &mut BNode) {
57+
bundle.shift(self.parent, self.next_sibling.clone());
58+
}
59+
5660
fn patch(self, node: VNode, bundle: &mut BNode) -> Self {
5761
test_log!("patching: {:?} -> {:?}", bundle, node);
5862
test_log!(
@@ -61,7 +65,6 @@ impl<'s> ElementWriter<'s> {
6165
self.next_sibling
6266
);
6367
// Advance the next sibling reference (from right to left)
64-
bundle.shift(self.parent, self.next_sibling.clone());
6568
let next = node.reconcile(self.parent_scope, self.parent, self.next_sibling, bundle);
6669
test_log!(" next_position: {:?}", next);
6770
Self {
@@ -71,23 +74,23 @@ impl<'s> ElementWriter<'s> {
7174
}
7275
}
7376

74-
struct NodeEntry(BNode);
75-
impl Borrow<Key> for NodeEntry {
77+
struct KeyedEntry(BNode, usize);
78+
impl Borrow<Key> for KeyedEntry {
7679
fn borrow(&self) -> &Key {
7780
self.0.key().expect("unkeyed child in fully keyed list")
7881
}
7982
}
80-
impl Hash for NodeEntry {
83+
impl Hash for KeyedEntry {
8184
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
8285
<Self as Borrow<Key>>::borrow(self).hash(state)
8386
}
8487
}
85-
impl PartialEq for NodeEntry {
88+
impl PartialEq for KeyedEntry {
8689
fn eq(&self, other: &Self) -> bool {
8790
<Self as Borrow<Key>>::borrow(self) == <Self as Borrow<Key>>::borrow(other)
8891
}
8992
}
90-
impl Eq for NodeEntry {}
93+
impl Eq for KeyedEntry {}
9194

9295
impl BNode {
9396
fn make_list(&mut self) -> &mut BList {
@@ -165,9 +168,14 @@ impl BList {
165168
parent_scope: &AnyScope,
166169
parent: &Element,
167170
next_sibling: NodeRef,
168-
lefts: Vec<VNode>,
169-
rights: &mut Vec<BNode>,
171+
left_vdoms: Vec<VNode>,
172+
rev_bundles: &mut Vec<BNode>,
170173
) -> NodeRef {
174+
macro_rules! key {
175+
($v:expr) => {
176+
$v.key().expect("unkeyed child in fully keyed list")
177+
};
178+
}
171179
/// Find the first differing key in 2 iterators
172180
fn matching_len<'a, 'b>(
173181
a: impl Iterator<Item = &'a Key>,
@@ -178,62 +186,72 @@ impl BList {
178186

179187
// Find first key mismatch from the back
180188
let matching_len_end = matching_len(
181-
lefts
182-
.iter()
183-
.map(|v| v.key().expect("unkeyed child in fully keyed list"))
184-
.rev(),
185-
rights
186-
.iter()
187-
.map(|v| v.key().expect("unkeyed child in fully keyed list")),
189+
left_vdoms.iter().map(|v| key!(v)).rev(),
190+
rev_bundles.iter().map(|v| key!(v)),
188191
);
189192

190-
if matching_len_end == std::cmp::min(lefts.len(), rights.len()) {
193+
// If there is no key mismatch, apply the unkeyed approach
194+
// Corresponds to adding or removing items from the back of the list
195+
if matching_len_end == std::cmp::min(left_vdoms.len(), rev_bundles.len()) {
191196
// No key changes
192-
return Self::apply_unkeyed(parent_scope, parent, next_sibling, lefts, rights);
197+
return Self::apply_unkeyed(
198+
parent_scope,
199+
parent,
200+
next_sibling,
201+
left_vdoms,
202+
rev_bundles,
203+
);
193204
}
194-
// We partially deconstruct the new vector in several steps.
195-
let mut lefts = lefts;
205+
206+
// We partially drain the new vnodes in several steps.
207+
let mut lefts = left_vdoms;
196208
let mut writer = ElementWriter {
197209
parent_scope,
198210
parent,
199211
next_sibling,
200212
};
201-
// Diff matching children at the end
213+
// Step 1. Diff matching children at the end
202214
let lefts_to = lefts.len() - matching_len_end;
203215
for (l, r) in lefts
204216
.drain(lefts_to..)
205217
.rev()
206-
.zip(rights[..matching_len_end].iter_mut())
218+
.zip(rev_bundles[..matching_len_end].iter_mut())
207219
{
208220
writer = writer.patch(l, r);
209221
}
222+
223+
// Step 2. Diff matching children in the middle, that is between the first and last key mismatch
210224
// Find first key mismatch from the front
211225
let matching_len_start = matching_len(
212-
lefts
213-
.iter()
214-
.map(|v| v.key().expect("unkeyed child in fully keyed list")),
215-
rights
216-
.iter()
217-
.map(|v| v.key().expect("unkeyed child in fully keyed list"))
218-
.rev(),
226+
lefts.iter().map(|v| key!(v)),
227+
rev_bundles.iter().map(|v| key!(v)).rev(),
219228
);
220229

221-
// Diff mismatched children in the middle
222-
let rights_to = rights.len() - matching_len_start;
223-
let mut spliced_middle = rights.splice(matching_len_end..rights_to, std::iter::empty());
224-
let mut rights_diff: HashSet<NodeEntry> =
230+
// Step 2.1. Splice out the existing middle part and build a lookup by key
231+
let rights_to = rev_bundles.len() - matching_len_start;
232+
let mut spliced_middle =
233+
rev_bundles.splice(matching_len_end..rights_to, std::iter::empty());
234+
let mut spare_bundles: HashSet<KeyedEntry> =
225235
HashSet::with_capacity((matching_len_end..rights_to).len());
226-
for r in &mut spliced_middle {
227-
rights_diff.insert(NodeEntry(r));
236+
for (idx, r) in (&mut spliced_middle).enumerate() {
237+
spare_bundles.insert(KeyedEntry(r, idx));
228238
}
239+
240+
// Step 2.2. Put the middle part back together in the new key order
229241
let mut replacements: Vec<BNode> = Vec::with_capacity((matching_len_start..lefts_to).len());
242+
// Roughly keep track of the order in which elements appear. If one appears out-of-order
243+
// we (over approximately) have to shift the element, otherwise it is guaranteed to be in place.
244+
let mut max_seen_idx = 0;
230245
for l in lefts
231246
.drain(matching_len_start..) // lefts_to.. has been drained
232247
.rev()
233248
{
234-
let l_key = l.key().expect("unkeyed child in fully keyed list");
235-
let bundle = match rights_diff.take(l_key) {
236-
Some(NodeEntry(mut r_bundle)) => {
249+
let bundle = match spare_bundles.take(key!(l)) {
250+
Some(KeyedEntry(mut r_bundle, idx)) => {
251+
if idx < max_seen_idx {
252+
writer.shift(&mut r_bundle);
253+
}
254+
max_seen_idx = usize::max(max_seen_idx, idx);
237255
writer = writer.patch(l, &mut r_bundle);
238256
r_bundle
239257
}
@@ -245,22 +263,22 @@ impl BList {
245263
};
246264
replacements.push(bundle);
247265
}
248-
// now drop the splice iterator
266+
// drop the splice iterator and immediately replace the range with the reordered elements
249267
std::mem::drop(spliced_middle);
250-
rights.splice(matching_len_end..matching_len_end, replacements);
268+
rev_bundles.splice(matching_len_end..matching_len_end, replacements);
251269

252-
// Remove any extra rights
253-
for NodeEntry(r) in rights_diff.drain() {
270+
// Step 2.3. Remove any extra rights
271+
for KeyedEntry(r, _) in spare_bundles.drain() {
254272
test_log!("removing: {:?}", r);
255273
r.detach(parent);
256274
}
257275

258-
// Diff matching children at the start
259-
let rights_to = rights.len() - matching_len_start;
276+
// Step 3. Diff matching children at the start
277+
let rights_to = rev_bundles.len() - matching_len_start;
260278
for (l, r) in lefts
261279
.drain(..) // matching_len_start.. has been drained already
262280
.rev()
263-
.zip(rights[rights_to..].iter_mut())
281+
.zip(rev_bundles[rights_to..].iter_mut())
264282
{
265283
writer = writer.patch(l, r);
266284
}
@@ -335,7 +353,6 @@ impl Reconcilable for VList {
335353
if let Some(additional) = rights.len().checked_sub(lefts.len()) {
336354
rights.reserve_exact(additional);
337355
}
338-
#[allow(clippy::let_and_return)]
339356
let first = if self.fully_keyed && blist.fully_keyed {
340357
BList::apply_keyed(parent_scope, parent, next_sibling, lefts, rights)
341358
} else {

0 commit comments

Comments
 (0)