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                            debug_assert!(
271                                matches!(self.chunks.back(), Some((p, _)) if p == previous),
272                                "Inserting new chunk at the end: The previous chunk is invalid"
273                            );
274
275                            self.chunks.push_back((*new, 0));
276                        }
277
278                        // New chunk at the beginning.
279                        (None, Some(next)) => {
280                            debug_assert!(
281                                matches!(self.chunks.front(), Some((n, _)) if n == next),
282                                "Inserting new chunk at the end: The previous chunk is invalid"
283                            );
284
285                            self.chunks.push_front((*new, 0));
286                        }
287
288                        // New chunk is inserted between 2 chunks.
289                        (Some(_previous), Some(next)) => {
290                            let next_chunk_index = self
291                                .chunks
292                                .iter()
293                                .position(|(chunk_identifier, _)| chunk_identifier == next)
294                                // SAFETY: Assuming `LinkedChunk` and `ObservableUpdates` are not
295                                // buggy, and assuming `Self::chunks` is correctly initialized, it
296                                // is not possible to insert a chunk between two chunks where one
297                                // does not exist. If this predicate fails, it means `LinkedChunk`
298                                // or `ObservableUpdates` contain a bug.
299                                .expect("Inserting new chunk: The chunk is not found");
300
301                            // No need to check `previous`. It's possible that the linked chunk is
302                            // lazily loaded, chunk by chunk. The `next` is always reliable, but the
303                            // `previous` might not exist in-memory yet.
304
305                            self.chunks.insert(next_chunk_index, (*new, 0));
306                        }
307
308                        // First chunk!
309                        (None, None) if self.chunks.is_empty() => {
310                            self.chunks.push_back((*new, 0));
311                        }
312
313                        // Impossible state.
314                        (None, None) => {
315                            unreachable!(
316                                "Inserting new chunk with no previous nor next chunk identifiers \
317                                is impossible"
318                            );
319                        }
320                    }
321                }
322
323                Update::RemoveChunk(chunk_identifier) => {
324                    let (offset, (chunk_index, _)) =
325                        self.map_to_offset(&Position(*chunk_identifier, 0));
326
327                    let (_, number_of_items) = self
328                        .chunks
329                        .remove(chunk_index)
330                        .expect("Removing an index out of the bounds");
331
332                    // Removing at the same index because each `Remove` shifts items to the left.
333                    diffs.extend(repeat_n(VectorDiff::Remove { index: offset }, number_of_items));
334                }
335
336                Update::PushItems { at: position, items } => {
337                    let number_of_chunks = self.chunks.len();
338                    let (offset, (chunk_index, chunk_length)) = self.map_to_offset(position);
339
340                    let is_pushing_back =
341                        chunk_index + 1 == number_of_chunks && position.index() >= *chunk_length;
342
343                    // Add the number of items to the chunk in `self.chunks`.
344                    *chunk_length += items.len();
345
346                    // See `reattaching` to learn more.
347                    if reattaching {
348                        continue;
349                    }
350
351                    // Optimisation: we can emit a `VectorDiff::Append` in this particular case.
352                    if is_pushing_back && detaching.not() {
353                        diffs.push(VectorDiff::Append { values: items.into() });
354                    }
355                    // No optimisation: let's emit `VectorDiff::Insert`.
356                    else {
357                        diffs.extend(items.iter().enumerate().map(|(nth, item)| {
358                            VectorDiff::Insert { index: offset + nth, value: item.clone() }
359                        }));
360                    }
361                }
362
363                Update::ReplaceItem { at: position, item } => {
364                    let (offset, (_chunk_index, _chunk_length)) = self.map_to_offset(position);
365
366                    // The chunk length doesn't change.
367
368                    diffs.push(VectorDiff::Set { index: offset, value: item.clone() });
369                }
370
371                Update::RemoveItem { at: position } => {
372                    let (offset, (_chunk_index, chunk_length)) = self.map_to_offset(position);
373
374                    // Remove one item to the chunk in `self.chunks`.
375                    *chunk_length -= 1;
376
377                    // See `reattaching` to learn more.
378                    if reattaching {
379                        continue;
380                    }
381
382                    // Let's emit a `VectorDiff::Remove`.
383                    diffs.push(VectorDiff::Remove { index: offset });
384                }
385
386                Update::DetachLastItems { at: position } => {
387                    let expected_chunk_identifier = position.chunk_identifier();
388                    let new_length = position.index();
389
390                    let chunk_length = self
391                        .chunks
392                        .iter_mut()
393                        .find_map(|(chunk_identifier, chunk_length)| {
394                            (*chunk_identifier == expected_chunk_identifier).then_some(chunk_length)
395                        })
396                        // SAFETY: Assuming `LinkedChunk` and `ObservableUpdates` are not buggy, and
397                        // assuming `Self::chunks` is correctly initialized, it is not possible to
398                        // detach items from a chunk that does not exist. If this predicate fails,
399                        // it means `LinkedChunk` or `ObservableUpdates` contain a bug.
400                        .expect("Detach last items: The chunk is not found");
401
402                    *chunk_length = new_length;
403
404                    // Entering the _detaching_ mode.
405                    detaching = true;
406                }
407
408                Update::StartReattachItems => {
409                    // Entering the _reattaching_ mode.
410                    reattaching = true;
411                }
412
413                Update::EndReattachItems => {
414                    // Exiting the _reattaching_ mode.
415                    reattaching = false;
416
417                    // Exiting the _detaching_ mode.
418                    detaching = false;
419                }
420
421                Update::Clear => {
422                    // Clean `self.chunks`.
423                    self.chunks.clear();
424
425                    // Let's straightforwardly emit a `VectorDiff::Clear`.
426                    diffs.push(VectorDiff::Clear);
427                }
428            }
429        }
430
431        diffs
432    }
433
434    fn map_to_offset(&mut self, position: &Position) -> (usize, (usize, &mut usize)) {
435        let expected_chunk_identifier = position.chunk_identifier();
436
437        let (offset, (chunk_index, chunk_length)) = {
438            let control_flow = self.chunks.iter_mut().enumerate().try_fold(
439                position.index(),
440                |offset, (chunk_index, (chunk_identifier, chunk_length))| {
441                    if chunk_identifier == &expected_chunk_identifier {
442                        ControlFlow::Break((offset, (chunk_index, chunk_length)))
443                    } else {
444                        ControlFlow::Continue(offset + *chunk_length)
445                    }
446                },
447            );
448
449            match control_flow {
450                // Chunk has been found, and all values have been calculated as
451                // expected.
452                ControlFlow::Break(values) => values,
453
454                // Chunk has not been found.
455                ControlFlow::Continue(..) => {
456                    // SAFETY: Assuming `LinkedChunk` and `ObservableUpdates` are not buggy, and
457                    // assuming `Self::chunks` is correctly initialized, it is not possible to work
458                    // on a chunk that does not exist. If this predicate fails, it means
459                    // `LinkedChunk` or `ObservableUpdates` contain a bug.
460                    panic!("The chunk is not found");
461                }
462            }
463        };
464
465        (offset, (chunk_index, chunk_length))
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use std::fmt::Debug;
472
473    use assert_matches::assert_matches;
474    use imbl::{vector, Vector};
475
476    use super::{
477        super::{Chunk, ChunkIdentifierGenerator, EmptyChunk, LinkedChunk, Update},
478        VectorDiff,
479    };
480
481    fn apply_and_assert_eq<Item>(
482        accumulator: &mut Vector<Item>,
483        diffs: Vec<VectorDiff<Item>>,
484        expected_diffs: &[VectorDiff<Item>],
485    ) where
486        Item: PartialEq + Clone + Debug,
487    {
488        assert_eq!(diffs, expected_diffs);
489
490        for diff in diffs {
491            diff.apply(accumulator);
492        }
493    }
494
495    #[test]
496    fn test_as_vector() {
497        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
498        let mut as_vector = linked_chunk.as_vector().unwrap();
499
500        let mut accumulator = Vector::new();
501
502        assert!(as_vector.take().is_empty());
503
504        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
505        #[rustfmt::skip]
506        assert_items_eq!(linked_chunk, ['a', 'b', 'c'] ['d']);
507
508        // From an `ObservableVector` point of view, it would look like:
509        //
510        // 0   1   2   3   4
511        // +---+---+---+---+
512        // | a | b | c | d |
513        // +---+---+---+---+
514        // ^^^^^^^^^^^^^^^^
515        // |
516        // new
517        apply_and_assert_eq(
518            &mut accumulator,
519            as_vector.take(),
520            &[
521                VectorDiff::Append { values: vector!['a', 'b', 'c'] },
522                VectorDiff::Append { values: vector!['d'] },
523            ],
524        );
525
526        linked_chunk
527            .insert_items_at(
528                ['w', 'x', 'y', 'z'],
529                linked_chunk.item_position(|item| *item == 'b').unwrap(),
530            )
531            .unwrap();
532        assert_items_eq!(linked_chunk, ['a', 'w', 'x'] ['y', 'z', 'b'] ['c'] ['d']);
533
534        // From an `ObservableVector` point of view, it would look like:
535        //
536        // 0   1   2   3   4   5   6   7   8
537        // +---+---+---+---+---+---+---+---+
538        // | a | w | x | y | z | b | c | d |
539        // +---+---+---+---+---+---+---+---+
540        //     ^^^^^^^^^^^^^^^^
541        //     |
542        //     new
543        apply_and_assert_eq(
544            &mut accumulator,
545            as_vector.take(),
546            &[
547                VectorDiff::Insert { index: 1, value: 'w' },
548                VectorDiff::Insert { index: 2, value: 'x' },
549                VectorDiff::Insert { index: 3, value: 'y' },
550                VectorDiff::Insert { index: 4, value: 'z' },
551            ],
552        );
553
554        linked_chunk.push_gap_back(());
555        linked_chunk.push_items_back(['e', 'f', 'g', 'h']);
556        assert_items_eq!(
557            linked_chunk,
558            ['a', 'w', 'x'] ['y', 'z', 'b'] ['c'] ['d'] [-] ['e', 'f', 'g'] ['h']
559        );
560
561        // From an `ObservableVector` point of view, it would look like:
562        //
563        // 0   1   2   3   4   5   6   7   8   9   10  11  12
564        // +---+---+---+---+---+---+---+---+---+---+---+---+
565        // | a | w | x | y | z | b | c | d | e | f | g | h |
566        // +---+---+---+---+---+---+---+---+---+---+---+---+
567        //                                 ^^^^^^^^^^^^^^^^
568        //                                 |
569        //                                 new
570        apply_and_assert_eq(
571            &mut accumulator,
572            as_vector.take(),
573            &[
574                VectorDiff::Append { values: vector!['e', 'f', 'g'] },
575                VectorDiff::Append { values: vector!['h'] },
576            ],
577        );
578
579        linked_chunk
580            .replace_gap_at(
581                ['i', 'j', 'k', 'l'],
582                linked_chunk.chunk_identifier(|chunk| chunk.is_gap()).unwrap(),
583            )
584            .unwrap();
585        assert_items_eq!(
586            linked_chunk,
587            ['a', 'w', 'x'] ['y', 'z', 'b'] ['c'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
588        );
589
590        // From an `ObservableVector` point of view, it would look like:
591        //
592        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
593        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
594        // | a | w | x | y | z | b | c | d | i | j | k | l | e | f | g | h |
595        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
596        //                                 ^^^^^^^^^^^^^^^^
597        //                                 |
598        //                                 new
599        apply_and_assert_eq(
600            &mut accumulator,
601            as_vector.take(),
602            &[
603                VectorDiff::Insert { index: 8, value: 'i' },
604                VectorDiff::Insert { index: 9, value: 'j' },
605                VectorDiff::Insert { index: 10, value: 'k' },
606                VectorDiff::Insert { index: 11, value: 'l' },
607            ],
608        );
609
610        linked_chunk
611            .insert_items_at(['m'], linked_chunk.item_position(|item| *item == 'a').unwrap())
612            .unwrap();
613        assert_items_eq!(
614            linked_chunk,
615            ['m', 'a', 'w'] ['x'] ['y', 'z', 'b'] ['c'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
616        );
617
618        // From an `ObservableVector` point of view, it would look like:
619        //
620        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17
621        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
622        // | m | a | w | x | y | z | b | c | d | i | j | k | l | e | f | g | h |
623        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
624        // ^^^^
625        // |
626        // new
627        apply_and_assert_eq(
628            &mut accumulator,
629            as_vector.take(),
630            &[VectorDiff::Insert { index: 0, value: 'm' }],
631        );
632
633        let removed_item = linked_chunk
634            .remove_item_at(
635                linked_chunk.item_position(|item| *item == 'c').unwrap(),
636                EmptyChunk::Remove,
637            )
638            .unwrap();
639        assert_eq!(removed_item, 'c');
640        assert_items_eq!(
641            linked_chunk,
642            ['m', 'a', 'w'] ['x'] ['y', 'z', 'b'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
643        );
644
645        // From an `ObservableVector` point of view, it would look like:
646        //
647        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
648        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
649        // | m | a | w | x | y | z | b | d | i | j | k | l | e | f | g | h |
650        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
651        //                             ^
652        //                             |
653        //                             `c` has been removed
654        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Remove { index: 7 }]);
655
656        let removed_item = linked_chunk
657            .remove_item_at(
658                linked_chunk.item_position(|item| *item == 'z').unwrap(),
659                EmptyChunk::Remove,
660            )
661            .unwrap();
662        assert_eq!(removed_item, 'z');
663        assert_items_eq!(
664            linked_chunk,
665            ['m', 'a', 'w'] ['x'] ['y', 'b'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['h']
666        );
667
668        // From an `ObservableVector` point of view, it would look like:
669        //
670        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
671        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
672        // | m | a | w | x | y | b | d | i | j | k | l | e | f | g | h |
673        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
674        //                     ^
675        //                     |
676        //                     `z` has been removed
677        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Remove { index: 5 }]);
678
679        linked_chunk
680            .insert_items_at(['z'], linked_chunk.item_position(|item| *item == 'h').unwrap())
681            .unwrap();
682
683        assert_items_eq!(
684            linked_chunk,
685            ['m', 'a', 'w'] ['x'] ['y', 'b'] ['d'] ['i', 'j', 'k'] ['l'] ['e', 'f', 'g'] ['z', 'h']
686        );
687
688        // From an `ObservableVector` point of view, it would look like:
689        //
690        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
691        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
692        // | m | a | w | x | y | b | d | i | j | k | l | e | f | g | z | h |
693        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
694        //                                                         ^^^^
695        //                                                         |
696        //                                                         new!
697        apply_and_assert_eq(
698            &mut accumulator,
699            as_vector.take(),
700            &[VectorDiff::Insert { index: 14, value: 'z' }],
701        );
702
703        // Ensure the “reconstitued” vector is the one expected.
704        assert_eq!(
705            accumulator,
706            vector!['m', 'a', 'w', 'x', 'y', 'b', 'd', 'i', 'j', 'k', 'l', 'e', 'f', 'g', 'z', 'h']
707        );
708
709        // Replace element 8 by an uppercase J.
710        linked_chunk
711            .replace_item_at(linked_chunk.item_position(|item| *item == 'j').unwrap(), 'J')
712            .unwrap();
713
714        assert_items_eq!(
715            linked_chunk,
716            ['m', 'a', 'w'] ['x'] ['y', 'b'] ['d'] ['i', 'J', 'k'] ['l'] ['e', 'f', 'g'] ['z', 'h']
717        );
718
719        // From an `ObservableVector` point of view, it would look like:
720        //
721        // 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16
722        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
723        // | m | a | w | x | y | b | d | i | J | k | l | e | f | g | z | h |
724        // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
725        //                                 ^^^^
726        //                                 |
727        //                                 new!
728        apply_and_assert_eq(
729            &mut accumulator,
730            as_vector.take(),
731            &[VectorDiff::Set { index: 8, value: 'J' }],
732        );
733
734        // Let's try to clear the linked chunk now.
735        linked_chunk.clear();
736
737        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Clear]);
738        assert!(accumulator.is_empty());
739
740        drop(linked_chunk);
741        assert!(as_vector.take().is_empty());
742    }
743
744    #[test]
745    fn test_as_vector_with_update_clear() {
746        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
747        let mut as_vector = linked_chunk.as_vector().unwrap();
748
749        {
750            // 1 initial chunk in the `UpdateToVectorDiff` mapper.
751            let chunks = &as_vector.mapper.chunks;
752            assert_eq!(chunks.len(), 1);
753            assert_eq!(chunks[0].0, ChunkIdentifierGenerator::FIRST_IDENTIFIER);
754            assert_eq!(chunks[0].1, 0);
755
756            assert!(as_vector.take().is_empty());
757        }
758
759        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
760
761        {
762            let diffs = as_vector.take();
763            assert_eq!(diffs.len(), 2);
764            assert_matches!(&diffs[0], VectorDiff::Append { .. });
765            assert_matches!(&diffs[1], VectorDiff::Append { .. });
766
767            // 2 chunks in the `UpdateToVectorDiff` mapper.
768            assert_eq!(as_vector.mapper.chunks.len(), 2);
769        }
770
771        linked_chunk.clear();
772
773        {
774            let diffs = as_vector.take();
775            assert_eq!(diffs.len(), 1);
776            assert_matches!(&diffs[0], VectorDiff::Clear);
777
778            // 1 chunk in the `UpdateToVectorDiff` mapper.
779            let chunks = &as_vector.mapper.chunks;
780            assert_eq!(chunks.len(), 1);
781            assert_eq!(chunks[0].0, ChunkIdentifierGenerator::FIRST_IDENTIFIER);
782            assert_eq!(chunks[0].1, 0);
783        }
784
785        // And we can push again.
786        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
787
788        {
789            let diffs = as_vector.take();
790            assert_eq!(diffs.len(), 2);
791            assert_matches!(&diffs[0], VectorDiff::Append { .. });
792            assert_matches!(&diffs[1], VectorDiff::Append { .. });
793        }
794    }
795
796    #[test]
797    fn test_updates_are_drained_when_constructing_as_vector() {
798        let mut linked_chunk = LinkedChunk::<10, char, ()>::new_with_update_history();
799
800        linked_chunk.push_items_back(['a']);
801
802        let mut as_vector = linked_chunk.as_vector().unwrap();
803        let diffs = as_vector.take();
804
805        // `diffs` are empty because `AsVector` is built _after_ `LinkedChunk`
806        // has been updated.
807        assert!(diffs.is_empty());
808
809        linked_chunk.push_items_back(['b']);
810
811        let diffs = as_vector.take();
812
813        // `diffs` is not empty because new updates are coming.
814        assert_eq!(diffs.len(), 1);
815    }
816
817    #[test]
818    fn test_as_vector_with_initial_content() {
819        // Fill the linked chunk with some initial items.
820        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
821        linked_chunk.push_items_back(['a', 'b', 'c', 'd']);
822
823        #[rustfmt::skip]
824        assert_items_eq!(linked_chunk, ['a', 'b', 'c'] ['d']);
825
826        // Empty updates first.
827        let _ = linked_chunk.updates().unwrap().take();
828
829        // Start observing future updates.
830        let mut as_vector = linked_chunk.as_vector().unwrap();
831
832        assert!(as_vector.take().is_empty());
833
834        // It's important to cause a change that will create new chunks, like pushing
835        // enough items.
836        linked_chunk.push_items_back(['e', 'f', 'g']);
837        #[rustfmt::skip]
838        assert_items_eq!(linked_chunk, ['a', 'b', 'c'] ['d', 'e', 'f'] ['g']);
839
840        // And the vector diffs can be computed without crashing.
841        let diffs = as_vector.take();
842        assert_eq!(diffs.len(), 2);
843        assert_matches!(&diffs[0], VectorDiff::Append { values } => {
844            assert_eq!(*values, ['e', 'f'].into());
845        });
846        assert_matches!(&diffs[1], VectorDiff::Append { values } => {
847            assert_eq!(*values, ['g'].into());
848        });
849    }
850
851    #[test]
852    fn test_as_vector_remove_chunk() {
853        let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
854        let mut as_vector = linked_chunk.as_vector().unwrap();
855
856        let mut accumulator = Vector::new();
857
858        assert!(as_vector.take().is_empty());
859
860        linked_chunk.push_items_back(['a', 'b']);
861        linked_chunk.push_gap_back(());
862        linked_chunk.push_items_back(['c']);
863        linked_chunk.push_gap_back(());
864        linked_chunk.push_items_back(['d', 'e', 'f', 'g']);
865
866        assert_items_eq!(linked_chunk, ['a', 'b'] [-] ['c'] [-] ['d', 'e', 'f'] ['g']);
867
868        // From an `ObservableVector` point of view, it would look like:
869        //
870        // 0   1   2   3   4   5   6   7
871        // +---+---+---+---+---+---+---+
872        // | a | b | c | d | e | f | g |
873        // +---+---+---+---+---+---+---+
874        // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
875        // |
876        // new
877        apply_and_assert_eq(
878            &mut accumulator,
879            as_vector.take(),
880            &[
881                VectorDiff::Append { values: vector!['a', 'b'] },
882                VectorDiff::Append { values: vector!['c'] },
883                VectorDiff::Append { values: vector!['d', 'e', 'f'] },
884                VectorDiff::Append { values: vector!['g'] },
885            ],
886        );
887
888        // Empty a chunk, and remove it once it is empty.
889        linked_chunk
890            .remove_item_at(
891                linked_chunk.item_position(|item| *item == 'c').unwrap(),
892                EmptyChunk::Remove,
893            )
894            .unwrap();
895
896        assert_items_eq!(linked_chunk, ['a', 'b'] [-] [-] ['d', 'e', 'f'] ['g']);
897
898        // From an `ObservableVector` point of view, it would look like:
899        //
900        // 0   1   2   3   4   5   6
901        // +---+---+---+---+---+---+
902        // | a | b | d | e | f | g |
903        // +---+---+---+---+---+---+
904        //         ^
905        //         |
906        //         `c` has been removed
907        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[VectorDiff::Remove { index: 2 }]);
908
909        // Remove a gap.
910        linked_chunk.remove_gap_at(linked_chunk.chunk_identifier(Chunk::is_gap).unwrap()).unwrap();
911
912        assert_items_eq!(linked_chunk, ['a', 'b'] [-] ['d', 'e', 'f'] ['g']);
913
914        // From an `ObservableVector` point of view, nothing changes.
915        apply_and_assert_eq(&mut accumulator, as_vector.take(), &[]);
916
917        // Remove a non-empty chunk. This is not possible with the public
918        // `LinkedChunk` API yet, but let's try.
919        let d_e_and_f = linked_chunk.item_position(|item| *item == 'f').unwrap().chunk_identifier();
920        let updates = linked_chunk.updates().unwrap();
921        updates.push(Update::RemoveChunk(d_e_and_f));
922        // Note that `linked_chunk` is getting out of sync with `AsVector`
923        // but it's just a test. Better, it's the end of the test.
924
925        // From an `ObservableVector` point of view, it would look like:
926        //
927        // 0   1   2   3
928        // +---+---+---+
929        // | a | b | g |
930        // +---+---+---+
931        //         ^
932        //         |
933        //         `d`, `e` and `f` have been removed
934        apply_and_assert_eq(
935            &mut accumulator,
936            as_vector.take(),
937            &[
938                VectorDiff::Remove { index: 2 },
939                VectorDiff::Remove { index: 2 },
940                VectorDiff::Remove { index: 2 },
941            ],
942        );
943    }
944
945    #[cfg(not(target_arch = "wasm32"))]
946    mod proptests {
947        use proptest::prelude::*;
948
949        use super::*;
950
951        #[derive(Debug, Clone)]
952        enum AsVectorOperation {
953            PushItems { items: Vec<char> },
954            PushGap,
955            ReplaceLastGap { items: Vec<char> },
956            RemoveItem { item: char },
957        }
958
959        fn as_vector_operation_strategy() -> impl Strategy<Value = AsVectorOperation> {
960            prop_oneof![
961                3 => prop::collection::vec(prop::char::ranges(vec!['a'..='z', 'A'..='Z'].into()), 0..=25)
962                    .prop_map(|items| AsVectorOperation::PushItems { items }),
963
964                2 => Just(AsVectorOperation::PushGap),
965
966                1 => prop::collection::vec(prop::char::ranges(vec!['a'..='z', 'A'..='Z'].into()), 0..=25)
967                    .prop_map(|items| AsVectorOperation::ReplaceLastGap { items }),
968
969                1 => prop::char::ranges(vec!['a'..='z', 'A'..='Z'].into())
970                    .prop_map(|item| AsVectorOperation::RemoveItem { item }),
971            ]
972        }
973
974        proptest! {
975            #[test]
976            fn test_as_vector_is_correct(
977                operations in prop::collection::vec(as_vector_operation_strategy(), 50..=200)
978            ) {
979                let mut linked_chunk = LinkedChunk::<10, char, ()>::new_with_update_history();
980                let mut as_vector = linked_chunk.as_vector().unwrap();
981
982                for operation in operations {
983                    match operation {
984                        AsVectorOperation::PushItems { items } => {
985                            linked_chunk.push_items_back(items);
986                        }
987
988                        AsVectorOperation::PushGap => {
989                            linked_chunk.push_gap_back(());
990                        }
991
992                        AsVectorOperation::ReplaceLastGap { items } => {
993                            let Some(gap_identifier) = linked_chunk
994                                .rchunks()
995                                .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
996                            else {
997                                continue;
998                            };
999
1000                            linked_chunk.replace_gap_at(items, gap_identifier).expect("Failed to replace a gap");
1001                        }
1002
1003                        AsVectorOperation::RemoveItem { item: expected_item } => {
1004                            let Some(position) = linked_chunk
1005                                .items().find_map(|(position, item)| (*item == expected_item).then_some(position))
1006                            else {
1007                                continue;
1008                            };
1009
1010                            linked_chunk.remove_item_at(position, EmptyChunk::Remove).expect("Failed to remove an item");
1011                        }
1012                    }
1013                }
1014
1015                let mut vector_from_diffs = Vec::new();
1016
1017                // Read all updates as `VectorDiff` and rebuild a `Vec<char>`.
1018                for diff in as_vector.take() {
1019                    match diff {
1020                        VectorDiff::Insert { index, value } => vector_from_diffs.insert(index, value),
1021                        VectorDiff::Append { values } => {
1022                            let mut values = values.iter().copied().collect();
1023
1024                            vector_from_diffs.append(&mut values);
1025                        }
1026                        VectorDiff::Remove { index } => {
1027                            vector_from_diffs.remove(index);
1028                        }
1029                        _ => unreachable!(),
1030                    }
1031                }
1032
1033                // Iterate over all chunks and collect items as `Vec<char>`.
1034                let vector_from_chunks = linked_chunk.items().map(|(_, item)| *item).collect::<Vec<_>>();
1035
1036                // Compare both `Vec`s.
1037                assert_eq!(vector_from_diffs, vector_from_chunks);
1038            }
1039        }
1040    }
1041}