1use std::sync::{Arc, RwLock};
16
17use eyeball_im::VectorDiff;
18
19use super::{
20 Position,
21 updates::{ReaderToken, Update, UpdatesInner},
22};
23use crate::linked_chunk::{ChunkMetadata, UpdateToVectorDiff};
24
25#[derive(Debug)]
39pub struct OrderTracker<Item, Gap> {
40 updates: Arc<RwLock<UpdatesInner<Item, Gap>>>,
42
43 token: ReaderToken,
45
46 mapper: UpdateToVectorDiff<Item, NullAccumulator<Item>>,
48}
49
50struct NullAccumulator<Item> {
51 _phantom: std::marker::PhantomData<Item>,
52}
53
54#[cfg(not(tarpaulin_include))]
55impl<Item> std::fmt::Debug for NullAccumulator<Item> {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 f.write_str("NullAccumulator")
58 }
59}
60
61impl<Item> super::UpdatesAccumulator<Item> for NullAccumulator<Item> {
62 fn new(_num_updates_hint: usize) -> Self {
63 Self { _phantom: std::marker::PhantomData }
64 }
65}
66
67impl<Item> Extend<VectorDiff<Item>> for NullAccumulator<Item> {
68 fn extend<T: IntoIterator<Item = VectorDiff<Item>>>(&mut self, _iter: T) {
69 }
71}
72
73impl<Item, Gap> OrderTracker<Item, Gap>
74where
75 Item: Clone,
76{
77 pub(super) fn new(
87 updates: Arc<RwLock<UpdatesInner<Item, Gap>>>,
88 token: ReaderToken,
89 all_chunks_metadata: Vec<ChunkMetadata>,
90 ) -> Self {
91 {
93 let mut updates = updates.write().unwrap();
94 let _ = updates.take_with_token(token);
95 }
96
97 Self { updates, token, mapper: UpdateToVectorDiff::from_metadata(all_chunks_metadata) }
98 }
99
100 pub fn flush_updates(&mut self, inhibit: bool) {
106 if inhibit {
107 let _ = self.updates.write().unwrap().take_with_token(self.token);
109 } else {
110 let mut updater = self.updates.write().unwrap();
112 let updates = updater.take_with_token(self.token);
113 let _ = self.mapper.map(updates);
114 }
115 }
116
117 pub fn map_updates(&mut self, updates: &[Update<Item, Gap>]) {
122 let _ = self.mapper.map(updates);
123 }
124
125 pub fn ordering(&self, event_pos: Position) -> Option<usize> {
136 debug_assert!(self.updates.read().unwrap().is_reader_up_to_date(self.token));
139
140 let mut ordering = 0;
142 for (chunk_id, chunk_length) in &self.mapper.chunks {
143 if *chunk_id == event_pos.chunk_identifier() {
144 let offset_within_chunk = event_pos.index();
145 if offset_within_chunk >= *chunk_length {
146 return None;
148 }
149 return Some(ordering + offset_within_chunk);
152 }
153 ordering += *chunk_length;
156 }
157
158 None
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use assert_matches::assert_matches;
165 use matrix_sdk_test_macros::async_test;
166
167 use crate::linked_chunk::{
168 ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunk,
169 OrderTracker, Position, RawChunk, Update, lazy_loader::from_last_chunk,
170 };
171
172 #[async_test]
173 async fn test_linked_chunk_without_update_history_no_tracking() {
174 let mut linked_chunk = LinkedChunk::<10, char, ()>::new();
175 assert_matches!(linked_chunk.order_tracker(None), None);
176 }
177
178 fn assert_order_fully_loaded(
181 linked_chunk: &LinkedChunk<3, char, ()>,
182 tracker: &OrderTracker<char, ()>,
183 ) {
184 assert_order(linked_chunk, tracker, 0);
185 }
186
187 fn assert_order(
191 linked_chunk: &LinkedChunk<3, char, ()>,
192 tracker: &OrderTracker<char, ()>,
193 offset: usize,
194 ) {
195 for (i, (item_pos, _value)) in linked_chunk.items().enumerate() {
196 assert_eq!(tracker.ordering(item_pos), Some(i + offset));
197 }
198 }
199
200 #[async_test]
201 async fn test_non_lazy_updates() {
202 let mut linked_chunk = LinkedChunk::<3, _, _>::new_with_update_history();
205
206 let mut tracker = linked_chunk.order_tracker(None).unwrap();
207
208 {
212 linked_chunk.push_items_back(['a', 'b', 'c']);
213 tracker.flush_updates(false);
214 assert_order_fully_loaded(&linked_chunk, &tracker);
215 }
216
217 {
219 linked_chunk.push_gap_back(());
220 tracker.flush_updates(false);
221 assert_order_fully_loaded(&linked_chunk, &tracker);
222 }
223
224 {
226 let pos_b = linked_chunk.item_position(|c| *c == 'b').unwrap();
227 linked_chunk.insert_items_at(pos_b, ['d', 'e']).unwrap();
228 tracker.flush_updates(false);
229 assert_order_fully_loaded(&linked_chunk, &tracker);
230 }
231
232 {
234 let c_pos = linked_chunk.item_position(|c| *c == 'c').unwrap();
235 linked_chunk.insert_gap_at((), c_pos).unwrap();
236 tracker.flush_updates(false);
237 assert_order_fully_loaded(&linked_chunk, &tracker);
238 }
239
240 {
242 let last_gap =
243 linked_chunk.rchunks().filter(|c| c.is_gap()).last().unwrap().identifier();
244 linked_chunk.replace_gap_at(['f', 'g'], last_gap).unwrap();
245 tracker.flush_updates(false);
246 assert_order_fully_loaded(&linked_chunk, &tracker);
247 }
248
249 {
251 let a_pos = linked_chunk.item_position(|c| *c == 'd').unwrap();
252 linked_chunk.remove_item_at(a_pos).unwrap();
253 tracker.flush_updates(false);
254 assert_order_fully_loaded(&linked_chunk, &tracker);
255 }
256
257 {
259 let b_pos = linked_chunk.item_position(|c| *c == 'e').unwrap();
260 linked_chunk.replace_item_at(b_pos, 'E').unwrap();
261 tracker.flush_updates(false);
262 assert_order_fully_loaded(&linked_chunk, &tracker);
263 }
264
265 {
267 linked_chunk.clear();
268 tracker.flush_updates(false);
269 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 0)), None);
270 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), None);
271 }
272 }
273
274 #[async_test]
275 async fn test_lazy_loading() {
276 let db_metadata = vec![
280 ChunkMetadata {
282 previous: None,
283 identifier: ChunkIdentifier(0),
284 next: Some(ChunkIdentifier(1)),
285 num_items: 3,
286 },
287 ChunkMetadata {
289 previous: Some(ChunkIdentifier(0)),
290 identifier: ChunkIdentifier(1),
291 next: Some(ChunkIdentifier(2)),
292 num_items: 0,
293 },
294 ChunkMetadata {
296 previous: Some(ChunkIdentifier(1)),
297 identifier: ChunkIdentifier(2),
298 next: Some(ChunkIdentifier(3)),
299 num_items: 3,
300 },
301 ChunkMetadata {
303 previous: Some(ChunkIdentifier(2)),
304 identifier: ChunkIdentifier(3),
305 next: None,
306 num_items: 1,
307 },
308 ];
309
310 let mut linked_chunk = from_last_chunk::<3, _, ()>(
312 Some(RawChunk {
313 content: ChunkContent::Items(vec!['g']),
314 previous: Some(ChunkIdentifier(2)),
315 identifier: ChunkIdentifier(3),
316 next: None,
317 }),
318 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier(3)),
319 )
320 .expect("could recreate the linked chunk")
321 .expect("the linked chunk isn't empty");
322
323 let tracker = linked_chunk.order_tracker(Some(db_metadata)).unwrap();
324
325 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 0)), Some(0));
330 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
332 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 2)), Some(2));
334 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 42)), None);
336
337 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(1), 0)), None);
339 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(1), 42)), None);
340
341 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 0)), Some(3));
343 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 1)), Some(4));
345 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 2)), Some(5));
347 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 3)), None);
350
351 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(6));
353 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 1)), None);
355 }
356
357 #[async_test]
358 async fn test_lazy_updates() {
359 let db_metadata = vec![
363 ChunkMetadata {
365 previous: None,
366 identifier: ChunkIdentifier(0),
367 next: Some(ChunkIdentifier(1)),
368 num_items: 2,
369 },
370 ChunkMetadata {
372 previous: Some(ChunkIdentifier(0)),
373 identifier: ChunkIdentifier(1),
374 next: Some(ChunkIdentifier(2)),
375 num_items: 0,
376 },
377 ChunkMetadata {
379 previous: Some(ChunkIdentifier(1)),
380 identifier: ChunkIdentifier(2),
381 next: Some(ChunkIdentifier(3)),
382 num_items: 3,
383 },
384 ChunkMetadata {
386 previous: Some(ChunkIdentifier(2)),
387 identifier: ChunkIdentifier(3),
388 next: None,
389 num_items: 1,
390 },
391 ];
392
393 let mut linked_chunk = from_last_chunk(
395 Some(RawChunk {
396 content: ChunkContent::Items(vec!['g']),
397 previous: Some(ChunkIdentifier(2)),
398 identifier: ChunkIdentifier(3),
399 next: None,
400 }),
401 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier(3)),
402 )
403 .expect("could recreate the linked chunk")
404 .expect("the linked chunk isn't empty");
405
406 let mut tracker = linked_chunk.order_tracker(Some(db_metadata)).unwrap();
407
408 {
410 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
412 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
414 }
415
416 {
420 linked_chunk.push_items_back(['h', 'i']);
421 tracker.flush_updates(false);
422
423 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
425 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
426 assert_order(&linked_chunk, &tracker, 5);
428 }
429
430 let gap_id = {
432 linked_chunk.push_gap_back(());
433 tracker.flush_updates(false);
434
435 let last_chunk = linked_chunk.rchunks().next().unwrap();
437 assert!(last_chunk.is_gap());
438 assert_eq!(tracker.ordering(Position::new(last_chunk.identifier(), 0)), None);
439 assert_eq!(tracker.ordering(Position::new(last_chunk.identifier(), 42)), None);
440
441 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
443 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
444 assert_order(&linked_chunk, &tracker, 5);
446
447 last_chunk.identifier()
448 };
449
450 {
452 let pos_h = linked_chunk.item_position(|c| *c == 'h').unwrap();
453 linked_chunk.insert_items_at(pos_h, ['j', 'k']).unwrap();
454 tracker.flush_updates(false);
455
456 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
458 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
459 assert_order(&linked_chunk, &tracker, 5);
461 }
462
463 {
465 linked_chunk.replace_gap_at(['l', 'm'], gap_id).unwrap();
466 tracker.flush_updates(false);
467
468 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
470 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
471 assert_order(&linked_chunk, &tracker, 5);
473 }
474
475 {
477 let j_pos = linked_chunk.item_position(|c| *c == 'j').unwrap();
478 linked_chunk.remove_item_at(j_pos).unwrap();
479 tracker.flush_updates(false);
480
481 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
483 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
484 assert_order(&linked_chunk, &tracker, 5);
486 }
487
488 {
490 let k_pos = linked_chunk.item_position(|c| *c == 'k').unwrap();
491 linked_chunk.replace_item_at(k_pos, 'K').unwrap();
492 tracker.flush_updates(false);
493
494 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
496 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), Some(5));
497 assert_order(&linked_chunk, &tracker, 5);
499 }
500
501 {
503 linked_chunk.clear();
504 tracker.flush_updates(false);
505 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 0)), None);
506 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(3), 0)), None);
507 }
508 }
509
510 #[async_test]
511 async fn test_out_of_band_updates() {
512 let db_metadata = vec![
516 ChunkMetadata {
518 previous: None,
519 identifier: ChunkIdentifier(0),
520 next: Some(ChunkIdentifier(1)),
521 num_items: 2,
522 },
523 ChunkMetadata {
525 previous: Some(ChunkIdentifier(0)),
526 identifier: ChunkIdentifier(1),
527 next: Some(ChunkIdentifier(2)),
528 num_items: 0,
529 },
530 ChunkMetadata {
532 previous: Some(ChunkIdentifier(1)),
533 identifier: ChunkIdentifier(2),
534 next: Some(ChunkIdentifier(3)),
535 num_items: 3,
536 },
537 ChunkMetadata {
539 previous: Some(ChunkIdentifier(2)),
540 identifier: ChunkIdentifier(3),
541 next: None,
542 num_items: 1,
543 },
544 ];
545
546 let mut linked_chunk = LinkedChunk::<3, char, ()>::new_with_update_history();
547
548 let mut tracker = linked_chunk.order_tracker(Some(db_metadata)).unwrap();
549
550 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), Some(1));
552 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 1)), Some(3));
554
555 tracker.map_updates(&[Update::RemoveChunk(ChunkIdentifier::new(0))]);
559
560 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(0), 1)), None);
562 assert_eq!(tracker.ordering(Position::new(ChunkIdentifier::new(2), 1)), Some(1));
565 }
566}