Skip to main content

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