From a2fc51afe1bec00cdbd53a21a82d960d2ea7fb8a Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Wed, 28 Feb 2024 09:47:27 +0100 Subject: [PATCH] !second --- crates/matrix-sdk/src/event_cache/store.rs | 329 ++++++++++++++++----- 1 file changed, 251 insertions(+), 78 deletions(-) diff --git a/crates/matrix-sdk/src/event_cache/store.rs b/crates/matrix-sdk/src/event_cache/store.rs index 0c91ae51c0d..db03d65e234 100644 --- a/crates/matrix-sdk/src/event_cache/store.rs +++ b/crates/matrix-sdk/src/event_cache/store.rs @@ -72,7 +72,7 @@ impl EventCacheStore for MemoryStore { pub mod experimental { #![allow(dead_code)] - use std::{fmt, iter::once, ptr::NonNull}; + use std::{fmt, iter::once, ops::Not, ptr::NonNull}; const DEFAULT_CHUNK_CAPACITY: usize = 100; @@ -90,62 +90,79 @@ pub mod experimental { self.chunks.len() } - /// Push one event at the back of existing events. - pub fn push_event_back(&mut self, event: Event) { - self.push_events_back(once(event)) + /// Push one event after existing events. + pub fn push_event(&mut self, event: Event) { + self.push_events(once(event)) } - /// Push events at the back of existing events. + /// Push events after existing events. /// /// The last event in `events` is the most recent one. - pub fn push_events_back(&mut self, events: I) + pub fn push_events(&mut self, events: I) where I: IntoIterator, I::IntoIter: ExactSizeIterator, { - self.chunks.push_items_back(events.into_iter()); + self.chunks.push_items_back(events.into_iter()) } - /* - pub fn push_events_at(&mut self, events: I, index: usize) + /// Push a gap. + pub fn push_gap(&mut self) { + self.chunks.push_gap_back() + } + + /// Push events at a specified position. + pub fn insert_events_at( + &mut self, + events: I, + position: ItemPosition, + ) -> Result<(), LinkedChunkError> where I: IntoIterator, I::IntoIter: ExactSizeIterator, + Event: fmt::Debug, { - self.chunks.push_items_at(events.into_iter(), index); - } - */ - - /// Push a gap between events. - pub fn push_gap_back(&mut self) { - self.chunks.push_gap_back() + self.chunks.insert_items_at(events.into_iter(), position) } - fn chunk_position<'a, P>(&'a self, predicate: P) -> Option + /// Search for a chunk, and return its position. + pub fn chunk_position<'a, P>(&'a self, predicate: P) -> Option where P: FnMut(&'a Chunk) -> bool, { self.chunks.chunk_position(predicate) } - fn event_position<'a, P>(&'a self, predicate: P) -> Option + /// Search for an item, and return its position. + pub fn event_position<'a, P>(&'a self, predicate: P) -> Option where P: FnMut(&'a Event) -> bool, { self.chunks.item_position(predicate) } + /// Iterate over the chunks. + /// /// The most recent chunk comes first. pub fn iter_chunks<'a>(&'a self) -> LinkedChunkIter<'a, Event, CHUNK_CAPACITY> { self.chunks.iter_chunks() } + /// Iterate over the events. + /// /// The most recent event comes first. pub fn iter_events<'a>(&'a self) -> impl Iterator { self.chunks.iter_items() } } + #[derive(Debug)] + pub enum LinkedChunkError { + InvalidChunkIndex { index: ChunkPosition }, + ChunkIsAGap { index: ChunkPosition }, + InvalidItemIndex { index: usize }, + } + struct LinkedChunk { first: NonNull>, last: Option>>, @@ -153,14 +170,21 @@ pub mod experimental { } impl LinkedChunk { + /// Create a new [`Self`]. fn new() -> Self { Self { first: Chunk::new_items_leaked(), last: None, length: 0 } } + /// Get the number of items in this linked chunk. fn len(&self) -> usize { self.length } + /// Push items at the end of the [`LinkedChunk`], i.e. on the last + /// chunk. + /// + /// If the last chunk doesn't have enough space to welcome all `items`, + /// then new chunks can be created (and linked appropriately). fn push_items_back(&mut self, items: I) where I: Iterator + ExactSizeIterator, @@ -168,40 +192,93 @@ pub mod experimental { let number_of_items = items.len(); let last_chunk = unsafe { self.last.as_mut().unwrap_or(&mut self.first).as_mut() }; - last_chunk.push_items_back(items); - // Now we can update `self.last` if necessary. - // - // From `last_chunk`, read the next chunk until we find the last one. - if let Some(current) = last_chunk.next { - let mut current = unsafe { current.as_ref() }; + // Push the items. + let last_chunk = last_chunk.push_items(items); - while let Some(next) = current.next() { - current = next; - } + assert!(last_chunk.is_last_chunk(), "`last_chunk` must be… the last chunk"); - self.last = Some(NonNull::from(current)); + // We need to update `self.last` if and only if `last_chunk` _is not_ the first + // chunk, and _is_ the last chunk (ensured by the `assert!` above). + if last_chunk.is_first_chunk().not() { + // Maybe `last_chunk` is the same as the previous `self.last` chunk, but it's + // OK. + self.last = Some(NonNull::from(last_chunk)); } self.length += number_of_items; } - /* - fn push_items_at(&mut self, items: I, index: usize) + /// Push a gap at the end of the [`LinkedChunk`], i.e. after the last + /// chunk. + fn push_gap_back(&mut self) { + let last_chunk = unsafe { self.last.as_mut().unwrap_or(&mut self.first).as_mut() }; + last_chunk.insert_next(Chunk::new_gap_leaked()); + + self.last = last_chunk.next; + } + + /// Insert items at a specified position in the [`LinkedChunk`]. + /// + /// Because the `position` can be invalid, this method returns a + /// `Result`. + fn insert_items_at( + &mut self, + items: I, + position: ItemPosition, + ) -> Result<(), LinkedChunkError> where I: Iterator + ExactSizeIterator, + T: fmt::Debug, { - let chunk = self.find + let chunk_index = position.chunk_index(); + let item_index = position.item_index(); + let number_of_items = items.len(); + + let chunk = self + .nth_chunk_mut(chunk_index) + .ok_or(LinkedChunkError::InvalidChunkIndex { index: chunk_index })?; + + let chunk = match &mut chunk.content { + ChunkContent::Gap => { + return Err(LinkedChunkError::ChunkIsAGap { index: chunk_index }) + } + ChunkContent::Items(current_items) => { + if item_index >= current_items.len() { + return Err(LinkedChunkError::InvalidItemIndex { index: item_index }); + } + + let detached_items = current_items.split_off(item_index); + chunk.push_items(items).push_items(detached_items.into_iter()) + } + }; + + // We need to update `self.last` if and only if `last_chunk` _is not_ the first + // chunk, and _is_ the last chunk. + if chunk.is_first_chunk().not() && chunk.is_last_chunk() { + // Maybe `chunk` is the same as the previous `self.last` chunk, but it's + // OK. + self.last = Some(NonNull::from(chunk)); + } + + self.length += number_of_items; + + Ok(()) } - */ - fn push_gap_back(&mut self) { - let last_chunk = unsafe { self.last.as_mut().unwrap_or(&mut self.first).as_mut() }; - last_chunk.insert_next(Chunk::new_gap_leaked()); + /// + fn nth_chunk_mut<'a>(&'a mut self, nth: ChunkPosition) -> Option<&'a mut Chunk> { + let mut chunk = + self.last.or(Some(self.first)).as_mut().map(|chunk| unsafe { chunk.as_mut() }); - self.last = last_chunk.next; + for _ in 0..nth { + chunk = chunk?.previous_mut(); + } + + chunk } + /// Search for a chunk, and return its position. fn chunk_position<'a, P>(&'a self, mut predicate: P) -> Option where P: FnMut(&'a Chunk) -> bool, @@ -210,6 +287,7 @@ pub mod experimental { .find_map(|(chunk_position, chunk)| predicate(chunk).then_some(chunk_position)) } + /// Search for an item, and return its position. fn item_position<'a, P>(&'a self, mut predicate: P) -> Option where P: FnMut(&'a T) -> bool, @@ -218,45 +296,57 @@ pub mod experimental { .find_map(|(item_position, item)| predicate(item).then_some(item_position)) } + /// Iterate over the chunks. + /// + /// It iterates from the last to the first chunk. fn iter_chunks<'a>(&'a self) -> LinkedChunkIter<'a, T, C> { LinkedChunkIter::new(unsafe { self.last.unwrap_or(self.first).as_ref() }) } + /// Iterate over the items. + /// + /// It iterates from the last the first item. fn iter_items<'a>(&'a self) -> impl Iterator { self.iter_chunks() .filter_map(|(chunk_index, chunk)| match &chunk.content { + ChunkContent::Gap => None, ChunkContent::Items(items) => { Some(items.iter().rev().enumerate().map(move |(item_index, item)| { (ItemPosition(chunk_index, item_index), item) })) } - _ => None, }) .flatten() } } + /// The position of a chunk in a [`LinkedChunk`]. type ChunkPosition = usize; + /// The position of an item in a [`LinkedChunk`]. #[derive(Debug, PartialEq)] pub struct ItemPosition(ChunkPosition, usize); impl ItemPosition { + /// Get the chunk index of the item. pub fn chunk_index(&self) -> ChunkPosition { self.0 } + /// Get the item index inside its chunk. pub fn item_index(&self) -> usize { self.1 } } + /// An iterator over a [`LinkedChunk`]. pub struct LinkedChunkIter<'a, T, const CHUNK_CAPACITY: usize> { chunk: Option<&'a Chunk>, position: usize, } impl<'a, T, const C: usize> LinkedChunkIter<'a, T, C> { + /// Create a new [`LinkedChunkIter`] from a particular [`Chunk`]. fn new(from_chunk: &'a Chunk) -> Self { Self { chunk: Some(from_chunk), position: 0 } } @@ -266,48 +356,54 @@ pub mod experimental { type Item = (ChunkPosition, &'a Chunk); fn next(&mut self) -> Option { - if let Some(chunk) = self.chunk { - let position = self.position; + let Some(chunk) = self.chunk else { + return None; + }; - self.chunk = chunk.previous(); - self.position += 1; + let position = self.position; - Some((position, chunk)) - } else { - None - } + self.chunk = chunk.previous(); + self.position += 1; + + Some((position, chunk)) } } + /// This enum represents the content of a [`Chunk`]. #[derive(Debug)] enum ChunkContent { + /// The chunk represents a gap in the linked chunk, i.e. a hole. It + /// means that some items are missing in this location. Gap, + + /// The chunk contains items. Items(Vec), } + /// A chunk is a node in the [`LinkedChunk`]. pub struct Chunk { + /// The previous chunk. previous: Option>>, + + /// The next chunk. next: Option>>, + + /// The content of the chunk. content: ChunkContent, } impl Chunk { + /// Create a new gap chunk. const fn new_gap() -> Self { Self { previous: None, next: None, content: ChunkContent::Gap } } + /// Create a new items chunk. const fn new_items() -> Self { Self { previous: None, next: None, content: ChunkContent::Items(Vec::new()) } } - const fn is_gap(&self) -> bool { - matches!(self.content, ChunkContent::Gap) - } - - const fn is_items(&self) -> bool { - !self.is_gap() - } - + /// Create a new gap chunk, but box it and leak it. fn new_gap_leaked() -> NonNull { let chunk = Self::new_gap(); let chunk_box = Box::new(chunk); @@ -315,6 +411,7 @@ pub mod experimental { NonNull::from(Box::leak(chunk_box)) } + /// Create a new items chunk, but box it and leak it. fn new_items_leaked() -> NonNull { let chunk = Self::new_items(); let chunk_box = Box::new(chunk); @@ -322,6 +419,29 @@ pub mod experimental { NonNull::from(Box::leak(chunk_box)) } + /// Check whether this current chunk is a gap chunk. + fn is_gap(&self) -> bool { + matches!(self.content, ChunkContent::Gap) + } + + /// Check whether this current chunk is an items chunk. + fn is_items(&self) -> bool { + !self.is_gap() + } + + /// Check whether this current chunk is the first chunk. + fn is_first_chunk(&self) -> bool { + self.previous.is_none() + } + + /// Check whether this current chunk is the last chunk. + fn is_last_chunk(&self) -> bool { + self.next.is_none() + } + + /// The length of the chunk, i.e. how many items are in it. + /// + /// It will always return 0 if it's a gap chunk. fn len(&self) -> usize { match &self.content { ChunkContent::Gap => 0, @@ -329,11 +449,22 @@ pub mod experimental { } } + /// Calculate the free space of the current chunk. + /// + /// It will always return 0 if it's a gap chunk. fn free_space(&self) -> usize { CAPACITY.saturating_sub(self.len()) } - fn push_items_back(&mut self, mut new_items: I) + /// Push items on the current chunk. + /// + /// If the chunk doesn't have enough spaces to welcome `new_items`, new + /// chunk will be inserted next, and correctly linked. + /// + /// This method returns the last inserted chunk if any, or the current + /// chunk. Basically, it returns the chunk onto which new computations + /// must happen. + fn push_items(&mut self, mut new_items: I) -> &mut Self where I: Iterator + ExactSizeIterator, { @@ -344,32 +475,42 @@ pub mod experimental { // Cannot push items on a `Gap`. Let's insert a new `Items` chunk to push the // items onto it. ChunkContent::Gap => { + // Insert a new items chunk. self.insert_next(Self::new_items_leaked()); - self.next_mut().expect("The next chunk must exist").push_items_back(new_items); + + // Now push the new items on the next chunk, and return the result of + // `push_items`. + self.next_mut().expect("The next chunk must exist").push_items(new_items) } ChunkContent::Items(items) => { // There is enough space to push all the new items. if number_of_new_items <= free_space { items.extend(new_items); + + // Return the current chunk. + self } else { if free_space > 0 { - // Take all possible items to fill the free space + // Take all possible items to fill the free space. items.extend(new_items.by_ref().take(free_space)); } // Create a new chunk. self.insert_next(Self::new_items_leaked()); - // Now push the rest of the new items on the next chunk. - self.next_mut() - .expect("The next chunk must exist") - .push_items_back(new_items); + // Now push the rest of the new items on the next chunk, and return the + // result of `push_items`. + self.next_mut().expect("The next chunk must exist").push_items(new_items) } } } } + /// Insert a new chunk after the current one. + /// + /// The respective [`Self::previous`] and [`Self::next`] of the current + /// and new chunk will be updated accordingly. fn insert_next(&mut self, mut new_chunk_ptr: NonNull) { let new_chunk = unsafe { new_chunk_ptr.as_mut() }; @@ -388,14 +529,22 @@ pub mod experimental { new_chunk.previous = Some(NonNull::from(self)); } + /// Get a reference to the previous chunk if any. fn previous(&self) -> Option<&Self> { - self.previous.as_ref().map(|non_null| unsafe { non_null.as_ref() }) + self.previous.map(|non_null| unsafe { non_null.as_ref() }) + } + + /// Get a mutable to the previous chunk if any. + fn previous_mut(&mut self) -> Option<&mut Self> { + self.previous.as_mut().map(|non_null| unsafe { non_null.as_mut() }) } + /// Get a reference to the next chunk if any. fn next(&self) -> Option<&Self> { - self.next.as_ref().map(|non_null| unsafe { non_null.as_ref() }) + self.next.map(|non_null| unsafe { non_null.as_ref() }) } + /// Get a mutable reference to the next chunk if any. fn next_mut(&mut self) -> Option<&mut Self> { self.next.as_mut().map(|non_null| unsafe { non_null.as_mut() }) } @@ -445,7 +594,8 @@ pub mod experimental { #[cfg(test)] mod tests { - use super::{Events, ItemPosition}; + + use super::{Events, ItemPosition, LinkedChunkError}; macro_rules! assert_events_eq { ( @_ [ $iterator:ident, $chunk_index:ident, $event_index:ident ] { [-] $( $rest:tt )* } { $( $accumulator:tt )* } ) => { @@ -501,50 +651,73 @@ pub mod experimental { #[test] fn test_push_events_back() { let mut events = Events::::new(); - events.push_events_back(['a']); + events.push_events(['a']); assert_events_eq!(events.iter_events(), ['a']); - events.push_events_back(['b', 'c']); + events.push_events(['b', 'c']); assert_events_eq!(events.iter_events(), ['c', 'b', 'a']); - events.push_events_back(['d']); - events.push_event_back('e'); + events.push_events(['d']); + events.push_event('e'); assert_events_eq!(events.iter_events(), ['e', 'd'] ['c', 'b', 'a']); - events.push_events_back(['f', 'g', 'h', 'i', 'j']); - assert_events_eq!(events.iter_events(), ['j'] ['i', 'h', 'g'] ['f', 'e', 'd'] ['c', 'b', 'a']) + events.push_events(['f', 'g', 'h', 'i', 'j']); + assert_events_eq!(events.iter_events(), ['j'] ['i', 'h', 'g'] ['f', 'e', 'd'] ['c', 'b', 'a']); + + assert_eq!(events.len(), 10); } #[test] fn test_push_gap_back() { let mut events = Events::::new(); - events.push_events_back(['a']); + events.push_events(['a']); assert_events_eq!(events.iter_events(), ['a']); - events.push_gap_back(); + events.push_gap(); assert_events_eq!(events.iter_events(), [-] ['a']); - events.push_events_back(['b', 'c', 'd', 'e']); + events.push_events(['b', 'c', 'd', 'e']); assert_events_eq!(events.iter_events(), ['e'] ['d', 'c', 'b'] [-] ['a']); - events.push_gap_back(); - events.push_gap_back(); // why not + events.push_gap(); + events.push_gap(); // why not assert_events_eq!(events.iter_events(), [-] [-] ['e'] ['d', 'c', 'b'] [-] ['a']); - events.push_events_back(['f', 'g', 'h', 'i']); + events.push_events(['f', 'g', 'h', 'i']); assert_events_eq!(events.iter_events(), ['i'] ['h', 'g', 'f'] [-] [-] ['e'] ['d', 'c', 'b'] [-] ['a']); + + assert_eq!(events.len(), 9); } #[test] fn test_positions() { let mut events = Events::::new(); - events.push_events_back(['a', 'b', 'c', 'd', 'e', 'f']); - events.push_gap_back(); - events.push_events_back(['g', 'h', 'i', 'j']); + events.push_events(['a', 'b', 'c', 'd', 'e', 'f']); + events.push_gap(); + events.push_events(['g', 'h', 'i', 'j']); assert_events_eq!(events.iter_events(), ['j'] ['i', 'h', 'g'] [-] ['f', 'e', 'd'] ['c', 'b', 'a']); assert_eq!(events.chunk_position(|chunk| chunk.is_gap()), Some(2)); assert_eq!(events.event_position(|event| *event == 'e'), Some(ItemPosition(3, 1))); } + + #[test] + fn test_insert_items_at() -> Result<(), LinkedChunkError> { + let mut events = Events::::new(); + events.push_events(['a', 'b', 'c', 'd', 'e', 'f']); + assert_events_eq!(events.iter_events(), ['f', 'e', 'd'] ['c', 'b', 'a']); + + let position_of_e = events.event_position(|event| *event == 'e').unwrap(); + + // Insert 4 elements, so that it overflows the chunk capacity. It's important to + // see whether chunks are correctly updated and linked. + events.insert_events_at(['w', 'x', 'y', 'z'], position_of_e)?; + + assert_events_eq!(events.iter_events(), ['f'] ['e', 'z', 'y'] ['x', 'w', 'd'] ['c', 'b', 'a']); + + assert_eq!(events.len(), 10); + + Ok(()) + } } }