Skip to content

Commit 9720873

Browse files
committed
feat(sdk): Optimise how insert_gap_at works when inserting at first position.
1 parent df4dc04 commit 9720873

File tree

1 file changed

+87
-44
lines changed

1 file changed

+87
-44
lines changed

crates/matrix-sdk/src/event_cache/linked_chunk.rs

Lines changed: 87 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,30 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
197197
.chunk_mut(chunk_identifier)
198198
.ok_or(LinkedChunkError::InvalidChunkIdentifier { identifier: chunk_identifier })?;
199199

200+
// If `item_index` is 0, we don't want to split the current items chunk to
201+
// insert a new gap chunk, otherwise it would create an empty current items
202+
// chunk. Let's handle this case in particular.
203+
//
204+
// Of course this optimisation applies if there is a previous chunk. Remember
205+
// the invariant: a `Gap` cannot be the first chunk.
206+
if item_index == 0 && chunk.is_items() && chunk.previous.is_some() {
207+
let previous_chunk = chunk
208+
.previous_mut()
209+
// SAFETY: The `previous` chunk exists because we have tested
210+
// `chunk.previous.is_some()` in the `if` statement.
211+
.unwrap();
212+
213+
previous_chunk.insert_next(Chunk::new_gap_leaked(
214+
chunk_identifier_generator.generate_next().unwrap(),
215+
content,
216+
));
217+
218+
// We don't need to update `self.last` because we have inserted a new chunk
219+
// before `chunk`.
220+
221+
return Ok(());
222+
}
223+
200224
let chunk = match &mut chunk.content {
201225
ChunkContent::Gap(..) => {
202226
return Err(LinkedChunkError::ChunkIsAGap { identifier: chunk_identifier });
@@ -345,15 +369,15 @@ impl<Item, Gap, const CAP: usize> LinkedChunk<Item, Gap, CAP> {
345369
}
346370
}
347371

348-
/// Search for a chunk, and return its identifier.
372+
/// Search backward for a chunk, and return its identifier.
349373
pub fn chunk_identifier<'a, P>(&'a self, mut predicate: P) -> Option<ChunkIdentifier>
350374
where
351375
P: FnMut(&'a Chunk<Item, Gap, CAP>) -> bool,
352376
{
353377
self.rchunks().find_map(|chunk| predicate(chunk).then(|| chunk.identifier()))
354378
}
355379

356-
/// Search for an item, and return its position.
380+
/// Search backward for an item, and return its position.
357381
pub fn item_position<'a, P>(&'a self, mut predicate: P) -> Option<Position>
358382
where
359383
P: FnMut(&'a Item) -> bool,
@@ -952,75 +976,60 @@ mod tests {
952976
};
953977

954978
macro_rules! assert_items_eq {
955-
( @_ [ $iterator:ident, $chunk_index:ident, $item_index:ident ] { [-] $( $rest:tt )* } { $( $accumulator:tt )* } ) => {
979+
( @_ [ $iterator:ident ] { [-] $( $rest:tt )* } { $( $accumulator:tt )* } ) => {
956980
assert_items_eq!(
957981
@_
958-
[ $iterator, $chunk_index, $item_index ]
982+
[ $iterator ]
959983
{ $( $rest )* }
960984
{
961985
$( $accumulator )*
962-
$chunk_index += 1;
986+
{
987+
let chunk = $iterator .next().expect("next chunk (expect gap)");
988+
assert!(chunk.is_gap(), "chunk ");
989+
}
963990
}
964991
)
965992
};
966993

967-
( @_ [ $iterator:ident, $chunk_index:ident, $item_index:ident ] { [ $( $item:expr ),* ] $( $rest:tt )* } { $( $accumulator:tt )* } ) => {
994+
( @_ [ $iterator:ident ] { [ $( $item:expr ),* ] $( $rest:tt )* } { $( $accumulator:tt )* } ) => {
968995
assert_items_eq!(
969996
@_
970-
[ $iterator, $chunk_index, $item_index ]
997+
[ $iterator ]
971998
{ $( $rest )* }
972999
{
9731000
$( $accumulator )*
974-
let _expected_chunk_identifier = $iterator .peek().unwrap().1.chunk_identifier();
975-
$(
976-
assert_matches!(
977-
$iterator .next(),
978-
Some((chunk_index, Position(chunk_identifier, item_index), & $item )) => {
979-
// Ensure the chunk index (from the enumeration) is correct.
980-
assert_eq!(chunk_index, $chunk_index);
981-
// Ensure the chunk identifier is the same for all items in this chunk.
982-
assert_eq!(chunk_identifier, _expected_chunk_identifier);
983-
// Ensure the item has the expected position.
984-
assert_eq!(item_index, $item_index);
985-
}
986-
);
987-
$item_index += 1;
988-
)*
989-
$item_index = 0;
990-
$chunk_index += 1;
1001+
{
1002+
let chunk = $iterator .next().expect("next chunk (expect items)");
1003+
assert!(chunk.is_items());
1004+
1005+
let ChunkContent::Items(items) = chunk.content() else { unreachable!() };
1006+
1007+
let mut items_iterator = items.iter();
1008+
1009+
$(
1010+
assert_eq!(items_iterator.next(), Some(& $item ));
1011+
)*
1012+
1013+
assert!(items_iterator.next().is_none(), "no more items");
1014+
}
9911015
}
9921016
)
9931017
};
9941018

995-
( @_ [ $iterator:ident, $chunk_index:ident, $item_index:ident ] {} { $( $accumulator:tt )* } ) => {
1019+
( @_ [ $iterator:ident ] {} { $( $accumulator:tt )* } ) => {
9961020
{
997-
let mut $chunk_index = 0;
998-
let mut $item_index = 0;
9991021
$( $accumulator )*
1022+
assert!( $iterator .next().is_none(), "no more chunks");
10001023
}
10011024
};
10021025

10031026
( $linked_chunk:expr, $( $all:tt )* ) => {
10041027
assert_items_eq!(
10051028
@_
1006-
[ iterator, _chunk_index, _item_index ]
1029+
[ iterator ]
10071030
{ $( $all )* }
10081031
{
1009-
let mut iterator = $linked_chunk
1010-
.chunks()
1011-
.enumerate()
1012-
.filter_map(|(chunk_index, chunk)| match &chunk.content {
1013-
ChunkContent::Gap(..) => None,
1014-
ChunkContent::Items(items) => {
1015-
let identifier = chunk.identifier();
1016-
1017-
Some(items.iter().enumerate().map(move |(item_index, item)| {
1018-
(chunk_index, Position(identifier, item_index), item)
1019-
}))
1020-
}
1021-
})
1022-
.flatten()
1023-
.peekable();
1032+
let mut iterator = $linked_chunk.chunks();
10241033
}
10251034
)
10261035
}
@@ -1403,14 +1412,48 @@ mod tests {
14031412
assert_items_eq!(linked_chunk, ['a'] [-] ['b', 'c'] ['d', 'e', 'f']);
14041413
}
14051414

1406-
// Insert at the beginning of a chunk.
1415+
// Insert at the beginning of a chunk + it's the first chunk.
14071416
{
14081417
let position_of_a = linked_chunk.item_position(|item| *item == 'a').unwrap();
14091418
linked_chunk.insert_gap_at((), position_of_a)?;
14101419

1420+
// A new empty chunk is created as the first chunk.
14111421
assert_items_eq!(linked_chunk, [] [-] ['a'] [-] ['b', 'c'] ['d', 'e', 'f']);
14121422
}
14131423

1424+
// Insert at the beginning of a chunk.
1425+
{
1426+
let position_of_d = linked_chunk.item_position(|item| *item == 'd').unwrap();
1427+
linked_chunk.insert_gap_at((), position_of_d)?;
1428+
1429+
// A new empty chunk is NOT created, i.e. `['d', 'e', 'f']` is not
1430+
// split into `[]` + `['d', 'e', 'f']` because it's a waste of
1431+
// space.
1432+
assert_items_eq!(linked_chunk, [] [-] ['a'] [-] ['b', 'c'] [-] ['d', 'e', 'f']);
1433+
}
1434+
1435+
// Insert in an empty chunk + it's the first chunk.
1436+
{
1437+
let position_of_first_empty_chunk = Position(ChunkIdentifier(0), 0);
1438+
assert_matches!(
1439+
linked_chunk.insert_gap_at((), position_of_first_empty_chunk),
1440+
Err(LinkedChunkError::InvalidItemIndex { index: 0 })
1441+
);
1442+
}
1443+
1444+
// Insert in an empty chunk.
1445+
{
1446+
// Replace a gap by empty items.
1447+
let gap_identifier = linked_chunk.chunk_identifier(Chunk::is_gap).unwrap();
1448+
let position = linked_chunk.replace_gap_at([], gap_identifier)?.first_position();
1449+
1450+
assert_items_eq!(linked_chunk, [] [-] ['a'] [-] ['b', 'c'] [] ['d', 'e', 'f']);
1451+
1452+
linked_chunk.insert_gap_at((), position)?;
1453+
1454+
assert_items_eq!(linked_chunk, [] [-] ['a'] [-] ['b', 'c'] [-] [] ['d', 'e', 'f']);
1455+
}
1456+
14141457
// Insert in a chunk that does not exist.
14151458
{
14161459
assert_matches!(

0 commit comments

Comments
 (0)