Skip to content

Commit 8f3873d

Browse files
committed
fix: store BList children in render order
Replace rev_children (reverse render order) with children (render order). apply_keyed assigns reverse indices to the spliced middle so the existing run algorithm works unchanged. shift iterates in reverse. Hydration drops a now-unnecessary reverse.
1 parent fcaf483 commit 8f3873d

File tree

1 file changed

+48
-52
lines changed

1 file changed

+48
-52
lines changed

packages/yew/src/dom_bundle/blist.rs

Lines changed: 48 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use crate::virtual_dom::{Key, VList, VNode};
1616
/// This struct represents a mounted [VList]
1717
#[derive(Debug)]
1818
pub(super) struct BList {
19-
/// The reverse (render order) list of child [BNode]s
20-
rev_children: Vec<BNode>,
19+
/// Child [BNode]s in render order
20+
children: Vec<BNode>,
2121
/// All [BNode]s in the BList have keys
2222
fully_keyed: bool,
2323
key: Option<Key>,
@@ -41,7 +41,7 @@ impl Deref for BList {
4141
type Target = Vec<BNode>;
4242

4343
fn deref(&self) -> &Self::Target {
44-
&self.rev_children
44+
&self.children
4545
}
4646
}
4747

@@ -119,7 +119,7 @@ impl BNode {
119119
_ => unreachable!("just been set to the variant"),
120120
};
121121
let key = b.key().cloned();
122-
self_list.rev_children.push(b);
122+
self_list.children.push(b);
123123
self_list.fully_keyed = key.is_some();
124124
self_list.key = key;
125125
self_list
@@ -132,7 +132,7 @@ impl BList {
132132
/// Create a new empty [BList]
133133
pub const fn new() -> BList {
134134
BList {
135-
rev_children: vec![],
135+
children: vec![],
136136
fully_keyed: true,
137137
key: None,
138138
}
@@ -145,7 +145,7 @@ impl BList {
145145

146146
/// Diff and patch unkeyed child lists
147147
///
148-
/// Pairs children by render-order position from the front, so that leading
148+
/// Pairs children front-to-front by render-order position so that leading
149149
/// nodes are always reconciled with themselves when the list grows or
150150
/// shrinks at the tail.
151151
fn apply_unkeyed(
@@ -163,26 +163,20 @@ impl BList {
163163
slot,
164164
};
165165

166-
// `rights` is stored in reverse render order. Flip to render order so
167-
// that simple index-based pairing matches children front-to-front.
168-
rights.reverse();
169-
170-
let left_len = lefts.len();
171-
172-
// Remove excess old nodes from the END of render order (now the tail).
173-
if left_len < rights.len() {
174-
for r in rights.drain(left_len..) {
166+
// Remove excess old nodes from the end of render order.
167+
if lefts.len() < rights.len() {
168+
for r in rights.drain(lefts.len()..) {
175169
test_log!("removing: {:?}", r);
176170
r.detach(root, parent, false);
177171
}
178172
}
179173

180174
let paired_count = rights.len(); // min(left_len, old_len)
181-
182-
// The NodeWriter must process children right-to-left in render order.
175+
let left_len = lefts.len();
183176
let mut lefts_rev = lefts.into_iter().rev();
184177

185-
// 1. Add excess new nodes at the tail of render order (rightmost first).
178+
// Add excess new nodes at the tail of render order (rightmost first
179+
// for the NodeWriter).
186180
let excess_start = rights.len();
187181
for l in lefts_rev
188182
.by_ref()
@@ -192,18 +186,14 @@ impl BList {
192186
rights.push(el);
193187
writer = next_writer;
194188
}
195-
// Items were pushed right-to-left; flip them to render order.
189+
// Items were pushed right-to-left; flip to render order.
196190
rights[excess_start..].reverse();
197191

198-
// 2. Patch paired nodes right-to-left. lefts_rev now yields positions [paired_count-1 ..
199-
// 0]. rights[..paired_count] is in render order, .rev() walks right-to-left.
192+
// Patch paired nodes right-to-left.
200193
for (l, r) in lefts_rev.zip(rights[..paired_count].iter_mut().rev()) {
201194
writer = writer.patch(l, r);
202195
}
203196

204-
// Flip back to reverse render order.
205-
rights.reverse();
206-
207197
writer.slot
208198
}
209199

@@ -217,7 +207,7 @@ impl BList {
217207
parent: &Element,
218208
slot: DomSlot,
219209
left_vdoms: Vec<VNode>,
220-
rev_bundles: &mut Vec<BNode>,
210+
bundles: &mut Vec<BNode>,
221211
) -> DomSlot {
222212
macro_rules! key {
223213
($v:expr) => {
@@ -232,10 +222,10 @@ impl BList {
232222
a.zip(b).take_while(|(a, b)| a == b).count()
233223
}
234224

235-
// Find first key mismatch from the back
225+
// Find first key mismatch from the back of render order
236226
let matching_len_end = matching_len(
237227
left_vdoms.iter().map(|v| key!(v)).rev(),
238-
rev_bundles.iter().map(|v| key!(v)),
228+
bundles.iter().map(|v| key!(v)).rev(),
239229
);
240230

241231
if cfg!(debug_assertions) {
@@ -252,9 +242,9 @@ impl BList {
252242

253243
// If there is no key mismatch, apply the unkeyed approach
254244
// Corresponds to adding or removing items from the back of the list
255-
if matching_len_end == std::cmp::min(left_vdoms.len(), rev_bundles.len()) {
245+
if matching_len_end == std::cmp::min(left_vdoms.len(), bundles.len()) {
256246
// No key changes
257-
return Self::apply_unkeyed(root, parent_scope, parent, slot, left_vdoms, rev_bundles);
247+
return Self::apply_unkeyed(root, parent_scope, parent, slot, left_vdoms, bundles);
258248
}
259249

260250
// We partially drain the new vnodes in several steps.
@@ -265,35 +255,36 @@ impl BList {
265255
parent,
266256
slot,
267257
};
268-
// Step 1. Diff matching children at the end
258+
// Step 1. Diff matching children at the end of render order
269259
let lefts_to = lefts.len() - matching_len_end;
260+
let bundles_from = bundles.len() - matching_len_end;
270261
for (l, r) in lefts
271262
.drain(lefts_to..)
272263
.rev()
273-
.zip(rev_bundles[..matching_len_end].iter_mut())
264+
.zip(bundles[bundles_from..].iter_mut().rev())
274265
{
275266
writer = writer.patch(l, r);
276267
}
277268

278269
// Step 2. Diff matching children in the middle, that is between the first and last key
279-
// mismatch Find first key mismatch from the front
270+
// mismatch. Find first key mismatch from the front.
280271
let mut matching_len_start = matching_len(
281272
lefts.iter().map(|v| key!(v)),
282-
rev_bundles.iter().map(|v| key!(v)).rev(),
273+
bundles.iter().map(|v| key!(v)),
283274
);
284275

285276
// Step 2.1. Splice out the existing middle part and build a lookup by key
286-
let rights_to = rev_bundles.len() - matching_len_start;
287-
let mut bundle_middle = matching_len_end..rights_to;
277+
let mut bundle_middle = matching_len_start..bundles_from;
288278
if bundle_middle.start > bundle_middle.end {
289279
// If this range is "inverted", this implies that the incoming nodes in lefts contain a
290280
// duplicate key!
291281
// Pictogram:
292282
// v lefts_to
293-
// lefts: | SSSSSSSS | ------ | EEEEEEEE |
294-
// ↕ matching_len_start
295-
// rev_bundles.rev(): | SSS | ?? | EEE |
296-
// ^ rights_to
283+
// lefts: | SSSSSSSS | ------ | EEEEEEEE |
284+
// ↕ matching_len_start
285+
// bundles: | SSS | ?? | EEE |
286+
// ^ matching_len_start
287+
// ^ bundles_from
297288
// Both a key from the (S)tarting portion and (E)nding portion of lefts has matched a
298289
// key in the ? portion of bundles. Since the former can't overlap, a key
299290
// must be duplicate. Duplicates might lead to us forgetting about some
@@ -304,15 +295,21 @@ impl BList {
304295
// by pretending. We still need to adjust some things so splicing doesn't
305296
// panic:
306297
matching_len_start = 0;
307-
bundle_middle = matching_len_end..rev_bundles.len();
298+
bundle_middle = 0..bundles_from;
308299
}
309300
let (matching_len_start, bundle_middle) = (matching_len_start, bundle_middle);
310301

302+
let middle_count = bundle_middle.len();
303+
311304
// BNode contains js objects that look suspicious to clippy but are harmless
312305
#[allow(clippy::mutable_key_type)]
313-
let mut spare_bundles: HashSet<KeyedEntry> = HashSet::with_capacity(bundle_middle.len());
314-
let mut spliced_middle = rev_bundles.splice(bundle_middle, std::iter::empty());
315-
for (idx, r) in (&mut spliced_middle).enumerate() {
306+
let mut spare_bundles: HashSet<KeyedEntry> = HashSet::with_capacity(middle_count);
307+
let mut spliced_middle = bundles.splice(bundle_middle, std::iter::empty());
308+
for (fwd_idx, r) in (&mut spliced_middle).enumerate() {
309+
// Assign indices measuring distance from the right (rightmost = 0).
310+
// The run algorithm below processes right-to-left and expects
311+
// increasing indices in that direction.
312+
let idx = middle_count - 1 - fwd_idx;
316313
#[cold]
317314
fn duplicate_in_bundle(root: &BSubtree, parent: &Element, r: BNode) {
318315
test_log!("removing: {:?}", r);
@@ -413,7 +410,9 @@ impl BList {
413410
}
414411
// drop the splice iterator and immediately replace the range with the reordered elements
415412
drop(spliced_middle);
416-
rev_bundles.splice(matching_len_end..matching_len_end, replacements);
413+
// replacements was built right-to-left; reverse to render order
414+
replacements.reverse();
415+
bundles.splice(matching_len_start..matching_len_start, replacements);
417416

418417
// Step 2.3. Remove any extra rights
419418
for KeyedEntry(_, r) in spare_bundles.drain() {
@@ -422,11 +421,10 @@ impl BList {
422421
}
423422

424423
// Step 3. Diff matching children at the start
425-
let rights_to = rev_bundles.len() - matching_len_start;
426424
for (l, r) in lefts
427425
.drain(..) // matching_len_start.. has been drained already
428426
.rev()
429-
.zip(rev_bundles[rights_to..].iter_mut())
427+
.zip(bundles[..matching_len_start].iter_mut().rev())
430428
{
431429
writer = writer.patch(l, r);
432430
}
@@ -437,13 +435,13 @@ impl BList {
437435

438436
impl ReconcileTarget for BList {
439437
fn detach(self, root: &BSubtree, parent: &Element, parent_to_detach: bool) {
440-
for child in self.rev_children.into_iter() {
438+
for child in self.children.into_iter() {
441439
child.detach(root, parent, parent_to_detach);
442440
}
443441
}
444442

445443
fn shift(&self, next_parent: &Element, mut slot: DomSlot) -> DomSlot {
446-
for node in self.rev_children.iter() {
444+
for node in self.children.iter().rev() {
447445
slot = node.shift(next_parent, slot);
448446
}
449447

@@ -498,7 +496,7 @@ impl Reconcilable for VList {
498496
// i.e. the current DOM list element that we want to replace with self.
499497
let (key, fully_keyed, lefts) = self.split_for_blist();
500498

501-
let rights = &mut blist.rev_children;
499+
let rights = &mut blist.children;
502500
test_log!("lefts: {:?}", lefts);
503501
test_log!("rights: {:?}", rights);
504502

@@ -541,10 +539,8 @@ mod feat_hydration {
541539
children.push(child);
542540
}
543541

544-
children.reverse();
545-
546542
BList {
547-
rev_children: children,
543+
children,
548544
fully_keyed,
549545
key,
550546
}

0 commit comments

Comments
 (0)