1use std::sync::{Arc, RwLock};
16
17use eyeball_im::VectorDiff;
18
19use super::{
20 updates::{ReaderToken, Update, UpdatesInner},
21 Position,
22};
23use crate::linked_chunk::{ChunkMetadata, UpdateToVectorDiff};
24
25#[derive(Debug)]
40pub struct OrderTracker<Item, Gap> {
41 updates: Arc<RwLock<UpdatesInner<Item, Gap>>>,
43
44 token: ReaderToken,
46
47 mapper: UpdateToVectorDiff<Item, NullAccumulator<Item>>,
49}
50
51struct NullAccumulator<Item> {
52 _phantom: std::marker::PhantomData<Item>,
53}
54
55#[cfg(not(tarpaulin_include))]
56impl<Item> std::fmt::Debug for NullAccumulator<Item> {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.write_str("NullAccumulator")
59 }
60}
61
62impl<Item> super::UpdatesAccumulator<Item> for NullAccumulator<Item> {
63 fn new(_num_updates_hint: usize) -> Self {
64 Self { _phantom: std::marker::PhantomData }
65 }
66}
67
68impl<Item> Extend<VectorDiff<Item>> for NullAccumulator<Item> {
69 fn extend<T: IntoIterator<Item = VectorDiff<Item>>>(&mut self, _iter: T) {
70 }
72}
73
74impl<Item, Gap> OrderTracker<Item, Gap>
75where
76 Item: Clone,
77{
78 pub(super) fn new(
89 updates: Arc<RwLock<UpdatesInner<Item, Gap>>>,
90 token: ReaderToken,
91 all_chunks_metadata: Vec<ChunkMetadata>,
92 ) -> Self {
93 {
95 let mut updates = updates.write().unwrap();
96 let _ = updates.take_with_token(token);
97 }
98
99 Self { updates, token, mapper: UpdateToVectorDiff::from_metadata(all_chunks_metadata) }
100 }
101
102 pub fn flush_updates(&mut self, inhibit: bool) {
109 if inhibit {
110 let _ = self.updates.write().unwrap().take_with_token(self.token);
112 } else {
113 let mut updater = self.updates.write().unwrap();
115 let updates = updater.take_with_token(self.token);
116 let _ = self.mapper.map(updates);
117 }
118 }
119
120 pub fn map_updates(&mut self, updates: &[Update<Item, Gap>]) {
125 let _ = self.mapper.map(updates);
126 }
127
128 pub fn ordering(&self, event_pos: Position) -> Option<usize> {
139 debug_assert!(self.updates.read().unwrap().is_reader_up_to_date(self.token));
142
143 let mut ordering = 0;
145 for (chunk_id, chunk_length) in &self.mapper.chunks {
146 if *chunk_id == event_pos.chunk_identifier() {
147 let offset_within_chunk = event_pos.index();
148 if offset_within_chunk >= *chunk_length {
149 return None;
151 }
152 return Some(ordering + offset_within_chunk);
155 }
156 ordering += *chunk_length;
159 }
160
161 None
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use assert_matches::assert_matches;
168 use matrix_sdk_test_macros::async_test;
169
170 use crate::linked_chunk::{
171 lazy_loader::from_last_chunk, ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator,
172 ChunkMetadata, LinkedChunk, OrderTracker, Position, RawChunk, Update,
173 };
174
175 #[async_test]
176 async fn test_linked_chunk_without_update_history_no_tracking() {
177 let mut linked_chunk = LinkedChunk::<10, char, ()>::new();
178 assert_matches!(linked_chunk.order_tracker(None), None);
179 }
180
181 fn assert_order_fully_loaded(
184 linked_chunk: &LinkedChunk<3, char, ()>,
185 tracker: &OrderTracker<char, ()>,
186 ) {
187 assert_order(linked_chunk, tracker, 0);
188 }
189
190 fn assert_order(
194 linked_chunk: &LinkedChunk<3, char, ()>,
195 tracker: &OrderTracker<char, ()>,
196 offset: usize,
197 ) {
198 for (i, (item_pos, _value)) in linked_chunk.items().enumerate() {
199 assert_eq!(tracker.ordering(item_pos), Some(i + offset));
200 }
201 }
202
203 #[async_test]
204 async fn test_non_lazy_updates() {
205 let mut linked_chunk = LinkedChunk::<3, _, _>::new_with_update_history();
208
209 let mut tracker = linked_chunk.order_tracker(None).unwrap();
210
211 {
215 linked_chunk.push_items_back(['a', 'b', 'c']);
216 tracker.flush_updates(false);
217 assert_order_fully_loaded(&linked_chunk, &tracker);
218 }
219
220 {
222 linked_chunk.push_gap_back(());
223 tracker.flush_updates(false);
224 assert_order_fully_loaded(&linked_chunk, &tracker);
225 }
226
227 {
229 let pos_b = linked_chunk.item_position(|c| *c == 'b').unwrap();
230 linked_chunk.insert_items_at(pos_b, ['d', 'e']).unwrap();
231 tracker.flush_updates(false);
232 assert_order_fully_loaded(&linked_chunk, &tracker);
233 }
234
235 {
237 let c_pos = linked_chunk.item_position(|c| *c == 'c').unwrap();
238 linked_chunk.insert_gap_at((), c_pos).unwrap();
239 tracker.flush_updates(false);
240 assert_order_fully_loaded(&linked_chunk, &tracker);
241 }
242
243 {
245 let last_gap =
246 linked_chunk.rchunks().filter(|c| c.is_gap()).last().unwrap().identifier();
247 linked_chunk.replace_gap_at(['f', 'g'], last_gap).unwrap();
248 tracker.flush_updates(false);
249 assert_order_fully_loaded(&linked_chunk, &tracker);
250 }
251
252 {
254 let a_pos = linked_chunk.item_position(|c| *c == 'd').unwrap();
255 linked_chunk.remove_item_at(a_pos).unwrap();
256 tracker.flush_updates(false);
257 assert_order_fully_loaded(&linked_chunk, &tracker);
258 }
259
260 {
262 let b_pos = linked_chunk.item_position(|c| *c == 'e').unwrap();
263 linked_chunk.replace_item_at(b_pos, 'E').unwrap();
264 tracker.flush_updates(false);
265 assert_order_fully_loaded(&linked_chunk, &tracker);
266 }
267
268 {
270 linked_chunk.clear();
271 tracker.flush_updates(false);
272 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 0)), None);
273 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), None);
274 }
275 }
276
277 #[async_test]
278 async fn test_lazy_loading() {
279 let db_metadata = vec![
283 ChunkMetadata {
285 previous: None,
286 identifier: ChunkIdentifier(0),
287 next: Some(ChunkIdentifier(1)),
288 num_items: 3,
289 },
290 ChunkMetadata {
292 previous: Some(ChunkIdentifier(0)),
293 identifier: ChunkIdentifier(1),
294 next: Some(ChunkIdentifier(2)),
295 num_items: 0,
296 },
297 ChunkMetadata {
299 previous: Some(ChunkIdentifier(1)),
300 identifier: ChunkIdentifier(2),
301 next: Some(ChunkIdentifier(3)),
302 num_items: 3,
303 },
304 ChunkMetadata {
306 previous: Some(ChunkIdentifier(2)),
307 identifier: ChunkIdentifier(3),
308 next: None,
309 num_items: 1,
310 },
311 ];
312
313 let mut linked_chunk = from_last_chunk::<3, _, ()>(
315 Some(RawChunk {
316 content: ChunkContent::Items(vec!['g']),
317 previous: Some(ChunkIdentifier(2)),
318 identifier: ChunkIdentifier(3),
319 next: None,
320 }),
321 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier(3)),
322 )
323 .expect("could recreate the linked chunk")
324 .expect("the linked chunk isn't empty");
325
326 let tracker = linked_chunk.order_tracker(Some(db_metadata)).unwrap();
327
328 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 0)), Some(0));
333 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
335 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 2)), Some(2));
337 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 42)), None);
339
340 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(1), 0)), None);
342 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(1), 42)), None);
343
344 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 0)), Some(3));
346 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 1)), Some(4));
348 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 2)), Some(5));
350 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 3)), None);
352
353 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(6));
355 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 1)), None);
357 }
358
359 #[async_test]
360 async fn test_lazy_updates() {
361 let db_metadata = vec![
365 ChunkMetadata {
367 previous: None,
368 identifier: ChunkIdentifier(0),
369 next: Some(ChunkIdentifier(1)),
370 num_items: 2,
371 },
372 ChunkMetadata {
374 previous: Some(ChunkIdentifier(0)),
375 identifier: ChunkIdentifier(1),
376 next: Some(ChunkIdentifier(2)),
377 num_items: 0,
378 },
379 ChunkMetadata {
381 previous: Some(ChunkIdentifier(1)),
382 identifier: ChunkIdentifier(2),
383 next: Some(ChunkIdentifier(3)),
384 num_items: 3,
385 },
386 ChunkMetadata {
388 previous: Some(ChunkIdentifier(2)),
389 identifier: ChunkIdentifier(3),
390 next: None,
391 num_items: 1,
392 },
393 ];
394
395 let mut linked_chunk = from_last_chunk(
397 Some(RawChunk {
398 content: ChunkContent::Items(vec!['g']),
399 previous: Some(ChunkIdentifier(2)),
400 identifier: ChunkIdentifier(3),
401 next: None,
402 }),
403 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier(3)),
404 )
405 .expect("could recreate the linked chunk")
406 .expect("the linked chunk isn't empty");
407
408 let mut tracker = linked_chunk.order_tracker(Some(db_metadata)).unwrap();
409
410 {
412 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
414 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
416 }
417
418 {
422 linked_chunk.push_items_back(['h', 'i']);
423 tracker.flush_updates(false);
424
425 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
427 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
428 assert_order(&linked_chunk, &tracker, 5);
430 }
431
432 let gap_id = {
434 linked_chunk.push_gap_back(());
435 tracker.flush_updates(false);
436
437 let last_chunk = linked_chunk.rchunks().next().unwrap();
439 assert!(last_chunk.is_gap());
440 assert_eq!(tracker.ordering(Position::new(last_chunk.identifier(), 0)), None);
441 assert_eq!(tracker.ordering(Position::new(last_chunk.identifier(), 42)), None);
442
443 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
445 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
446 assert_order(&linked_chunk, &tracker, 5);
448
449 last_chunk.identifier()
450 };
451
452 {
454 let pos_h = linked_chunk.item_position(|c| *c == 'h').unwrap();
455 linked_chunk.insert_items_at(pos_h, ['j', 'k']).unwrap();
456 tracker.flush_updates(false);
457
458 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
460 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
461 assert_order(&linked_chunk, &tracker, 5);
463 }
464
465 {
467 linked_chunk.replace_gap_at(['l', 'm'], gap_id).unwrap();
468 tracker.flush_updates(false);
469
470 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
472 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
473 assert_order(&linked_chunk, &tracker, 5);
475 }
476
477 {
479 let j_pos = linked_chunk.item_position(|c| *c == 'j').unwrap();
480 linked_chunk.remove_item_at(j_pos).unwrap();
481 tracker.flush_updates(false);
482
483 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
485 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
486 assert_order(&linked_chunk, &tracker, 5);
488 }
489
490 {
492 let k_pos = linked_chunk.item_position(|c| *c == 'k').unwrap();
493 linked_chunk.replace_item_at(k_pos, 'K').unwrap();
494 tracker.flush_updates(false);
495
496 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
498 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
499 assert_order(&linked_chunk, &tracker, 5);
501 }
502
503 {
505 linked_chunk.clear();
506 tracker.flush_updates(false);
507 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 0)), None);
508 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), None);
509 }
510 }
511
512 #[async_test]
513 async fn test_out_of_band_updates() {
514 let db_metadata = vec![
518 ChunkMetadata {
520 previous: None,
521 identifier: ChunkIdentifier(0),
522 next: Some(ChunkIdentifier(1)),
523 num_items: 2,
524 },
525 ChunkMetadata {
527 previous: Some(ChunkIdentifier(0)),
528 identifier: ChunkIdentifier(1),
529 next: Some(ChunkIdentifier(2)),
530 num_items: 0,
531 },
532 ChunkMetadata {
534 previous: Some(ChunkIdentifier(1)),
535 identifier: ChunkIdentifier(2),
536 next: Some(ChunkIdentifier(3)),
537 num_items: 3,
538 },
539 ChunkMetadata {
541 previous: Some(ChunkIdentifier(2)),
542 identifier: ChunkIdentifier(3),
543 next: None,
544 num_items: 1,
545 },
546 ];
547
548 let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
549
550 let mut tracker = linked_chunk.order_tracker(Some(db_metadata)).unwrap();
551
552 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
555 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 1)), Some(3));
557
558 tracker.map_updates(&[Update::RemoveChunk(ChunkIdentifier::new(0))]);
562
563 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), None);
565 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 1)), Some(1));
568 }
569}