loro_internal/diff/
diff_impl.rs

1//! This file was copied from the similar crate at
2//! https://github.com/mitsuhiko/similar/blob/2b31f65445df9093ba007ca5a5ae6a71b899d491/src/algorithms/myers.rs
3//! The original license is in the LICENSE file in the same directory as this file
4//!
5//! This file has been optimized for constant performance, removed some unnecessary cases
6//! and simplified the use of DiffHandler.
7//! Myers' diff algorithm.
8//!
9//! * time: `O((N+M)D)`
10//! * space `O(N+M)`
11//!
12//! See [the original article by Eugene W. Myers](http://www.xmailserver.org/diff2.pdf)
13//! describing it.
14//!
15//! The implementation of this algorithm is based on the implementation by
16//! Brandon Williams.
17use crate::change::get_sys_timestamp;
18use fxhash::FxHashMap;
19use std::cmp::Ordering;
20use std::collections::BinaryHeap;
21use std::iter::zip;
22use std::ops::{Index, IndexMut};
23
24/// Options for controlling the text update behavior.
25///
26/// - `timeout_ms`: Optional timeout in milliseconds for the diff computation
27/// - `use_refined_diff`: Whether to use a more refined but slower diff algorithm. Defaults to true.
28#[derive(Clone, Debug)]
29pub struct UpdateOptions {
30    pub timeout_ms: Option<f64>,
31    pub use_refined_diff: bool,
32}
33
34impl Default for UpdateOptions {
35    fn default() -> Self {
36        Self {
37            timeout_ms: None,
38            use_refined_diff: true,
39        }
40    }
41}
42
43#[derive(thiserror::Error, Debug, PartialEq)]
44pub enum UpdateTimeoutError {
45    #[error("Timeout")]
46    Timeout,
47}
48
49/// Utility function to check if a range is empty that works on older rust versions
50#[inline(always)]
51fn is_empty_range(start: usize, end: usize) -> bool {
52    start >= end
53}
54
55#[inline(always)]
56fn is_not_empty_range(start: usize, end: usize) -> bool {
57    start < end
58}
59
60fn common_prefix(xs: &[u32], ys: &[u32]) -> usize {
61    let chunk_size = 4;
62    let off = zip(xs.chunks_exact(chunk_size), ys.chunks_exact(chunk_size))
63        .take_while(|(xs_chunk, ys_chunk)| xs_chunk == ys_chunk)
64        .count()
65        * chunk_size;
66    off + zip(&xs[off..], &ys[off..])
67        .take_while(|(x, y)| x == y)
68        .count()
69}
70
71fn common_suffix_len(old: &[u32], new: &[u32]) -> usize {
72    let chunk_size = 4;
73    let old_len = old.len();
74    let new_len = new.len();
75
76    let off = zip(old.rchunks_exact(chunk_size), new.rchunks_exact(chunk_size))
77        .take_while(|(old_chunk, new_chunk)| old_chunk == new_chunk)
78        .count()
79        * chunk_size;
80
81    off + zip(
82        old[..old_len - off].iter().rev(),
83        new[..new_len - off].iter().rev(),
84    )
85    .take_while(|(o, n)| o == n)
86    .count()
87}
88
89pub(crate) trait DiffHandler {
90    fn insert(&mut self, old_index: usize, new_index: usize, new_len: usize);
91    fn delete(&mut self, old_index: usize, old_len: usize);
92}
93
94#[derive(Debug)]
95pub(crate) struct OperateProxy<D: DiffHandler> {
96    handler: D,
97}
98
99impl<D: DiffHandler> OperateProxy<D> {
100    pub fn new(handler: D) -> Self {
101        Self { handler }
102    }
103
104    pub fn delete(&mut self, old_index: usize, old_len: usize) {
105        self.handler.delete(old_index, old_len);
106    }
107
108    pub fn insert(&mut self, old_index: usize, new_index: usize, new_len: usize) {
109        self.handler.insert(old_index, new_index, new_len);
110    }
111
112    #[allow(unused)]
113    fn unwrap(self) -> D {
114        self.handler
115    }
116}
117
118pub(crate) fn diff<D: DiffHandler>(
119    proxy: &mut OperateProxy<D>,
120    options: UpdateOptions,
121    old: &[u32],
122    new: &[u32],
123) -> Result<(), UpdateTimeoutError> {
124    let max_d = (old.len() + new.len()).div_ceil(2) + 1;
125    let mut vb = OffsetVec::new(max_d);
126    let mut vf = OffsetVec::new(max_d);
127    let start_time = if options.timeout_ms.is_some() {
128        get_sys_timestamp()
129    } else {
130        0.
131    };
132
133    conquer(
134        proxy,
135        options.use_refined_diff,
136        options.timeout_ms,
137        start_time,
138        old,
139        0,
140        old.len(),
141        new,
142        0,
143        new.len(),
144        &mut vf,
145        &mut vb,
146    )
147}
148
149struct OffsetVec(isize, Vec<usize>);
150
151impl OffsetVec {
152    fn new(max_d: usize) -> Self {
153        Self(max_d as isize, vec![0; max_d << 1])
154    }
155
156    fn len(&self) -> usize {
157        self.1.len()
158    }
159}
160
161impl Index<isize> for OffsetVec {
162    type Output = usize;
163    fn index(&self, index: isize) -> &Self::Output {
164        &self.1[(index + self.0) as usize]
165    }
166}
167
168impl IndexMut<isize> for OffsetVec {
169    fn index_mut(&mut self, index: isize) -> &mut Self::Output {
170        &mut self.1[(index + self.0) as usize]
171    }
172}
173
174struct MiddleSnakeResult {
175    #[allow(unused)]
176    d: usize,
177    x_start: usize,
178    y_start: usize,
179}
180
181#[allow(clippy::too_many_arguments)]
182fn find_middle_snake(
183    timeout_ms: Option<f64>,
184    start_time: f64,
185    old: &[u32],
186    old_start: usize,
187    old_end: usize,
188    new: &[u32],
189    new_start: usize,
190    new_end: usize,
191    vf: &mut OffsetVec,
192    vb: &mut OffsetVec,
193) -> Result<Option<MiddleSnakeResult>, UpdateTimeoutError> {
194    let n = old_end - old_start;
195    let m = new_end - new_start;
196    let delta = n as isize - m as isize;
197    let odd = delta & 1 != 0;
198    vf[1] = 0;
199    vb[1] = 0;
200    let d_max = (n + m).div_ceil(2) + 1;
201    assert!(vf.len() >= d_max);
202    assert!(vb.len() >= d_max);
203
204    for d in 0..d_max as isize {
205        for k in (-d..=d).rev().step_by(2) {
206            let mut x = if k == -d || (k != d && vf[k - 1] < vf[k + 1]) {
207                vf[k + 1]
208            } else {
209                vf[k - 1] + 1
210            };
211            let y = (x as isize - k) as usize;
212            let (x0, y0) = (x, y);
213            if x < n && y < m {
214                let advance =
215                    common_prefix(&old[old_start + x..old_end], &new[new_start + y..new_end]);
216                x += advance;
217            }
218            vf[k] = x;
219            if odd && (k - delta).abs() <= (d - 1) && vf[k] + vb[delta - k] >= n {
220                return Ok(Some(MiddleSnakeResult {
221                    d: d as usize,
222                    x_start: x0 + old_start,
223                    y_start: y0 + new_start,
224                }));
225            }
226
227            if let Some(timeout_ms) = timeout_ms {
228                if get_sys_timestamp() - start_time > timeout_ms {
229                    return Err(UpdateTimeoutError::Timeout);
230                }
231            }
232        }
233
234        for k in (-d..=d).rev().step_by(2) {
235            let mut x = if k == -d || (k != d && vb[k - 1] < vb[k + 1]) {
236                vb[k + 1]
237            } else {
238                vb[k - 1] + 1
239            };
240            let mut y = (x as isize - k) as usize;
241            if x < n && y < m {
242                let advance = common_suffix_len(
243                    &old[old_start..old_start + n - x],
244                    &new[new_start..new_start + m - y],
245                );
246                x += advance;
247                y += advance;
248            }
249            vb[k] = x;
250            if !odd && (k - delta).abs() <= d && vb[k] + vf[delta - k] >= n {
251                return Ok(Some(MiddleSnakeResult {
252                    d: d as usize,
253                    x_start: n - x + old_start,
254                    y_start: m - y + new_start,
255                }));
256            }
257
258            if let Some(timeout_ms) = timeout_ms {
259                if get_sys_timestamp() - start_time > timeout_ms {
260                    return Err(UpdateTimeoutError::Timeout);
261                }
262            }
263        }
264    }
265
266    Ok(None)
267}
268
269#[allow(clippy::too_many_arguments)]
270fn conquer<D: DiffHandler>(
271    proxy: &mut OperateProxy<D>,
272    should_use_dj: bool,
273    timeout_ms: Option<f64>,
274    start_time: f64,
275    old: &[u32],
276    mut old_start: usize,
277    mut old_end: usize,
278    new: &[u32],
279    mut new_start: usize,
280    mut new_end: usize,
281    vf: &mut OffsetVec,
282    vb: &mut OffsetVec,
283) -> Result<(), UpdateTimeoutError> {
284    let common_prefix_len = common_prefix(&old[old_start..old_end], &new[new_start..new_end]);
285    if common_prefix_len > 0 {
286        old_start += common_prefix_len;
287        new_start += common_prefix_len;
288    }
289
290    let common_suffix_len = common_suffix_len(&old[old_start..old_end], &new[new_start..new_end]);
291    old_end -= common_suffix_len;
292    new_end -= common_suffix_len;
293
294    if is_not_empty_range(old_start, old_end) || is_not_empty_range(new_start, new_end) {
295        let len_old = old_end - old_start;
296        let len_new = new_end - new_start;
297        if should_use_dj && (len_old.max(1) * len_new.max(1) < 128 * 128) {
298            let ok = dj_diff(
299                proxy,
300                &old[old_start..old_end],
301                &new[new_start..new_end],
302                old_start,
303                new_start,
304                10_000,
305            );
306            if ok {
307                return Ok(());
308            }
309        }
310
311        if is_empty_range(new_start, new_end) {
312            proxy.delete(old_start, old_end - old_start);
313        } else if is_empty_range(old_start, old_end) {
314            proxy.insert(old_start, new_start, new_end - new_start);
315        } else if let Some(MiddleSnakeResult {
316            d: _,
317            x_start,
318            y_start,
319        }) = find_middle_snake(
320            timeout_ms, start_time, old, old_start, old_end, new, new_start, new_end, vf, vb,
321        )? {
322            conquer(
323                proxy,
324                should_use_dj,
325                timeout_ms,
326                start_time,
327                old,
328                old_start,
329                x_start,
330                new,
331                new_start,
332                y_start,
333                vf,
334                vb,
335            )?;
336            conquer(
337                proxy,
338                should_use_dj,
339                timeout_ms,
340                start_time,
341                old,
342                x_start,
343                old_end,
344                new,
345                y_start,
346                new_end,
347                vf,
348                vb,
349            )?;
350        } else {
351            proxy.delete(old_start, old_end - old_start);
352            proxy.insert(old_start, new_start, new_end - new_start);
353        }
354    }
355
356    Ok(())
357}
358
359/// Return false if this method gives up early
360#[must_use]
361pub(crate) fn dj_diff<D: DiffHandler>(
362    proxy: &mut OperateProxy<D>,
363    old: &[u32],
364    new: &[u32],
365    old_offset: usize,
366    new_offset: usize,
367    max_try_count: usize,
368) -> bool {
369    let common_prefix_len = common_prefix(old, new);
370    let common_suffix_len = common_suffix_len(&old[common_prefix_len..], &new[common_prefix_len..]);
371    let old = &old[common_prefix_len..old.len() - common_suffix_len];
372    let new = &new[common_prefix_len..new.len() - common_suffix_len];
373    if old.len() >= u16::MAX as usize || new.len() >= u16::MAX as usize {
374        return false;
375    }
376
377    if old.is_empty() {
378        if new.is_empty() {
379            return true;
380        }
381
382        proxy.insert(
383            old_offset + common_prefix_len,
384            new_offset + common_prefix_len,
385            new.len(),
386        );
387        return true;
388    }
389
390    if new.is_empty() {
391        proxy.delete(old_offset + common_prefix_len, old.len());
392        return true;
393    }
394
395    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
396    struct Point {
397        x: u16,
398        y: u16,
399    }
400
401    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
402    enum Direction {
403        Up,
404        Left,
405        UpLeft,
406    }
407
408    struct QueueItem {
409        point: Point,
410        cost: u32,
411        from: Direction,
412    }
413
414    impl PartialEq for QueueItem {
415        fn eq(&self, other: &Self) -> bool {
416            self.cost == other.cost
417        }
418    }
419
420    impl PartialOrd for QueueItem {
421        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
422            Some(self.cmp(other))
423        }
424    }
425
426    impl Ord for QueueItem {
427        fn cmp(&self, other: &Self) -> Ordering {
428            other.cost.cmp(&self.cost)
429        }
430    }
431
432    impl Eq for QueueItem {}
433
434    let mut visited: FxHashMap<Point, Direction> = FxHashMap::default();
435    let mut q: BinaryHeap<QueueItem> = BinaryHeap::new();
436    q.push(QueueItem {
437        point: Point { x: 0, y: 0 },
438        cost: 0,
439        from: Direction::UpLeft,
440    });
441
442    while let Some(QueueItem { point, cost, from }) = q.pop() {
443        if visited.contains_key(&point) {
444            continue;
445        }
446
447        visited.insert(point, from);
448        if point.x == old.len() as u16 && point.y == new.len() as u16 {
449            break;
450        }
451
452        if visited.len() + q.len() > max_try_count {
453            // println!("give up on: visited len: {}", visited.len());
454            // println!("queue len: {}", q.len());
455            return false;
456        }
457
458        if point.x < old.len() as u16 {
459            let next_point = Point {
460                x: point.x + 1,
461                y: point.y,
462            };
463
464            if !visited.contains_key(&next_point) {
465                let mut next_cost = cost + 1;
466                if from != Direction::Left {
467                    next_cost += 8;
468                }
469
470                q.push(QueueItem {
471                    point: next_point,
472                    cost: next_cost,
473                    from: Direction::Left,
474                });
475            }
476        }
477
478        if point.y < new.len() as u16 {
479            let next_point = Point {
480                x: point.x,
481                y: point.y + 1,
482            };
483
484            if !visited.contains_key(&next_point) {
485                let direction = Direction::Up;
486                let mut next_cost = cost + 1;
487                if from != direction {
488                    next_cost += 8;
489                }
490
491                q.push(QueueItem {
492                    point: next_point,
493                    cost: next_cost,
494                    from: Direction::Up,
495                });
496            }
497        }
498
499        if point.x < old.len() as u16
500            && point.y < new.len() as u16
501            && old[point.x as usize] == new[point.y as usize]
502        {
503            let next_point = Point {
504                x: point.x + 1,
505                y: point.y + 1,
506            };
507
508            if !visited.contains_key(&next_point) {
509                let next_cost = if from == Direction::UpLeft {
510                    cost
511                } else {
512                    cost + 1
513                };
514
515                q.push(QueueItem {
516                    point: next_point,
517                    cost: next_cost,
518                    from: Direction::UpLeft,
519                });
520            }
521        }
522    }
523
524    // println!("visited len: {}", visited.len());
525    // println!("queue len: {}", q.len());
526
527    // Backtrack from end point to construct diff operations
528    let mut current = Point {
529        x: old.len() as u16,
530        y: new.len() as u16,
531    };
532
533    let mut path: Vec<(Direction, usize)> = Vec::new();
534    while current.x > 0 || current.y > 0 {
535        let direction = visited.get(&current).unwrap();
536        if let Some((last_dir, count)) = path.last_mut() {
537            if last_dir == direction {
538                *count += 1;
539            } else {
540                path.push((*direction, 1));
541            }
542        } else {
543            path.push((*direction, 1));
544        }
545        match direction {
546            Direction::Left => {
547                current.x -= 1;
548            }
549            Direction::Up => {
550                current.y -= 1;
551            }
552            Direction::UpLeft => {
553                current.x -= 1;
554                current.y -= 1;
555            }
556        }
557    }
558
559    path.reverse();
560
561    let mut old_index = common_prefix_len;
562    let mut new_index = common_prefix_len;
563
564    for (direction, count) in path {
565        match direction {
566            Direction::Left => {
567                proxy.delete(old_offset + old_index, count);
568                old_index += count;
569            }
570            Direction::Up => {
571                proxy.insert(old_offset + old_index, new_index + new_offset, count);
572                new_index += count;
573            }
574            Direction::UpLeft => {
575                old_index += count;
576                new_index += count;
577            }
578        }
579    }
580
581    true
582}
583
584#[cfg(test)]
585mod tests {
586    use super::*;
587
588    #[derive(Debug, Default)]
589    struct RecordingDiffHandler {
590        ops: Vec<DiffOperation>,
591    }
592
593    #[derive(Debug, PartialEq)]
594    enum DiffOperation {
595        Insert {
596            old_index: usize,
597            new_index: usize,
598            length: usize,
599        },
600        Delete {
601            old_index: usize,
602            length: usize,
603        },
604    }
605
606    impl DiffHandler for RecordingDiffHandler {
607        fn insert(&mut self, pos: usize, start: usize, len: usize) {
608            self.ops.push(DiffOperation::Insert {
609                old_index: pos,
610                new_index: start,
611                length: len,
612            });
613        }
614
615        fn delete(&mut self, pos: usize, len: usize) {
616            self.ops.push(DiffOperation::Delete {
617                old_index: pos,
618                length: len,
619            });
620        }
621    }
622
623    #[test]
624    fn test_recording_diff_handler() {
625        let handler = RecordingDiffHandler::default();
626        let mut proxy = OperateProxy::new(handler);
627        let old = vec![1, 2, 3];
628        let new = vec![1, 4, 3];
629
630        let _ = dj_diff(&mut proxy, &old, &new, 0, 0, 100_000);
631        let handler = proxy.unwrap();
632        assert_eq!(
633            handler.ops,
634            vec![
635                DiffOperation::Delete {
636                    old_index: 1,
637                    length: 1
638                },
639                DiffOperation::Insert {
640                    old_index: 2,
641                    new_index: 1,
642                    length: 1
643                },
644            ]
645        );
646    }
647
648    #[test]
649    fn test_dj_diff_same() {
650        let handler = RecordingDiffHandler::default();
651        let mut proxy = OperateProxy::new(handler);
652        let old = vec![1, 2, 3];
653        let new = vec![1, 2, 3];
654        let _ = dj_diff(&mut proxy, &old, &new, 0, 0, 100_000);
655        let handler = proxy.unwrap();
656        assert_eq!(handler.ops, vec![]);
657    }
658
659    #[test]
660    fn test_dj_diff_1() {
661        let handler = RecordingDiffHandler::default();
662        let mut proxy = OperateProxy::new(handler);
663        let old = vec![1];
664        let new = vec![0, 1, 2];
665        let _ = dj_diff(&mut proxy, &old, &new, 0, 0, 100_000);
666        let handler = proxy.unwrap();
667        assert_eq!(
668            handler.ops,
669            vec![
670                DiffOperation::Insert {
671                    old_index: 0,
672                    new_index: 0,
673                    length: 1
674                },
675                DiffOperation::Insert {
676                    old_index: 1,
677                    new_index: 2,
678                    length: 1
679                },
680            ]
681        );
682    }
683
684    #[test]
685    fn test_diff_may_scatter() {
686        let handler = RecordingDiffHandler::default();
687        let mut proxy = OperateProxy::new(handler);
688        let old = vec![1, 2, 3, 4, 5];
689        let new = vec![99, 1, 2, 3, 4, 5, 98, 97, 96, 3, 95, 4, 93, 92, 5, 91];
690        diff(&mut proxy, UpdateOptions::default(), &old, &new).unwrap();
691        let handler = proxy.unwrap();
692        assert_eq!(
693            handler.ops,
694            vec![
695                DiffOperation::Insert {
696                    old_index: 0,
697                    new_index: 0,
698                    length: 1
699                },
700                DiffOperation::Insert {
701                    old_index: 5,
702                    new_index: 6,
703                    length: 10
704                },
705            ]
706        );
707    }
708
709    #[test]
710    fn test_diff_may_scatter_1() {
711        let handler = RecordingDiffHandler::default();
712        let mut proxy = OperateProxy::new(handler);
713        let old = vec![1, 2, 3, 4, 5];
714        let new = vec![99, 1, 2, 98, 97, 96, 3, 95, 4, 93, 92, 5, 1, 2, 3, 4, 5, 91];
715        diff(&mut proxy, UpdateOptions::default(), &old, &new).unwrap();
716        let handler = proxy.unwrap();
717        assert_eq!(
718            handler.ops,
719            vec![
720                DiffOperation::Insert {
721                    old_index: 0,
722                    new_index: 0,
723                    length: 12
724                },
725                DiffOperation::Insert {
726                    old_index: 5,
727                    new_index: 17,
728                    length: 1
729                },
730            ]
731        );
732    }
733
734    #[test]
735    fn test_dj_diff_may_scatter() {
736        let handler = RecordingDiffHandler::default();
737        let mut proxy = OperateProxy::new(handler);
738        let old = vec![1, 2, 3, 4, 5];
739        let new = vec![99, 1, 2, 3, 4, 5, 98, 97, 96, 3, 95, 4, 93, 92, 5, 91];
740        let _ = dj_diff(&mut proxy, &old, &new, 0, 0, 100_000);
741        let handler = proxy.unwrap();
742        assert_eq!(
743            handler.ops,
744            vec![
745                DiffOperation::Insert {
746                    old_index: 0,
747                    new_index: 0,
748                    length: 1
749                },
750                DiffOperation::Insert {
751                    old_index: 5,
752                    new_index: 6,
753                    length: 10
754                },
755            ]
756        );
757    }
758
759    #[test]
760    fn test_dj_diff_may_scatter_1() {
761        let handler = RecordingDiffHandler::default();
762        let mut proxy = OperateProxy::new(handler);
763        let old = vec![1, 2, 3, 4, 5];
764        let new = vec![99, 1, 2, 98, 97, 96, 3, 95, 4, 93, 92, 5, 1, 2, 3, 4, 5, 91];
765        let _ = dj_diff(&mut proxy, &old, &new, 0, 0, 100_000);
766        let handler = proxy.unwrap();
767        assert_eq!(
768            handler.ops,
769            vec![
770                DiffOperation::Insert {
771                    old_index: 0,
772                    new_index: 0,
773                    length: 12
774                },
775                DiffOperation::Insert {
776                    old_index: 5,
777                    new_index: 17,
778                    length: 1
779                },
780            ]
781        );
782    }
783
784    #[test]
785    fn test_dj_diff_100_differences() {
786        let handler = RecordingDiffHandler::default();
787        let mut proxy = OperateProxy::new(handler);
788        let old = vec![1; 100];
789        let new = vec![2; 100];
790        let _ = dj_diff(&mut proxy, &old, &new, 0, 0, 100_000);
791        let handler = proxy.unwrap();
792        assert_eq!(
793            handler.ops,
794            vec![
795                DiffOperation::Delete {
796                    old_index: 0,
797                    length: 100
798                },
799                DiffOperation::Insert {
800                    old_index: 100,
801                    new_index: 0,
802                    length: 100
803                },
804            ]
805        );
806    }
807
808    #[test]
809    fn test_dj_diff_insert() {
810        let handler = RecordingDiffHandler::default();
811        let mut proxy = OperateProxy::new(handler);
812        let old = vec![1; 100];
813        let mut new = old.clone();
814        new.splice(50..50, [1, 2, 3, 4, 1, 2, 3, 4]);
815        new.splice(0..0, [0, 1, 2, 3, 4, 5, 6, 7]);
816        let _ = dj_diff(&mut proxy, &old, &new, 0, 0, 100_000);
817        let handler = proxy.unwrap();
818        assert_eq!(
819            handler.ops,
820            vec![
821                DiffOperation::Insert {
822                    old_index: 0,
823                    new_index: 0,
824                    length: 9
825                },
826                DiffOperation::Insert {
827                    old_index: 50,
828                    new_index: 59,
829                    length: 7
830                }
831            ]
832        );
833    }
834
835    #[test]
836    fn test_diff() {
837        let handler = RecordingDiffHandler::default();
838        let mut proxy = OperateProxy::new(handler);
839        let old = vec![1; 100];
840        let new = vec![2; 100];
841        diff(&mut proxy, Default::default(), &old, &new).unwrap();
842    }
843
844    #[test]
845    fn test_timeout() {
846        let handler = RecordingDiffHandler::default();
847        let mut proxy = OperateProxy::new(handler);
848        let old = vec![1; 10000];
849        let new = vec![2; 10000];
850        let options = UpdateOptions {
851            timeout_ms: Some(0.1),
852            ..Default::default()
853        };
854        let result = diff(&mut proxy, options, &old, &new);
855        assert!(result.is_err());
856    }
857}