task(sdk) Add RoomEventCacheState::load_more_events_backwards.

This patch adds the `RoomEventCacheState::load_more_events_backwards`
method to load a new chunk and to insert it at the beginning of the
`LinkedChunk`.

It uses the new `EventCacheStore::load_previous_chunk` method, along
with the new `LinkedChunkBuilder::insert_new_first_chunk` method.
This commit is contained in:
Ivan Enderlin
2025-02-10 15:26:05 +01:00
parent 0aae72c161
commit bfec34db20
3 changed files with 123 additions and 5 deletions

View File

@@ -80,7 +80,7 @@ impl LinkedChunkBuilder {
pub fn insert_new_first_chunk<const CAP: usize, Item, Gap>(
linked_chunk: &mut LinkedChunk<CAP, Item, Gap>,
new_first_chunk: RawChunk<Item, Gap>,
) -> Result<&Chunk<CAP, Item, Gap>, LinkedChunkBuilderError>
) -> Result<(), LinkedChunkBuilderError>
where
Item: Clone,
Gap: Clone,
@@ -187,7 +187,7 @@ impl LinkedChunkBuilder {
}
}
Ok(linked_chunk.links.first_chunk())
Ok(())
}
}

View File

@@ -16,7 +16,11 @@ use std::cmp::Ordering;
use eyeball_im::VectorDiff;
pub use matrix_sdk_base::event_cache::{Event, Gap};
use matrix_sdk_base::{apply_redaction, event_cache::store::DEFAULT_CHUNK_CAPACITY};
use matrix_sdk_base::{
apply_redaction,
event_cache::store::DEFAULT_CHUNK_CAPACITY,
linked_chunk::{ChunkContent, LinkedChunkBuilder, LinkedChunkBuilderError, RawChunk},
};
use matrix_sdk_common::linked_chunk::{
AsVector, Chunk, ChunkIdentifier, EmptyChunk, Error, Iter, IterBackward, LinkedChunk,
ObservableUpdates, Position,
@@ -407,6 +411,19 @@ impl RoomEvents {
}
}
// Private implementations, implementation specific.
impl RoomEvents {
pub(super) fn insert_new_chunk_as_first(
&mut self,
mut raw_new_first_chunk: RawChunk<Event, Gap>,
) -> Result<(), LinkedChunkBuilderError> {
// Pretend there is no previous chunk.
raw_new_first_chunk.previous = None;
LinkedChunkBuilder::insert_new_first_chunk(&mut self.chunks, raw_new_first_chunk)
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;

View File

@@ -17,6 +17,7 @@
use std::{collections::BTreeMap, fmt, sync::Arc};
use events::Gap;
use eyeball_im::VectorDiff;
use matrix_sdk_base::{
deserialized_responses::{AmbiguityChange, TimelineEvent},
linked_chunk::ChunkContent,
@@ -519,6 +520,20 @@ fn chunk_debug_string(content: &ChunkContent<TimelineEvent, Gap>) -> String {
}
}
/// Internal type to represent the output of
/// `RoomEventCacheState::load_more_events_backwards`.
#[derive(Debug)]
pub(super) enum LoadMoreEventsBackwardsOutcome {
/// A gap has been inserted.
Gap,
/// The start of the timeline has been reached.
StartOfTimeline,
/// Events have been inserted.
Events(Vec<TimelineEvent>, Vec<VectorDiff<TimelineEvent>>),
}
// Use a private module to hide `events` to this parent module.
mod private {
use std::sync::Arc;
@@ -527,14 +542,14 @@ mod private {
use matrix_sdk_base::{
deserialized_responses::{TimelineEvent, TimelineEventKind},
event_cache::{store::EventCacheStoreLock, Event},
linked_chunk::{LinkedChunkBuilder, Update},
linked_chunk::{ChunkContent, LinkedChunkBuilder, Update},
};
use matrix_sdk_common::executor::spawn;
use once_cell::sync::OnceCell;
use ruma::{serde::Raw, OwnedEventId, OwnedRoomId};
use tracing::{error, instrument, trace};
use super::events::RoomEvents;
use super::{events::RoomEvents, LoadMoreEventsBackwardsOutcome};
use crate::event_cache::{deduplicator::Deduplicator, EventCacheError};
/// State for a single room's event cache.
@@ -659,6 +674,92 @@ mod private {
Ok((events, duplicated_event_ids, all_duplicates))
}
/// Load more events backwards if the last chunk is **not** a gap.
///
/// Return `Ok(Some((events, reached_start), _))` if events have been
/// inserted, `Ok(None)` if a gap has been inserted or if the
/// store is disabled.
#[must_use = "Updates as `VectorDiff` must probably be propagated via `RoomEventCacheUpdate`"]
pub(in super::super) async fn load_more_events_backwards(
&mut self,
) -> Result<LoadMoreEventsBackwardsOutcome, EventCacheError> {
let Some(store) = self.store.get() else {
// No store: no events to insert. Pretend the caller has to act as if a gap was
// present.
return Ok(LoadMoreEventsBackwardsOutcome::Gap);
};
let first_chunk = self
.events
.chunks()
.next()
// SAFETY: A `LinkedChunk` is never empty, it always contains at least one chunk.
.expect("The `LinkedChunk` in `RoomEvents` cannot be empty");
// The first chunk is a gap. Don't load more events! The gap must be resolved.
if first_chunk.is_gap() {
return Ok(LoadMoreEventsBackwardsOutcome::Gap);
}
// Because `first_chunk` is `not `Send`, get this information before the
// `.await` point, so that this `Future` can implement `Send`.
let first_chunk_identifier = first_chunk.identifier();
let room_id = &self.room;
let store = store.lock().await?;
// The first chunk is not a gap, we can load its previous chunk.
let new_first_chunk =
match store.load_previous_chunk(room_id, first_chunk_identifier).await {
Ok(Some(new_first_chunk)) => {
// All good, let's continue with this chunk.
new_first_chunk
}
Ok(None) => {
// No previous chunk: no events to insert. Better, it means we've reached
// the start of the timeline!
return Ok(LoadMoreEventsBackwardsOutcome::StartOfTimeline);
}
Err(err) => {
error!("error when loading the previous chunk of a linked chunk: {err}");
// Clear storage for this room.
store.handle_linked_chunk_updates(room_id, vec![Update::Clear]).await?;
// Return the error.
return Err(err.into());
}
};
let events = match &new_first_chunk.content {
ChunkContent::Gap(_) => None,
ChunkContent::Items(events) => Some(events.clone()),
};
if let Err(err) = self.events.insert_new_chunk_as_first(new_first_chunk) {
error!("error when inserting the previous chunk into its linked chunk: {err}");
// Clear storage for this room.
store.handle_linked_chunk_updates(room_id, vec![Update::Clear]).await?;
// Return the error.
return Err(err.into());
};
// ⚠️ Let's not propagate the updates to the store! We already have these data
// in the store! Let's drain them.
let _ = self.events.updates().take();
// However, we want to get updates as `VectorDiff`s.
let updates_as_vector_diffs = self.events.updates_as_vector_diffs();
Ok(match events {
None => LoadMoreEventsBackwardsOutcome::Gap,
Some(events) => {
LoadMoreEventsBackwardsOutcome::Events(events, updates_as_vector_diffs)
}
})
}
/// Removes the bundled relations from an event, if they were present.
///
/// Only replaces the present if it contained bundled relations.