matrix_sdk_common/linked_chunk/
as_vector.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    collections::VecDeque,
17    iter::repeat_n,
18    ops::{ControlFlow, Not},
19    sync::{Arc, RwLock},
20};
21
22use eyeball_im::VectorDiff;
23
24use super::{
25    updates::{ReaderToken, Update, UpdatesInner},
26    ChunkContent, ChunkIdentifier, Iter, Position,
27};
28
29/// A type alias to represent a chunk's length. This is purely for commodity.
30type ChunkLength = usize;
31
32/// A type that transforms a `Vec<Update<Item, Gap>>` (given by
33/// [`ObservableUpdates::take`](super::ObservableUpdates::take)) into a
34/// `Vec<VectorDiff<Item>>` (this type). Basically, it helps to consume a
35/// [`LinkedChunk<CAP, Item, Gap>`](super::LinkedChunk) as if it was an
36/// [`eyeball_im::ObservableVector<Item>`].
37#[derive(Debug)]
38pub struct AsVector<Item, Gap> {
39    /// Strong reference to [`UpdatesInner`].
40    updates: Arc<RwLock<UpdatesInner<Item, Gap>>>,
41
42    /// The token to read the updates.
43    token: ReaderToken,
44
45    /// Mapper from `Update` to `VectorDiff`.
46    mapper: UpdateToVectorDiff,
47}
48
49impl<Item, Gap> AsVector<Item, Gap> {
50    /// Create a new [`AsVector`].
51    ///
52    /// `updates` is the inner value of
53    /// [`ObservableUpdates`][super::updates::ObservableUpdates].
54    /// It's required to read the new [`Update`]s. `token` is the
55    /// [`ReaderToken`] necessary for this type to read the [`Update`]s.
56    /// `chunk_iterator` is the iterator of all [`Chunk`](super::Chunk)s, used
57    /// to set up its internal state.
58    pub(super) fn new<const CAP: usize>(
59        updates: Arc<RwLock<UpdatesInner<Item, Gap>>>,
60        token: ReaderToken,
61        chunk_iterator: Iter<'_, CAP, Item, Gap>,
62    ) -> Self {
63        // Drain previous updates so that this type is synced with `Updates`.
64        {
65            let mut updates = updates.write().unwrap();
66            let _ = updates.take_with_token(token);
67        }
68
69        Self { updates, token, mapper: UpdateToVectorDiff::new(chunk_iterator) }
70    }
71
72    /// Take the new updates as [`VectorDiff`].
73    ///
74    /// It returns an empty `Vec` if there is no new `VectorDiff` for the
75    /// moment.
76    pub fn take(&mut self) -> Vec<VectorDiff<Item>>
77    where
78        Item: Clone,
79    {
80        let mut updates = self.updates.write().unwrap();
81
82        self.mapper.map(updates.take_with_token(self.token))
83    }
84}
85
86/// Internal type that converts [`Update`] into [`VectorDiff`].
87#[derive(Debug)]
88struct UpdateToVectorDiff {
89    /// Pairs of all known chunks and their respective length. This is the only
90    /// required data for this algorithm.
91    chunks: VecDeque<(ChunkIdentifier, ChunkLength)>,
92}
93
94impl UpdateToVectorDiff {
95    /// Construct [`UpdateToVectorDiff`], based on an iterator of
96    /// [`Chunk`](super::Chunk)s, used to set up its own internal state.
97    ///
98    /// See [`Self::map`] to learn more about the algorithm.
99    fn new<const CAP: usize, Item, Gap>(chunk_iterator: Iter<'_, CAP, Item, Gap>) -> Self {
100        let mut initial_chunk_lengths = VecDeque::new();
101
102        for chunk in chunk_iterator {
103            initial_chunk_lengths.push_back((
104                chunk.identifier(),
105                match chunk.content() {
106                    ChunkContent::Gap(_) => 0,
107                    ChunkContent::Items(items) => items.len(),
108                },
109            ))
110        }
111
112        Self { chunks: initial_chunk_lengths }
113    }
114
115    /// Map several [`Update`] into [`VectorDiff`].
116    ///
117    /// How does this type transform `Update` into `VectorDiff`? There is no
118    /// internal buffer of kind [`eyeball_im::ObservableVector<Item>`],
119    /// which could have been used to generate the `VectorDiff`s. They are
120    /// computed manually.
121    ///
122    /// The only buffered data is pairs of [`ChunkIdentifier`] and
123    /// [`ChunkLength`]. The following rules must be respected (they are defined
124    /// in [`Self::new`]):
125    ///
126    /// * A chunk of kind [`ChunkContent::Gap`] has a length of 0,
127    /// * A chunk of kind [`ChunkContent::Items`] has a length equals to its
128    ///   number of items,
129    /// * The pairs must be ordered exactly like the chunks in [`LinkedChunk`],
130    ///   i.e. the first pair must represent the first chunk, the last pair must
131    ///   represent the last chunk.
132    ///
133    /// The only thing this algorithm does is maintaining the pairs:
134    ///
135    /// * [`Update::NewItemsChunk`] and [`Update::NewGapChunk`] are inserting a
136    ///   new pair with a chunk length of 0 at the appropriate index,
137    /// * [`Update::RemoveChunk`] is removing a pair, and is potentially
138    ///   emitting [`VectorDiff`],
139    /// * [`Update::PushItems`] is increasing the length of the appropriate pair
140    ///   by the number of new items, and is potentially emitting
141    ///   [`VectorDiff`],
142    /// * [`Update::DetachLastItems`] is decreasing the length of the
143    ///   appropriate pair by the number of items to be detached; no
144    ///   [`VectorDiff`] is emitted,
145    /// * [`Update::StartReattachItems`] and [`Update::EndReattachItems`] are
146    ///   respectively muting or unmuting the emission of [`VectorDiff`] by
147    ///   [`Update::PushItems`],
148    /// * [`Update::Clear`] reinitialises the state.
149    ///
150    /// The only `VectorDiff` that are emitted are [`VectorDiff::Insert`],
151    /// [`VectorDiff::Append`], [`VectorDiff::Remove`] and
152    /// [`VectorDiff::Clear`].
153    ///
154    /// `VectorDiff::Append` is an optimisation when numerous
155    /// `VectorDiff::Insert`s have to be emitted at the last position.
156    ///
157    /// `VectorDiff::Insert` needs an index. To compute this index, the
158    /// algorithm will iterate over all pairs to accumulate each chunk length
159    /// until it finds the appropriate pair (given by
160    /// [`Update::PushItems::at`]). This is _the offset_. To this offset, the
161    /// algorithm adds the position's index of the new items (still given by
162    /// [`Update::PushItems::at`]). This is _the index_. This logic works
163    /// for all cases as long as pairs are maintained according to the rules
164    /// hereinabove.
165    ///
166    /// That's a pretty memory compact and computation efficient way to map a
167    /// `Vec<Update<Item, Gap>>` into a `Vec<VectorDiff<Item>>`. The larger the
168    /// `LinkedChunk` capacity is, the fewer pairs the algorithm will have
169    /// to handle, e.g. for 1'000 items and a `LinkedChunk` capacity of 128,
170    /// it's only 8 pairs, that is 256 bytes.
171    ///
172    /// [`LinkedChunk`]: super::LinkedChunk
173    /// [`ChunkContent::Gap`]: super::ChunkContent::Gap
174    /// [`ChunkContent::Content`]: super::ChunkContent::Content
175    fn map<Item, Gap>(&mut self, updates: &[Update<Item, Gap>]) -> Vec<VectorDiff<Item>>
176    where
177        Item: Clone,
178    {
179        let mut diffs = Vec::with_capacity(updates.len());
180
181        // A flag specifying when updates are reattaching detached items.
182        //
183        // Why is it useful?
184        //
185        // Imagine a `LinkedChunk::<3, char, ()>` containing `['a', 'b', 'c'] ['d']`. If
186        // one wants to insert [`w`, x`, 'y', 'z'] at position
187        // `Position(ChunkIdentifier(0), 1)`, i.e. at the position of `b`, here is what
188        // happens:
189        //
190        // 1. `LinkedChunk` will split off `['a', 'b', 'c']` at index 1, the chunk
191        //    becomes `['a']` and `b` and `c` are _detached_, thus we have:
192        //
193        //     ['a'] ['d']
194        //
195        // 2. `LinkedChunk` will then insert `w`, `x`, `y` and `z` to get:
196        //
197        //     ['a', 'w', 'x'] ['y', 'z'] ['d']
198        //
199        // 3. `LinkedChunk` will now reattach `b` and `c` after `z`, like so:
200        //
201        //     ['a', 'w', 'x'] ['y', 'z', 'b'] ['c'] ['d']
202        //
203        // This detaching/reattaching approach makes it reliable and safe. Good. Now,
204        // what updates are we going to receive for each step?
205        //
206        // Step 1, detaching last items:
207        //
208        // ```
209        // Update::DetachLastItems { at: Position(ChunkIdentifier(0), 1) }
210        // ```
211        //
212        // Step 2, inserting new items:
213        //
214        // ```
215        // Update::PushItems {
216        //     at: Position(ChunkIdentifier(0), 1),
217        //     items: vec!['w', 'x'],
218        // }
219        // Update::NewItemsChunk {
220        //     previous: Some(ChunkIdentifier(0)),
221        //     new: ChunkIdentifier(2),
222        //     next: Some(ChunkIdentifier(1)),
223        // }
224        // Update::PushItems {
225        //     at: Position(ChunkIdentifier(2), 0),
226        //     items: vec!['y', 'z'],
227        // }
228        // ```
229        //
230        // Step 3, reattaching detached items:
231        //
232        // ```
233        // Update::StartReattachItems
234        // Update::PushItems {
235        //     at: Position(ChunkIdentifier(2), 2),
236        //     items: vec!['b']
237        // }
238        // Update::NewItemsChunk {
239        //     previous: Some(ChunkIdentifier(2)),
240        //     new: ChunkIdentifier(3),
241        //     next: Some(ChunkIdentifier(1)),
242        // }
243        // Update::PushItems {
244        //     at: Position(ChunkIdentifier(3), 0),
245        //     items: vec!['c'],
246        // }
247        // Update::EndReattachItems
248        // ```
249        //
250        // To ensure an optimised behaviour of this algorithm:
251        //
252        // * `Update::DetachLastItems` must not emit `VectorDiff::Remove`,
253        //
254        // * `Update::PushItems` must not emit `VectorDiff::Insert`s or
255        //   `VectorDiff::Append`s if it happens after `StartReattachItems` and before
256        //   `EndReattachItems`. However, `Self::chunks` must always be updated.
257        //
258        // From the `VectorDiff` “point of view”, this optimisation aims at avoiding
259        // removing items to push them again later.
260        let mut reattaching = false;
261        let mut detaching = false;
262
263        for update in updates {
264            match update {
265                Update::NewItemsChunk { previous, new, next }
266                | Update::NewGapChunk { previous, new, next, .. } => {
267                    match (previous, next) {
268                        // New chunk at the end.
269                        (Some(_previous), None) => {
270                            // No need to check `previous`. It's possible that the linked chunk is
271                            // lazily loaded, chunk by chunk. The `next` is always reliable, but the
272                            // `previous` might not exist in-memory yet.
273
274                            self.chunks.push_back((*new, 0));
275                        }
276
277                        // New chunk at the beginning.
278                        (None, Some(next)) => {
279                            debug_assert!(
280                                matches!(self.chunks.front(), Some((n, _)) if n == next),
281                                "Inserting new chunk at the end: The previous chunk is invalid"
282                            );
283
284                            self.chunks.push_front((*new, 0));
285                        }
286
287                        // New chunk is inserted between 2 chunks.
288                        (Some(_previous), Some(next)) => {
289                            let next_chunk_index = self
290                                .chunks
291                                .iter()
292                                .position(|(chunk_identifier, _)| chunk_identifier == next)
293                                // SAFETY: Assuming `LinkedChunk` and `ObservableUpdates` are not
294                                // buggy, and assuming `Self::chunks` is correctly initialized, it
295                                // is not possible to insert a chunk between two chunks where one
296                                // does not exist. If this predicate fails, it means `LinkedChunk`
297                                // or `ObservableUpdates` contain a bug.
298                                .expect("Inserting new chunk: The chunk is not found");
299
300                            // No need to check `previous`. It's possible that the linked chunk is
301                            // lazily loaded, chunk by chunk. The `next` is always reliable, but the
302                            // `previous` might not exist in-memory yet.
303
304                            self.chunks.insert(next_chunk_index, (*new, 0));
305                        }
306
307                        // First chunk!
308                        (None, None) if self.chunks.is_empty() => {
309                            self.chunks.push_back((*new, 0));
310                        }
311
312                        // Impossible state.
313                        (None, None) => {
314                            unreachable!(
315                                "Inserting new chunk with no previous nor next chunk identifiers \
316                                is impossible"
317                            );
318                        }
319                    }
320                }
321
322                Update::RemoveChunk(chunk_identifier) => {
323                    let (offset, (chunk_index, _)) =
324                        self.map_to_offset(&Position(*chunk_identifier, 0));
325
326                    let (_, number_of_items) = self
327                        .chunks
328                        .remove(chunk_index)
329                        .expect("Removing an index out of the bounds");
330
331                    // Removing at the same index because each `Remove` shifts items to the left.
332                    diffs.extend(repeat_n(VectorDiff::Remove { index: offset }, number_of_items));
333                }
334
335                Update::PushItems { at: position, items } => {
336                    let number_of_chunks = self.chunks.len();
337                    let (offset, (chunk_index, chunk_length)) = self.map_to_offset(position);
338
339                    let is_pushing_back =
340                        chunk_index + 1 == number_of_chunks && position.index() >= *chunk_length;
341
342                    // Add the number of items to the chunk in `self.chunks`.
343                    *chunk_length += items.len();
344
345                    // See `reattaching` to learn more.
346                    if reattaching {
347                        continue;
348                    }
349
350                    // Optimisation: we can emit a `VectorDiff::Append` in this particular case.
351                    if is_pushing_back && detaching.not() {
352                        diffs.push(VectorDiff::Append { values: items.into() });
353                    }
354                    // No optimisation: let's emit `VectorDiff::Insert`.
355                    else {
356                        diffs.extend(items.iter().enumerate().map(|(nth, item)| {
357                            VectorDiff::Insert { index: offset + nth, value: item.clone() }
358                        }));
359                    }
360                }
361
362                Update::ReplaceItem { at: position, item } => {
363                    let (offset, (_chunk_index, _chunk_length)) = self.map_to_offset(position);
364
365                    // The chunk length doesn't change.
366
367                    diffs.push(VectorDiff::Set { index: offset, value: item.clone() });
368                }
369
370                Update::RemoveItem { at: position } => {
371                    let (offset, (_chunk_index, chunk_length)) = self.map_to_offset(position);
372
373                    // Remove one item to the chunk in `self.chunks`.
374                    *chunk_length -= 1;
375
376                    // See `reattaching` to learn more.
377                    if reattaching {
378                        continue;
379                    }
380
381                    // Let's emit a `VectorDiff::Remove`.
382                    diffs.push(VectorDiff::Remove { index: offset });
383                }
384
385                Update::DetachLastItems { at: position } => {
386                    let expected_chunk_identifier = position.chunk_identifier();
387                    let new_length = position.index();
388
389                    let chunk_length = self
390                        .chunks
391                        .iter_mut()
392                        .find_map(|(chunk_identifier, chunk_length)| {
393                            (*chunk_identifier == expected_chunk_identifier).then_some(chunk_length)
394                        })
395                        // SAFETY: Assuming `LinkedChunk` and `ObservableUpdates` are not buggy, and
396                        // assuming `Self::chunks` is correctly initialized, it is not possible to
397                        // detach items from a chunk that does not exist. If this predicate fails,
398                        // it means `LinkedChunk` or `ObservableUpdates` contain a bug.
399                        .expect("Detach last items: The chunk is not found");
400
401                    *chunk_length = new_length;
402
403                    // Entering the _detaching_ mode.
404                    detaching = true;
405                }
406
407                Update::StartReattachItems => {
408                    // Entering the _reattaching_ mode.
409                    reattaching = true;
410                }
411
412                Update::EndReattachItems => {
413                    // Exiting the _reattaching_ mode.
414                    reattaching = false;
415
416                    // Exiting the _detaching_ mode.
417                    detaching = false;
418                }
419
420                Update::Clear => {
421                    // Clean `self.chunks`.
422                    self.chunks.clear();
423
424                    // Let's straightforwardly emit a `VectorDiff::Clear`.
425                    diffs.push(VectorDiff::Clear);
426                }
427            }
428        }
429
430        diffs
431    }
432
433    fn map_to_offset(&mut self, position: &Position) -> (usize, (usize, &mut usize)) {
434        let expected_chunk_identifier = position.chunk_identifier();
435
436        let (offset, (chunk_index, chunk_length)) = {
437            let control_flow = self.chunks.iter_mut().enumerate().try_fold(
438                position.index(),
439                |offset, (chunk_index, (chunk_identifier, chunk_length))| {
440                    if chunk_identifier == &expected_chunk_identifier {
441                        ControlFlow::Break((offset, (chunk_index, chunk_length)))
442                    } else {
443                        ControlFlow::Continue(offset + *chunk_length)
444                    }
445                },
446            );
447
448            match control_flow {
449                // Chunk has been found, and all values have been calculated as
450                // expected.
451                ControlFlow::Break(values) => values,
452
453                // Chunk has not been found.
454                ControlFlow::Continue(..) => {
455                    // SAFETY: Assuming `LinkedChunk` and `ObservableUpdates` are not buggy, and
456                    // assuming `Self::chunks` is correctly initialized, it is not possible to work
457                    // on a chunk that does not exist. If this predicate fails, it means
458                    // `LinkedChunk` or `ObservableUpdates` contain a bug.
459                    panic!("The chunk is not found");
460                }
461            }
462        };
463
464        (offset, (chunk_index, chunk_length))
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use std::fmt::Debug;
471
472    use assert_matches::assert_matches;
473    use imbl::{vector, Vector};
474
475    use super::{
476        super::{Chunk, ChunkIdentifierGenerator, LinkedChunk, Update},
477        VectorDiff,
478    };
479
480    fn apply_and_assert_eq<Item>(
481        accumulator: &mut Vector<Item>,
482        diffs: Vec<VectorDiff<Item>>,
483        expected_diffs: &[VectorDiff<Item>],
484    ) where
485        Item: PartialEq + Clone + Debug,
486    {
487        assert_eq!(diffs, expected_diffs);
488
489        for diff in diffs {
490            diff.apply(accumulator);
491        }
492    }
493
494    #[test]
495    fn test_as_vector() {
496        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
497        let mut as_vector = linked_chunk.as_vector().unwrap();
498
499        let mut accumulator = Vector::new();
500
501        assert!(as_vector.take().is_empty());
502
503        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
504        #[rustfmt::skip]
505        assert_items_eq!(linked_chunk, ['a', 'b', 'c'] ['d']);
506
507        // From an `ObservableVector` point of view, it would look like:
508        //
509        // 0   1   2   3   4
510        // +---+---+---+---+
511        // | a | b | c | d |
512        // +---+---+---+---+
513        // ^^^^^^^^^^^^^^^^
514        // |
515        // new
516        apply_and_assert_eq(
517            &mut accumulator,
518            as_vector.take(),
519            &[
520                VectorDiff::Append { values: vector!['a', 'b', 'c'] },
521                VectorDiff::Append { values: vector!['d'] },
522            ],
523        );
524
525        linked_chunk
526            .insert_items_at(
527                ['w', 'x', 'y', 'z'],
528                linked_chunk.item_position(|item| *item == 'b').unwrap(),
529            )
530            .unwrap();
531        assert_items_eq!(linked_chunk, ['a', 'w', 'x'] ['y', 'z', 'b'] ['c'] ['d']);
532
533        // From an `ObservableVector` point of view, it would look like:
534        //
535        // 0   1   2   3   4   5   6   7   8
536        // +---+---+---+---+---+---+---+---+
537        // | a | w | x | y | z | b | c | d |
538        // +---+---+---+---+---+---+---+---+
539        //     ^^^^^^^^^^^^^^^^
540        //     |
541        //     new
542        apply_and_assert_eq(
543            &mut accumulator,
544            as_vector.take(),
545            &[
546                VectorDiff::Insert { index: 1, value: 'w' },
547                VectorDiff::Insert { index: 2, value: 'x' },
548                VectorDiff::Insert { index: 3, value: 'y' },
549                VectorDiff::Insert { index: 4, value: 'z' },
550            ],
551        );
552
553        linked_chunk.push_gap_back(());
554        linked_chunk.push_items_back(['e', 'f', 'g', 'h']);
555        assert_items_eq!(
556            linked_chunk,
557            ['a', 'w', 'x'] ['y', 'z', 'b'] ['c'] ['d'] [-] ['e', 'f', 'g'] ['h']
558        );
559
560        // From an `ObservableVector` point of view, it would look like:
561        //
562        // 0   1   2   3   4   5   6   7   8   9   10  11  12
563        // +---+---+---+---+---+---+---+---+---+---+---+---+
564        // | a | w | x | y | z | b | c | d | e | f | g | h |
565        // +---+---+---+---+---+---+---+---+---+---+---+---+
566        //                                 ^^^^^^^^^^^^^^^^
567        //                                 |
568        //                                 new
569        apply_and_assert_eq(
570            &mut accumulator,
571            as_vector.take(),
572            &[
573                VectorDiff::Append { values: vector!['e', 'f', 'g'] },
574                VectorDiff::Append { values: vector!['h'] },
575            ],
576        );
577
578        linked_chunk
579            .replace_gap_at(
580                ['i', 'j', 'k', 'l'],
581                linked_chunk.chunk_identifier(|chunk| chunk.is_gap()).unwrap(),
582            )
583            .unwrap();
584        assert_items_eq!(
585            linked_chunk,
586            ['a', 'w', 'x'] ['y', 'z', 'b'] ['c'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
587        );
588
589        // From an `ObservableVector` point of view, it would look like:
590        //
591        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
592        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
593        // | a | w | x | y | z | b | c | d | i | j | k | l | e | f | g | h |
594        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
595        //                                 ^^^^^^^^^^^^^^^^
596        //                                 |
597        //                                 new
598        apply_and_assert_eq(
599            &mut accumulator,
600            as_vector.take(),
601            &[
602                VectorDiff::Insert { index: 8, value: 'i' },
603                VectorDiff::Insert { index: 9, value: 'j' },
604                VectorDiff::Insert { index: 10, value: 'k' },
605                VectorDiff::Insert { index: 11, value: 'l' },
606            ],
607        );
608
609        linked_chunk
610            .insert_items_at(['m'], linked_chunk.item_position(|item| *item == 'a').unwrap())
611            .unwrap();
612        assert_items_eq!(
613            linked_chunk,
614            ['m', 'a', 'w'] ['x'] ['y', 'z', 'b'] ['c'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
615        );
616
617        // From an `ObservableVector` point of view, it would look like:
618        //
619        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17
620        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
621        // | m | a | w | x | y | z | b | c | d | i | j | k | l | e | f | g | h |
622        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
623        // ^^^^
624        // |
625        // new
626        apply_and_assert_eq(
627            &mut accumulator,
628            as_vector.take(),
629            &[VectorDiff::Insert { index: 0, value: 'm' }],
630        );
631
632        let removed_item = linked_chunk
633            .remove_item_at(linked_chunk.item_position(|item| *item == 'c').unwrap())
634            .unwrap();
635        assert_eq!(removed_item, 'c');
636        assert_items_eq!(
637            linked_chunk,
638            ['m', 'a', 'w'] ['x'] ['y', 'z', 'b'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
639        );
640
641        // From an `ObservableVector` point of view, it would look like:
642        //
643        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
644        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
645        // | m | a | w | x | y | z | b | d | i | j | k | l | e | f | g | h |
646        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
647        //                             ^
648        //                             |
649        //                             `c` has been removed
650        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Remove { index: 7 }]);
651
652        let removed_item = linked_chunk
653            .remove_item_at(linked_chunk.item_position(|item| *item == 'z').unwrap())
654            .unwrap();
655        assert_eq!(removed_item, 'z');
656        assert_items_eq!(
657            linked_chunk,
658            ['m', 'a', 'w'] ['x'] ['y', 'b'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
659        );
660
661        // From an `ObservableVector` point of view, it would look like:
662        //
663        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
664        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
665        // | m | a | w | x | y | b | d | i | j | k | l | e | f | g | h |
666        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
667        //                     ^
668        //                     |
669        //                     `z` has been removed
670        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Remove { index: 5 }]);
671
672        linked_chunk
673            .insert_items_at(['z'], linked_chunk.item_position(|item| *item == 'h').unwrap())
674            .unwrap();
675
676        assert_items_eq!(
677            linked_chunk,
678            ['m', 'a', 'w'] ['x'] ['y', 'b'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['z', 'h']
679        );
680
681        // From an `ObservableVector` point of view, it would look like:
682        //
683        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
684        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
685        // | m | a | w | x | y | b | d | i | j | k | l | e | f | g | z | h |
686        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
687        //                                                         ^^^^
688        //                                                         |
689        //                                                         new!
690        apply_and_assert_eq(
691            &mut accumulator,
692            as_vector.take(),
693            &[VectorDiff::Insert { index: 14, value: 'z' }],
694        );
695
696        // Ensure the “reconstitued” vector is the one expected.
697        assert_eq!(
698            accumulator,
699            vector!['m', 'a', 'w', 'x', 'y', 'b', 'd', 'i', 'j', 'k', 'l', 'e', 'f', 'g', 'z', 'h']
700        );
701
702        // Replace element 8 by an uppercase J.
703        linked_chunk
704            .replace_item_at(linked_chunk.item_position(|item| *item == 'j').unwrap(), 'J')
705            .unwrap();
706
707        assert_items_eq!(
708            linked_chunk,
709            ['m', 'a', 'w'] ['x'] ['y', 'b'] ['d'] ['i', 'J', 'k'] ['l'] ['e', 'f', 'g'] ['z', 'h']
710        );
711
712        // From an `ObservableVector` point of view, it would look like:
713        //
714        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
715        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
716        // | m | a | w | x | y | b | d | i | J | k | l | e | f | g | z | h |
717        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
718        //                                 ^^^^
719        //                                 |
720        //                                 new!
721        apply_and_assert_eq(
722            &mut accumulator,
723            as_vector.take(),
724            &[VectorDiff::Set { index: 8, value: 'J' }],
725        );
726
727        // Let's try to clear the linked chunk now.
728        linked_chunk.clear();
729
730        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Clear]);
731        assert!(accumulator.is_empty());
732
733        drop(linked_chunk);
734        assert!(as_vector.take().is_empty());
735    }
736
737    #[test]
738    fn test_as_vector_with_update_clear() {
739        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
740        let mut as_vector = linked_chunk.as_vector().unwrap();
741
742        {
743            // 1 initial chunk in the `UpdateToVectorDiff` mapper.
744            let chunks = &as_vector.mapper.chunks;
745            assert_eq!(chunks.len(), 1);
746            assert_eq!(chunks[0].0, ChunkIdentifierGenerator::FIRST_IDENTIFIER);
747            assert_eq!(chunks[0].1, 0);
748
749            assert!(as_vector.take().is_empty());
750        }
751
752        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
753
754        {
755            let diffs = as_vector.take();
756            assert_eq!(diffs.len(), 2);
757            assert_matches!(&diffs[0], VectorDiff::Append { .. });
758            assert_matches!(&diffs[1], VectorDiff::Append { .. });
759
760            // 2 chunks in the `UpdateToVectorDiff` mapper.
761            assert_eq!(as_vector.mapper.chunks.len(), 2);
762        }
763
764        linked_chunk.clear();
765
766        {
767            let diffs = as_vector.take();
768            assert_eq!(diffs.len(), 1);
769            assert_matches!(&diffs[0], VectorDiff::Clear);
770
771            // 1 chunk in the `UpdateToVectorDiff` mapper.
772            let chunks = &as_vector.mapper.chunks;
773            assert_eq!(chunks.len(), 1);
774            assert_eq!(chunks[0].0, ChunkIdentifierGenerator::FIRST_IDENTIFIER);
775            assert_eq!(chunks[0].1, 0);
776        }
777
778        // And we can push again.
779        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
780
781        {
782            let diffs = as_vector.take();
783            assert_eq!(diffs.len(), 2);
784            assert_matches!(&diffs[0], VectorDiff::Append { .. });
785            assert_matches!(&diffs[1], VectorDiff::Append { .. });
786        }
787    }
788
789    #[test]
790    fn test_updates_are_drained_when_constructing_as_vector() {
791        let mut linked_chunk = LinkedChunk::<10, char, ()>::new_with_update_history();
792
793        linked_chunk.push_items_back(['a']);
794
795        let mut as_vector = linked_chunk.as_vector().unwrap();
796        let diffs = as_vector.take();
797
798        // `diffs` are empty because `AsVector` is built _after_ `LinkedChunk`
799        // has been updated.
800        assert!(diffs.is_empty());
801
802        linked_chunk.push_items_back(['b']);
803
804        let diffs = as_vector.take();
805
806        // `diffs` is not empty because new updates are coming.
807        assert_eq!(diffs.len(), 1);
808    }
809
810    #[test]
811    fn test_as_vector_with_initial_content() {
812        // Fill the linked chunk with some initial items.
813        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
814        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
815
816        #[rustfmt::skip]
817        assert_items_eq!(linked_chunk, ['a', 'b', 'c'] ['d']);
818
819        // Empty updates first.
820        let _ = linked_chunk.updates().unwrap().take();
821
822        // Start observing future updates.
823        let mut as_vector = linked_chunk.as_vector().unwrap();
824
825        assert!(as_vector.take().is_empty());
826
827        // It's important to cause a change that will create new chunks, like pushing
828        // enough items.
829        linked_chunk.push_items_back(['e', 'f', 'g']);
830        #[rustfmt::skip]
831        assert_items_eq!(linked_chunk, ['a', 'b', 'c'] ['d', 'e', 'f'] ['g']);
832
833        // And the vector diffs can be computed without crashing.
834        let diffs = as_vector.take();
835        assert_eq!(diffs.len(), 2);
836        assert_matches!(&diffs[0], VectorDiff::Append { values } => {
837            assert_eq!(*values, ['e', 'f'].into());
838        });
839        assert_matches!(&diffs[1], VectorDiff::Append { values } => {
840            assert_eq!(*values, ['g'].into());
841        });
842    }
843
844    #[test]
845    fn test_as_vector_remove_chunk() {
846        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
847        let mut as_vector = linked_chunk.as_vector().unwrap();
848
849        let mut accumulator = Vector::new();
850
851        assert!(as_vector.take().is_empty());
852
853        linked_chunk.push_items_back(['a', 'b']);
854        linked_chunk.push_gap_back(());
855        linked_chunk.push_items_back(['c']);
856        linked_chunk.push_gap_back(());
857        linked_chunk.push_items_back(['d', 'e', 'f', 'g']);
858
859        assert_items_eq!(linked_chunk, ['a', 'b'] [-] ['c'] [-] ['d', 'e', 'f'] ['g']);
860
861        // From an `ObservableVector` point of view, it would look like:
862        //
863        // 0   1   2   3   4   5   6   7
864        // +---+---+---+---+---+---+---+
865        // | a | b | c | d | e | f | g |
866        // +---+---+---+---+---+---+---+
867        // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
868        // |
869        // new
870        apply_and_assert_eq(
871            &mut accumulator,
872            as_vector.take(),
873            &[
874                VectorDiff::Append { values: vector!['a', 'b'] },
875                VectorDiff::Append { values: vector!['c'] },
876                VectorDiff::Append { values: vector!['d', 'e', 'f'] },
877                VectorDiff::Append { values: vector!['g'] },
878            ],
879        );
880
881        // Empty a chunk, and remove it once it is empty.
882        linked_chunk
883            .remove_item_at(linked_chunk.item_position(|item| *item == 'c').unwrap())
884            .unwrap();
885
886        assert_items_eq!(linked_chunk, ['a', 'b'] [-] [-] ['d', 'e', 'f'] ['g']);
887
888        // From an `ObservableVector` point of view, it would look like:
889        //
890        // 0   1   2   3   4   5   6
891        // +---+---+---+---+---+---+
892        // | a | b | d | e | f | g |
893        // +---+---+---+---+---+---+
894        //         ^
895        //         |
896        //         `c` has been removed
897        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Remove { index: 2 }]);
898
899        // Remove a gap.
900        linked_chunk
901            .remove_empty_chunk_at(linked_chunk.chunk_identifier(Chunk::is_gap).unwrap())
902            .unwrap();
903
904        assert_items_eq!(linked_chunk, ['a', 'b'] [-] ['d', 'e', 'f'] ['g']);
905
906        // From an `ObservableVector` point of view, nothing changes.
907        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[]);
908
909        // Remove a non-empty chunk. This is not possible with the public
910        // `LinkedChunk` API yet, but let's try.
911        let d_e_and_f = linked_chunk.item_position(|item| *item == 'f').unwrap().chunk_identifier();
912        let updates = linked_chunk.updates().unwrap();
913        updates.push(Update::RemoveChunk(d_e_and_f));
914        // Note that `linked_chunk` is getting out of sync with `AsVector`
915        // but it's just a test. Better, it's the end of the test.
916
917        // From an `ObservableVector` point of view, it would look like:
918        //
919        // 0   1   2   3
920        // +---+---+---+
921        // | a | b | g |
922        // +---+---+---+
923        //         ^
924        //         |
925        //         `d`, `e` and `f` have been removed
926        apply_and_assert_eq(
927            &mut accumulator,
928            as_vector.take(),
929            &[
930                VectorDiff::Remove { index: 2 },
931                VectorDiff::Remove { index: 2 },
932                VectorDiff::Remove { index: 2 },
933            ],
934        );
935    }
936
937    #[cfg(not(target_arch = "wasm32"))]
938    mod proptests {
939        use proptest::prelude::*;
940
941        use super::*;
942
943        #[derive(Debug, Clone)]
944        enum AsVectorOperation {
945            PushItems { items: Vec<char> },
946            PushGap,
947            ReplaceLastGap { items: Vec<char> },
948            RemoveItem { item: char },
949        }
950
951        fn as_vector_operation_strategy() -> impl Strategy<Value = AsVectorOperation> {
952            prop_oneof![
953                3 => prop::collection::vec(prop::char::ranges(vec!['a'..='z', 'A'..='Z'].into()), 0..=25)
954                    .prop_map(|items| AsVectorOperation::PushItems { items }),
955
956                2 => Just(AsVectorOperation::PushGap),
957
958                1 => prop::collection::vec(prop::char::ranges(vec!['a'..='z', 'A'..='Z'].into()), 0..=25)
959                    .prop_map(|items| AsVectorOperation::ReplaceLastGap { items }),
960
961                1 => prop::char::ranges(vec!['a'..='z', 'A'..='Z'].into())
962                    .prop_map(|item| AsVectorOperation::RemoveItem { item }),
963            ]
964        }
965
966        proptest! {
967            #[test]
968            fn test_as_vector_is_correct(
969                operations in prop::collection::vec(as_vector_operation_strategy(), 50..=200)
970            ) {
971                let mut linked_chunk = LinkedChunk::<10, char, ()>::new_with_update_history();
972                let mut as_vector = linked_chunk.as_vector().unwrap();
973
974                for operation in operations {
975                    match operation {
976                        AsVectorOperation::PushItems { items } => {
977                            linked_chunk.push_items_back(items);
978                        }
979
980                        AsVectorOperation::PushGap => {
981                            linked_chunk.push_gap_back(());
982                        }
983
984                        AsVectorOperation::ReplaceLastGap { items } => {
985                            let Some(gap_identifier) = linked_chunk
986                                .rchunks()
987                                .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
988                            else {
989                                continue;
990                            };
991
992                            linked_chunk.replace_gap_at(items, gap_identifier).expect("Failed to replace a gap");
993                        }
994
995                        AsVectorOperation::RemoveItem { item: expected_item } => {
996                            let Some(position) = linked_chunk
997                                .items().find_map(|(position, item)| (*item == expected_item).then_some(position))
998                            else {
999                                continue;
1000                            };
1001
1002                            linked_chunk.remove_item_at(position).expect("Failed to remove an item");
1003                        }
1004                    }
1005                }
1006
1007                let mut vector_from_diffs = Vec::new();
1008
1009                // Read all updates as `VectorDiff` and rebuild a `Vec<char>`.
1010                for diff in as_vector.take() {
1011                    match diff {
1012                        VectorDiff::Insert { index, value } => vector_from_diffs.insert(index, value),
1013                        VectorDiff::Append { values } => {
1014                            let mut values = values.iter().copied().collect();
1015
1016                            vector_from_diffs.append(&mut values);
1017                        }
1018                        VectorDiff::Remove { index } => {
1019                            vector_from_diffs.remove(index);
1020                        }
1021                        _ => unreachable!(),
1022                    }
1023                }
1024
1025                // Iterate over all chunks and collect items as `Vec<char>`.
1026                let vector_from_chunks = linked_chunk.items().map(|(_, item)| *item).collect::<Vec<_>>();
1027
1028                // Compare both `Vec`s.
1029                assert_eq!(vector_from_diffs, vector_from_chunks);
1030            }
1031        }
1032    }
1033}