1use as_variant::as_variant;
16use eyeball_im::VectorDiff;
17pub use matrix_sdk_base::event_cache::{Event, Gap};
18use matrix_sdk_base::{
19 event_cache::store::DEFAULT_CHUNK_CAPACITY,
20 linked_chunk::{
21 ChunkContent, ChunkIdentifierGenerator, ChunkMetadata, OrderTracker, RawChunk,
22 lazy_loader::{self, LazyLoaderError},
23 },
24};
25use matrix_sdk_common::linked_chunk::{
26 AsVector, Chunk, ChunkIdentifier, Error, Iter, IterBackward, LinkedChunk, ObservableUpdates,
27 Position,
28};
29use tracing::trace;
30
31#[derive(Debug)]
33pub(in crate::event_cache) struct EventLinkedChunk {
34 chunks: LinkedChunk<DEFAULT_CHUNK_CAPACITY, Event, Gap>,
36
37 chunks_updates_as_vectordiffs: AsVector<Event, Gap>,
41
42 pub order_tracker: OrderTracker<Event, Gap>,
44}
45
46impl Default for EventLinkedChunk {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl EventLinkedChunk {
53 pub fn new() -> Self {
55 Self::with_initial_linked_chunk(None, None)
56 }
57
58 pub fn with_initial_linked_chunk(
62 linked_chunk: Option<LinkedChunk<DEFAULT_CHUNK_CAPACITY, Event, Gap>>,
63 full_linked_chunk_metadata: Option<Vec<ChunkMetadata>>,
64 ) -> Self {
65 let mut linked_chunk = linked_chunk.unwrap_or_else(LinkedChunk::new_with_update_history);
66
67 let chunks_updates_as_vectordiffs = linked_chunk
68 .as_vector()
69 .expect("`LinkedChunk` must have been built with `new_with_update_history`");
70
71 let order_tracker = linked_chunk
72 .order_tracker(full_linked_chunk_metadata)
73 .expect("`LinkedChunk` must have been built with `new_with_update_history`");
74
75 Self { chunks: linked_chunk, chunks_updates_as_vectordiffs, order_tracker }
76 }
77
78 pub fn reset(&mut self) {
83 self.chunks.clear();
84 }
85
86 #[cfg(test)]
90 pub(in crate::event_cache) fn push_events<I>(&mut self, events: I)
91 where
92 I: IntoIterator<Item = Event>,
93 I::IntoIter: ExactSizeIterator,
94 {
95 self.chunks.push_items_back(events);
96 }
97
98 fn replace_gap_at(
106 &mut self,
107 gap_identifier: ChunkIdentifier,
108 events: Vec<Event>,
109 ) -> Result<Option<Position>, Error> {
110 let has_only_one_chunk = {
118 let mut it = self.chunks.chunks();
119
120 let _ =
122 it.next().ok_or(Error::InvalidChunkIdentifier { identifier: gap_identifier })?;
123
124 it.next().is_none()
126 };
127
128 let next_pos = if events.is_empty() && !has_only_one_chunk {
129 self.chunks.remove_empty_chunk_at(gap_identifier)?
132 } else {
133 Some(self.chunks.replace_gap_at(events, gap_identifier)?.first_position())
135 };
136
137 Ok(next_pos)
138 }
139
140 pub fn remove_events_by_position(&mut self, mut positions: Vec<Position>) -> Result<(), Error> {
144 sort_positions_descending(&mut positions);
145
146 for position in positions {
147 self.chunks.remove_item_at(position)?;
148 }
149
150 Ok(())
151 }
152
153 pub fn replace_event_at(&mut self, position: Position, event: Event) -> Result<(), Error> {
158 self.chunks.replace_item_at(position, event)
159 }
160
161 pub fn chunk_identifier<'a, P>(&'a self, predicate: P) -> Option<ChunkIdentifier>
163 where
164 P: FnMut(&'a Chunk<DEFAULT_CHUNK_CAPACITY, Event, Gap>) -> bool,
165 {
166 self.chunks.chunk_identifier(predicate)
167 }
168
169 pub fn chunks(&self) -> Iter<'_, DEFAULT_CHUNK_CAPACITY, Event, Gap> {
173 self.chunks.chunks()
174 }
175
176 pub fn rchunks(&self) -> IterBackward<'_, DEFAULT_CHUNK_CAPACITY, Event, Gap> {
180 self.chunks.rchunks()
181 }
182
183 pub fn revents(&self) -> impl Iterator<Item = (Position, &Event)> {
187 self.chunks.ritems()
188 }
189
190 pub fn events(&self) -> impl Iterator<Item = (Position, &Event)> {
194 self.chunks.items()
195 }
196
197 pub fn event_order(&self, event_pos: Position) -> Option<usize> {
201 self.order_tracker.ordering(event_pos)
202 }
203
204 #[cfg(any(test, debug_assertions))]
205 #[allow(dead_code)] fn assert_event_ordering(&self) {
207 let mut iter = self.chunks.items().enumerate();
208 let Some((i, (first_event_pos, _))) = iter.next() else {
209 return;
210 };
211
212 assert_eq!(i, 0);
214
215 let offset =
218 self.event_order(first_event_pos).expect("first event's ordering must be known");
219
220 for (i, (next_pos, _)) in iter {
221 let next_index =
222 self.event_order(next_pos).expect("next event's ordering must be known");
223 assert_eq!(offset + i, next_index, "event ordering must be continuous");
224 }
225 }
226
227 pub fn updates_as_vector_diffs(&mut self) -> Vec<VectorDiff<Event>> {
233 let updates = self.chunks_updates_as_vectordiffs.take();
234
235 self.order_tracker.flush_updates(false);
236
237 updates
238 }
239
240 pub(super) fn store_updates(&mut self) -> &mut ObservableUpdates<Event, Gap> {
248 self.chunks.updates().expect("this is always built with an update history in the ctor")
249 }
250
251 pub fn debug_string(&self) -> Vec<String> {
254 let mut result = Vec::new();
255
256 for chunk in self.chunks.chunks() {
257 let content =
258 chunk_debug_string(chunk.identifier(), chunk.content(), &self.order_tracker);
259 let lazy_previous = if let Some(cid) = chunk.lazy_previous() {
260 format!(" (lazy previous = {})", cid.index())
261 } else {
262 "".to_owned()
263 };
264 let line = format!("chunk #{}{lazy_previous}: {content}", chunk.identifier().index());
265
266 result.push(line);
267 }
268
269 result
270 }
271
272 pub fn rgap(&self) -> Option<Gap> {
277 self.rchunks()
278 .find_map(|chunk| as_variant!(chunk.content(), ChunkContent::Gap(gap) => gap.clone()))
279 }
280
281 pub fn push_live_events(&mut self, new_gap: Option<Gap>, events: &[Event]) {
284 if let Some(new_gap) = new_gap {
285 let prev_chunk_to_remove = self.rchunks().next().and_then(|chunk| {
288 (chunk.is_items() && chunk.num_items() == 0).then_some(chunk.identifier())
289 });
290
291 self.chunks.push_gap_back(new_gap);
292
293 if let Some(prev_chunk_to_remove) = prev_chunk_to_remove {
294 self.chunks
295 .remove_empty_chunk_at(prev_chunk_to_remove)
296 .expect("we just checked the chunk is there, and it's an empty item chunk");
297 }
298 }
299
300 self.chunks.push_items_back(events.to_vec());
301 }
302
303 pub fn finish_back_pagination(
319 &mut self,
320 prev_gap_id: Option<ChunkIdentifier>,
321 new_gap: Option<Gap>,
322 events: &[Event],
323 ) -> bool {
324 let first_event_pos = self.events().next().map(|(item_pos, _)| item_pos);
325
326 let insert_new_gap_pos = if let Some(gap_id) = prev_gap_id {
328 trace!("replacing previous gap with the back-paginated events");
330
331 self.replace_gap_at(gap_id, events.to_vec())
335 .expect("gap_identifier is a valid chunk id we read previously")
336 } else if let Some(pos) = first_event_pos {
337 trace!("inserted events before the first known event");
340
341 self.chunks
342 .insert_items_at(pos, events.to_vec())
343 .expect("pos is a valid position we just read above");
344
345 Some(pos)
346 } else {
347 trace!("pushing events received from back-pagination");
349
350 self.chunks.push_items_back(events.to_vec());
351
352 self.events().next().map(|(item_pos, _)| item_pos)
354 };
355
356 let has_new_gap = new_gap.is_some();
362 if let Some(new_gap) = new_gap {
363 if let Some(new_pos) = insert_new_gap_pos {
364 self.chunks
365 .insert_gap_at(new_gap, new_pos)
366 .expect("events_chunk_pos represents a valid chunk position");
367 } else {
368 self.chunks.push_gap_back(new_gap);
369 }
370 }
371
372 let has_gaps = self.chunks().any(|chunk| chunk.is_gap());
378
379 let first_chunk_is_definitive_head =
381 self.chunks().next().map(|chunk| chunk.is_definitive_head());
382
383 let network_reached_start = !has_new_gap;
384 let reached_start =
385 !has_gaps && first_chunk_is_definitive_head.unwrap_or(network_reached_start);
386
387 trace!(
388 ?network_reached_start,
389 ?has_gaps,
390 ?first_chunk_is_definitive_head,
391 ?reached_start,
392 "finished handling network back-pagination"
393 );
394
395 reached_start
396 }
397}
398
399impl EventLinkedChunk {
401 fn inhibit_updates_to_ordering_tracker<F: FnOnce(&mut Self) -> R, R>(&mut self, f: F) -> R {
410 self.order_tracker.flush_updates(false);
412
413 let r = f(self);
415
416 self.order_tracker.flush_updates(true);
419
420 r
421 }
422
423 pub(super) fn replace_with(
430 &mut self,
431 last_chunk: Option<RawChunk<Event, Gap>>,
432 chunk_identifier_generator: ChunkIdentifierGenerator,
433 ) -> Result<(), LazyLoaderError> {
434 self.inhibit_updates_to_ordering_tracker(move |this| {
437 lazy_loader::replace_with(&mut this.chunks, last_chunk, chunk_identifier_generator)
438 })
439 }
440
441 pub(super) fn insert_new_chunk_as_first(
443 &mut self,
444 raw_new_first_chunk: RawChunk<Event, Gap>,
445 ) -> Result<(), LazyLoaderError> {
446 self.inhibit_updates_to_ordering_tracker(move |this| {
449 lazy_loader::insert_new_first_chunk(&mut this.chunks, raw_new_first_chunk)
450 })
451 }
452}
453
454fn chunk_debug_string(
456 chunk_id: ChunkIdentifier,
457 content: &ChunkContent<Event, Gap>,
458 order_tracker: &OrderTracker<Event, Gap>,
459) -> String {
460 match content {
461 ChunkContent::Gap(Gap { prev_token }) => {
462 format!("gap['{prev_token}']")
463 }
464 ChunkContent::Items(vec) => {
465 let items = vec
466 .iter()
467 .enumerate()
468 .map(|(i, event)| {
469 event.event_id().map_or_else(
470 || "<no event id>".to_owned(),
471 |id| {
472 let pos = Position::new(chunk_id, i);
473 let order = format!("#{}: ", order_tracker.ordering(pos).unwrap());
474
475 let event_id = id.as_str().chars().take(1 + 8).collect::<String>();
477
478 format!("{order}{event_id}")
479 },
480 )
481 })
482 .collect::<Vec<_>>()
483 .join(", ");
484
485 format!("events[{items}]")
486 }
487 }
488}
489
490pub(super) fn sort_positions_descending(positions: &mut [Position]) {
498 positions.sort_by(|a, b| {
499 b.chunk_identifier()
500 .cmp(&a.chunk_identifier())
501 .then_with(|| a.index().cmp(&b.index()).reverse())
502 });
503}
504
505#[cfg(test)]
506mod tests {
507 use assert_matches::assert_matches;
508 use assert_matches2::assert_let;
509 use matrix_sdk_test::{ALICE, DEFAULT_TEST_ROOM_ID, event_factory::EventFactory};
510 use ruma::{EventId, OwnedEventId, event_id, user_id};
511
512 use super::*;
513
514 macro_rules! assert_events_eq {
515 ( $events_iterator:expr, [ $( ( $event_id:ident at ( $chunk_identifier:literal, $index:literal ) ) ),* $(,)? ] ) => {
516 {
517 let mut events = $events_iterator;
518
519 $(
520 assert_let!(Some((position, event)) = events.next());
521 assert_eq!(position.chunk_identifier(), $chunk_identifier );
522 assert_eq!(position.index(), $index );
523 assert_eq!(event.event_id().unwrap(), $event_id );
524 )*
525
526 assert!(events.next().is_none(), "No more events are expected");
527 }
528 };
529 }
530
531 fn new_event(event_id: &str) -> (OwnedEventId, Event) {
532 let event_id = EventId::parse(event_id).unwrap();
533 let event = EventFactory::new()
534 .text_msg("")
535 .sender(user_id!("@mnt_io:matrix.org"))
536 .event_id(&event_id)
537 .into_event();
538
539 (event_id, event)
540 }
541
542 #[test]
543 fn test_new_event_linked_chunk_has_zero_events() {
544 let linked_chunk = EventLinkedChunk::new();
545
546 assert_eq!(linked_chunk.events().count(), 0);
547 }
548
549 #[test]
550 fn test_replace_gap_at() {
551 let (event_id_0, event_0) = new_event("$ev0");
552 let (event_id_1, event_1) = new_event("$ev1");
553 let (event_id_2, event_2) = new_event("$ev2");
554
555 let mut linked_chunk = EventLinkedChunk::new();
556
557 linked_chunk.chunks.push_items_back([event_0]);
558 linked_chunk.chunks.push_gap_back(Gap { prev_token: "hello".to_owned() });
559
560 let gap_chunk_id = linked_chunk
561 .chunks()
562 .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
563 .unwrap();
564
565 linked_chunk.replace_gap_at(gap_chunk_id, vec![event_1, event_2]).unwrap();
566
567 assert_events_eq!(
568 linked_chunk.events(),
569 [
570 (event_id_0 at (0, 0)),
571 (event_id_1 at (2, 0)),
572 (event_id_2 at (2, 1)),
573 ]
574 );
575
576 {
577 let mut chunks = linked_chunk.chunks();
578
579 assert_let!(Some(chunk) = chunks.next());
580 assert!(chunk.is_items());
581
582 assert_let!(Some(chunk) = chunks.next());
583 assert!(chunk.is_items());
584
585 assert!(chunks.next().is_none());
586 }
587 }
588
589 #[test]
590 fn test_replace_gap_at_with_no_new_events() {
591 let (_, event_0) = new_event("$ev0");
592 let (_, event_1) = new_event("$ev1");
593 let (_, event_2) = new_event("$ev2");
594
595 let mut linked_chunk = EventLinkedChunk::new();
596
597 linked_chunk.chunks.push_items_back([event_0, event_1]);
598 linked_chunk.chunks.push_gap_back(Gap { prev_token: "middle".to_owned() });
599 linked_chunk.chunks.push_items_back([event_2]);
600 linked_chunk.chunks.push_gap_back(Gap { prev_token: "end".to_owned() });
601
602 let first_gap_id = linked_chunk
604 .chunks()
605 .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
606 .unwrap();
607
608 let pos = linked_chunk.replace_gap_at(first_gap_id, vec![]).unwrap();
610 assert_eq!(pos, Some(Position::new(ChunkIdentifier::new(2), 0)));
611
612 let second_gap_id = linked_chunk
614 .chunks()
615 .find_map(|chunk| chunk.is_gap().then_some(chunk.identifier()))
616 .unwrap();
617
618 let pos = linked_chunk.replace_gap_at(second_gap_id, vec![]).unwrap();
620 assert!(pos.is_none());
621 }
622
623 #[test]
624 fn test_remove_events() {
625 let (event_id_0, event_0) = new_event("$ev0");
626 let (event_id_1, event_1) = new_event("$ev1");
627 let (event_id_2, event_2) = new_event("$ev2");
628 let (event_id_3, event_3) = new_event("$ev3");
629
630 let mut linked_chunk = EventLinkedChunk::new();
632 linked_chunk.chunks.push_items_back([event_0, event_1]);
633 linked_chunk.chunks.push_gap_back(Gap { prev_token: "hello".to_owned() });
634 linked_chunk.chunks.push_items_back([event_2, event_3]);
635
636 assert_events_eq!(
637 linked_chunk.events(),
638 [
639 (event_id_0 at (0, 0)),
640 (event_id_1 at (0, 1)),
641 (event_id_2 at (2, 0)),
642 (event_id_3 at (2, 1)),
643 ]
644 );
645 assert_eq!(linked_chunk.chunks().count(), 3);
646
647 linked_chunk
649 .remove_events_by_position(vec![
650 Position::new(ChunkIdentifier::new(2), 1),
651 Position::new(ChunkIdentifier::new(0), 1),
652 ])
653 .unwrap();
654
655 assert_events_eq!(
656 linked_chunk.events(),
657 [
658 (event_id_0 at (0, 0)),
659 (event_id_2 at (2, 0)),
660 ]
661 );
662
663 linked_chunk
665 .remove_events_by_position(vec![Position::new(ChunkIdentifier::new(2), 0)])
666 .unwrap();
667
668 assert_events_eq!(
669 linked_chunk.events(),
670 [
671 (event_id_0 at (0, 0)),
672 ]
673 );
674 assert_eq!(linked_chunk.chunks().count(), 2);
675 }
676
677 #[test]
678 fn test_remove_events_unknown_event() {
679 let mut linked_chunk = EventLinkedChunk::new();
681
682 assert_events_eq!(linked_chunk.events(), []);
683
684 linked_chunk
687 .remove_events_by_position(vec![Position::new(ChunkIdentifier::new(42), 153)])
688 .unwrap_err();
689
690 assert_events_eq!(linked_chunk.events(), []);
691
692 let mut events = linked_chunk.events();
693 assert!(events.next().is_none());
694 }
695
696 #[test]
697 fn test_reset() {
698 let (event_id_0, event_0) = new_event("$ev0");
699 let (event_id_1, event_1) = new_event("$ev1");
700 let (event_id_2, event_2) = new_event("$ev2");
701 let (event_id_3, event_3) = new_event("$ev3");
702
703 let mut linked_chunk = EventLinkedChunk::new();
705 linked_chunk.chunks.push_items_back([event_0, event_1]);
706 linked_chunk.chunks.push_gap_back(Gap { prev_token: "raclette".to_owned() });
707 linked_chunk.chunks.push_items_back([event_2]);
708
709 let diffs = linked_chunk.updates_as_vector_diffs();
711
712 assert_eq!(diffs.len(), 2);
713
714 assert_matches!(
715 &diffs[0],
716 VectorDiff::Append { values } => {
717 assert_eq!(values.len(), 2);
718 assert_eq!(values[0].event_id(), Some(event_id_0));
719 assert_eq!(values[1].event_id(), Some(event_id_1));
720 }
721 );
722 assert_matches!(
723 &diffs[1],
724 VectorDiff::Append { values } => {
725 assert_eq!(values.len(), 1);
726 assert_eq!(values[0].event_id(), Some(event_id_2));
727 }
728 );
729
730 linked_chunk.reset();
732 linked_chunk.chunks.push_items_back([event_3]);
733
734 let diffs = linked_chunk.updates_as_vector_diffs();
736
737 assert_eq!(diffs.len(), 2);
738
739 assert_matches!(&diffs[0], VectorDiff::Clear);
740 assert_matches!(
741 &diffs[1],
742 VectorDiff::Append { values } => {
743 assert_eq!(values.len(), 1);
744 assert_eq!(values[0].event_id(), Some(event_id_3));
745 }
746 );
747 }
748
749 #[test]
750 fn test_debug_string() {
751 let event_factory = EventFactory::new().room(&DEFAULT_TEST_ROOM_ID).sender(*ALICE);
752
753 let mut linked_chunk = EventLinkedChunk::new();
754 linked_chunk.chunks.push_items_back(vec![
755 event_factory
756 .text_msg("hey")
757 .event_id(event_id!("$123456789101112131415617181920"))
758 .into_event(),
759 event_factory.text_msg("you").event_id(event_id!("$2")).into_event(),
760 ]);
761 linked_chunk.chunks.push_gap_back(Gap { prev_token: "raclette".to_owned() });
762
763 let _ = linked_chunk.updates_as_vector_diffs();
765
766 let output = linked_chunk.debug_string();
767
768 assert_eq!(output.len(), 2);
769 assert_eq!(&output[0], "chunk #0: events[#0: $12345678, #1: $2]");
770 assert_eq!(&output[1], "chunk #1: gap['raclette']");
771 }
772
773 #[test]
774 fn test_sort_positions_descending() {
775 let mut positions = vec![
776 Position::new(ChunkIdentifier::new(2), 1),
777 Position::new(ChunkIdentifier::new(1), 0),
778 Position::new(ChunkIdentifier::new(2), 0),
779 Position::new(ChunkIdentifier::new(1), 1),
780 Position::new(ChunkIdentifier::new(0), 0),
781 ];
782
783 sort_positions_descending(&mut positions);
784
785 assert_eq!(
786 positions,
787 &[
788 Position::new(ChunkIdentifier::new(2), 1),
789 Position::new(ChunkIdentifier::new(2), 0),
790 Position::new(ChunkIdentifier::new(1), 1),
791 Position::new(ChunkIdentifier::new(1), 0),
792 Position::new(ChunkIdentifier::new(0), 0),
793 ]
794 );
795 }
796}