@@ -4,6 +4,7 @@ use crate::dom_bundle::{DomBundle, Reconcilable};
44use crate :: html:: { AnyScope , NodeRef } ;
55use crate :: virtual_dom:: { Key , VList , VNode , VText } ;
66use std:: borrow:: Borrow ;
7+ use std:: cmp:: Ordering ;
78use std:: collections:: HashSet ;
89use std:: hash:: Hash ;
910use std:: ops:: Deref ;
@@ -28,6 +29,7 @@ impl Deref for BList {
2829}
2930
3031/// Helper struct, that keeps the position where the next element is to be placed at
32+ #[ derive( Clone ) ]
3133struct NodeWriter < ' s > {
3234 parent_scope : & ' s AnyScope ,
3335 parent : & ' s Element ,
@@ -246,27 +248,86 @@ impl BList {
246248
247249 // Step 2.2. Put the middle part back together in the new key order
248250 let mut replacements: Vec < BNode > = Vec :: with_capacity ( ( matching_len_start..lefts_to) . len ( ) ) ;
249- // Roughly keep track of the order in which elements appear. If one appears out-of-order
250- // we (over approximately) have to shift the element, otherwise it is guaranteed to be in place.
251- let mut max_seen_idx = 0 ;
251+ // The goal is to shift as few nodes as possible.
252+
253+ // We handle runs of in-order nodes. When we encounter one out-of-order, we decide whether:
254+ // - to shift all nodes in the current run to the position after the node before of the run, or to
255+ // - "commit" to the current run, shift all nodes before the end of the run that we might
256+ // encounter in the future, and then start a new run.
257+ // Example of a run:
258+ // barrier_idx --v v-- end_idx
259+ // spliced_middle [ ... , M , N , C , D , E , F , G , ... ] (original element order)
260+ // ^---^-----------^ the nodes that are part of the current run
261+ // v start_writer
262+ // replacements [ ... , M , C , D , G ] (new element order)
263+ // ^-- start_idx
264+ let mut barrier_idx = 0 ; // nodes from spliced_middle[..barrier_idx] are shifted unconditionally
265+ struct RunInformation < ' a > {
266+ start_writer : NodeWriter < ' a > ,
267+ start_idx : usize ,
268+ end_idx : usize ,
269+ }
270+ let mut current_run: Option < RunInformation < ' _ > > = None ;
271+
252272 for l in lefts
253273 . drain ( matching_len_start..) // lefts_to.. has been drained
254274 . rev ( )
255275 {
256- let bundle = match spare_bundles. take ( key ! ( l) ) {
257- Some ( KeyedEntry ( idx, mut r_bundle) ) => {
258- if idx < max_seen_idx {
259- writer. shift ( & mut r_bundle) ;
276+ let ancestor = spare_bundles. take ( key ! ( l) ) ;
277+ // Check if we need to shift or commit a run
278+ if let Some ( run) = current_run. as_mut ( ) {
279+ if let Some ( KeyedEntry ( idx, _) ) = ancestor {
280+ // If there are only few runs, this is a cold path
281+ if idx < run. end_idx {
282+ // Have to decide whether to shift or commit the current run. A few calculations:
283+ // A perfect estimate of the amount of nodes we have to shift if we move this run:
284+ let run_length = replacements. len ( ) - run. start_idx ;
285+ // A very crude estimate of the amount of nodes we will have to shift if we commit the run:
286+ // Note nodes of the current run should not be counted here!
287+ let estimated_skipped_nodes = run. end_idx - idx. max ( barrier_idx) ;
288+ // double run_length to counteract that the run is part of the estimated_skipped_nodes
289+ if 2 * run_length > estimated_skipped_nodes {
290+ // less work to commit to this run
291+ barrier_idx = 1 + run. end_idx ;
292+ } else {
293+ // Less work to shift this run
294+ for r in replacements[ run. start_idx ..] . iter_mut ( ) . rev ( ) {
295+ run. start_writer . shift ( r) ;
296+ }
297+ }
298+ current_run = None ;
260299 }
261- max_seen_idx = usize:: max ( max_seen_idx, idx) ;
262- writer = writer. patch ( l, & mut r_bundle) ;
263- r_bundle
264300 }
265- None => {
266- let ( next_writer, bundle) = writer. add ( l) ;
267- writer = next_writer;
268- bundle
301+ }
302+ let bundle = if let Some ( KeyedEntry ( idx, mut r_bundle) ) = ancestor {
303+ match current_run. as_mut ( ) {
304+ // hot path
305+ // We know that idx >= run.end_idx, so this node doesn't need to shift
306+ Some ( run) => run. end_idx = idx,
307+ None => {
308+ match idx. cmp ( & barrier_idx) {
309+ // peep hole optimization, don't start a run as the element is already where it should be
310+ Ordering :: Equal => barrier_idx += 1 ,
311+ // shift the node unconditionally, don't start a run
312+ Ordering :: Less => writer. shift ( & mut r_bundle) ,
313+ // start a run
314+ Ordering :: Greater => {
315+ current_run = Some ( RunInformation {
316+ start_writer : writer. clone ( ) ,
317+ start_idx : replacements. len ( ) ,
318+ end_idx : idx,
319+ } )
320+ }
321+ }
322+ }
269323 }
324+ writer = writer. patch ( l, & mut r_bundle) ;
325+ r_bundle
326+ } else {
327+ // Even if there is an active run, we don't have to modify it
328+ let ( next_writer, bundle) = writer. add ( l) ;
329+ writer = next_writer;
330+ bundle
270331 } ;
271332 replacements. push ( bundle) ;
272333 }
0 commit comments