1use std::marker::PhantomData;
16
17use super::{
18 Chunk, ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, Ends, LinkedChunk,
19 ObservableUpdates, RawChunk, Update,
20};
21
22pub fn from_last_chunk<const CAP: usize, Item, Gap>(
25 chunk: Option<RawChunk<Item, Gap>>,
26 chunk_identifier_generator: ChunkIdentifierGenerator,
27) -> Result<Option<LinkedChunk<CAP, Item, Gap>>, LazyLoaderError> {
28 let Some(mut chunk) = chunk else {
29 return Ok(None);
30 };
31
32 {
34 if let ChunkContent::Items(items) = &chunk.content
36 && items.len() > CAP
37 {
38 return Err(LazyLoaderError::ChunkTooLarge { id: chunk.identifier });
39 }
40
41 if chunk.next.is_some() {
43 return Err(LazyLoaderError::ChunkIsNotLast { id: chunk.identifier });
44 }
45 }
46
47 {
49 let lazy_previous = chunk.previous.take();
51
52 let mut chunk_ptr = Chunk::new_leaked(chunk.identifier, chunk.content);
54
55 unsafe { chunk_ptr.as_mut() }.lazy_previous = lazy_previous;
59
60 Ok(Some(LinkedChunk {
61 links: Ends { first: chunk_ptr, last: None },
62 chunk_identifier_generator,
63 updates: Some(ObservableUpdates::new()),
64 marker: PhantomData,
65 }))
66 }
67}
68
69pub fn insert_new_first_chunk<const CAP: usize, Item, Gap>(
71 linked_chunk: &mut LinkedChunk<CAP, Item, Gap>,
72 mut new_first_chunk: RawChunk<Item, Gap>,
73) -> Result<(), LazyLoaderError>
74where
75 Item: Clone,
76 Gap: Clone,
77{
78 {
80 if let ChunkContent::Items(items) = &new_first_chunk.content
82 && items.len() > CAP
83 {
84 return Err(LazyLoaderError::ChunkTooLarge { id: new_first_chunk.identifier });
85 }
86
87 if let Some(previous_chunk) = new_first_chunk.previous
89 && linked_chunk.chunks().any(|chunk| chunk.identifier() == previous_chunk)
90 {
91 return Err(LazyLoaderError::Cycle {
92 new_chunk: new_first_chunk.identifier,
93 with_chunk: previous_chunk,
94 });
95 }
96
97 let first_chunk = linked_chunk.links.first_chunk();
98 let expected_next_chunk = first_chunk.identifier();
99
100 let Some(next_chunk) = new_first_chunk.next else {
102 return Err(LazyLoaderError::MissingNextChunk { id: new_first_chunk.identifier });
103 };
104
105 if next_chunk != expected_next_chunk {
107 return Err(LazyLoaderError::CannotConnectTwoChunks {
108 new_chunk: new_first_chunk.identifier,
109 with_chunk: expected_next_chunk,
110 });
111 }
112
113 if first_chunk.lazy_previous() != Some(new_first_chunk.identifier) {
116 return Err(LazyLoaderError::CannotConnectTwoChunks {
117 new_chunk: first_chunk.identifier,
118 with_chunk: new_first_chunk.identifier,
119 });
120 }
121
122 }
124
125 {
127 let lazy_previous = new_first_chunk.previous.take();
129 let mut new_first_chunk =
130 Chunk::new_leaked(new_first_chunk.identifier, new_first_chunk.content);
131
132 let links = &mut linked_chunk.links;
133
134 {
136 let first_chunk = links.first_chunk_mut();
137
138 debug_assert!(
139 first_chunk.previous.is_none(),
140 "The first chunk is not supposed to have a previous chunk"
141 );
142
143 first_chunk.lazy_previous = None;
145 unsafe { new_first_chunk.as_mut() }.lazy_previous = lazy_previous;
146
147 first_chunk.previous = Some(new_first_chunk);
150 }
151
152 {
154 let old_first_chunk = links.first;
156
157 links.first = new_first_chunk;
159
160 links.first_chunk_mut().next = Some(old_first_chunk);
163
164 debug_assert!(
165 links.first_chunk().previous.is_none(),
166 "The new first chunk is not supposed to have a previous chunk"
167 );
168
169 if links.last.is_none() {
173 links.last = Some(old_first_chunk);
174 }
175 }
176 }
177
178 if let Some(updates) = linked_chunk.updates.as_mut() {
180 let first_chunk = linked_chunk.links.first_chunk();
181 emit_new_first_chunk_updates(first_chunk, updates);
182 }
183
184 Ok(())
185}
186
187fn emit_new_first_chunk_updates<const CAP: usize, Item, Gap>(
190 chunk: &Chunk<CAP, Item, Gap>,
191 updates: &mut ObservableUpdates<Item, Gap>,
192) where
193 Item: Clone,
194 Gap: Clone,
195{
196 let previous = chunk.previous().map(Chunk::identifier).or(chunk.lazy_previous);
197 let new = chunk.identifier();
198 let next = chunk.next().map(Chunk::identifier);
199
200 match chunk.content() {
201 ChunkContent::Gap(gap) => {
202 updates.push(Update::NewGapChunk { previous, new, next, gap: gap.clone() });
203 }
204 ChunkContent::Items(items) => {
205 updates.push(Update::NewItemsChunk { previous, new, next });
206 updates.push(Update::PushItems { at: chunk.first_position(), items: items.clone() });
207 }
208 }
209}
210
211pub fn replace_with<const CAP: usize, Item, Gap>(
216 linked_chunk: &mut LinkedChunk<CAP, Item, Gap>,
217 chunk: Option<RawChunk<Item, Gap>>,
218 chunk_identifier_generator: ChunkIdentifierGenerator,
219) -> Result<(), LazyLoaderError>
220where
221 Item: Clone,
222 Gap: Clone,
223{
224 let Some(mut chunk) = chunk else {
225 linked_chunk.clear();
229 return Ok(());
230 };
231
232 if let ChunkContent::Items(items) = &chunk.content
235 && items.len() > CAP
236 {
237 return Err(LazyLoaderError::ChunkTooLarge { id: chunk.identifier });
238 }
239
240 if chunk.next.is_some() {
242 return Err(LazyLoaderError::ChunkIsNotLast { id: chunk.identifier });
243 }
244
245 linked_chunk.chunk_identifier_generator = chunk_identifier_generator;
247
248 let lazy_previous = chunk.previous.take();
250
251 let mut chunk_ptr = Chunk::new_leaked(chunk.identifier, chunk.content);
253
254 unsafe { chunk_ptr.as_mut() }.lazy_previous = lazy_previous;
258
259 linked_chunk.links.replace_with(chunk_ptr);
261
262 if let Some(updates) = linked_chunk.updates.as_mut() {
263 updates.clear_pending();
266 updates.push(Update::Clear);
267
268 emit_new_first_chunk_updates(linked_chunk.links.first_chunk(), updates);
269 }
270
271 Ok(())
272}
273
274#[doc(hidden)]
276pub fn from_all_chunks<const CAP: usize, Item, Gap>(
277 mut chunks: Vec<RawChunk<Item, Gap>>,
278) -> Result<Option<LinkedChunk<CAP, Item, Gap>>, LazyLoaderError>
279where
280 Item: Clone,
281 Gap: Clone,
282{
283 if chunks.is_empty() {
284 return Ok(None);
285 }
286
287 chunks.sort_by(|a, b| b.next.cmp(&a.next));
291
292 let last_chunk = chunks
293 .pop()
294 .expect("`chunks` is supposed to not be empty, we must be able to `pop` an item");
296 let last_chunk_identifier = last_chunk.identifier;
297 let chunk_identifier_generator =
298 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier);
299
300 let Some(mut linked_chunk) = from_last_chunk(Some(last_chunk), chunk_identifier_generator)?
301 else {
302 return Ok(None);
303 };
304
305 let mut next_chunk = last_chunk_identifier;
306
307 while let Some(chunk) = chunks
308 .iter()
309 .position(|chunk| chunk.next == Some(next_chunk))
310 .map(|index| chunks.remove(index))
311 {
312 next_chunk = chunk.identifier;
313 insert_new_first_chunk(&mut linked_chunk, chunk)?;
314 }
315
316 let first_chunk = linked_chunk.links.first_chunk();
317
318 if first_chunk.previous().is_some() || first_chunk.lazy_previous.is_some() {
323 return Err(LazyLoaderError::ChunkIsNotFirst { id: first_chunk.identifier() });
324 }
325
326 if !chunks.is_empty() {
327 return Err(LazyLoaderError::MultipleConnectedComponents);
328 }
329
330 Ok(Some(linked_chunk))
331}
332
333#[derive(thiserror::Error, Debug)]
334pub enum LazyLoaderError {
335 #[error("chunk with id {} has a next chunk, it is supposed to be the last chunk", id.index())]
336 ChunkIsNotLast { id: ChunkIdentifier },
337
338 #[error("chunk with id {} forms a cycle with chunk with id {}", new_chunk.index(), with_chunk.index())]
339 Cycle { new_chunk: ChunkIdentifier, with_chunk: ChunkIdentifier },
340
341 #[error("chunk with id {} is supposed to have a next chunk", id.index())]
342 MissingNextChunk { id: ChunkIdentifier },
343
344 #[error(
345 "chunk with id {} cannot be connected to chunk with id {} because the identifiers do not match",
346 new_chunk.index(),
347 with_chunk.index()
348 )]
349 CannotConnectTwoChunks { new_chunk: ChunkIdentifier, with_chunk: ChunkIdentifier },
350
351 #[error("chunk with id {} is too large", id.index())]
352 ChunkTooLarge { id: ChunkIdentifier },
353
354 #[doc(hidden)]
355 #[error("the last chunk is missing")]
356 MissingLastChunk,
357
358 #[doc(hidden)]
359 #[error("chunk with id {} has a previous chunk, it is supposed to be the first chunk", id.index())]
360 ChunkIsNotFirst { id: ChunkIdentifier },
361
362 #[doc(hidden)]
363 #[error("multiple connected components")]
364 MultipleConnectedComponents,
365}
366
367#[cfg(test)]
368mod tests {
369 use assert_matches::assert_matches;
370
371 use super::{
372 super::Position, ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, LazyLoaderError,
373 LinkedChunk, RawChunk, Update, from_all_chunks, from_last_chunk, insert_new_first_chunk,
374 replace_with,
375 };
376
377 #[test]
378 fn test_from_last_chunk_err_too_much_items() {
379 let last_chunk = RawChunk {
380 previous: None,
381 identifier: ChunkIdentifier::new(0),
382 next: None,
383 content: ChunkContent::Items(vec!['a', 'b', 'c']),
384 };
385 let chunk_identifier_generator =
386 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
387
388 let maybe_linked_chunk =
389 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator);
390
391 assert_matches!(
392 maybe_linked_chunk,
393 Err(LazyLoaderError::ChunkTooLarge { id }) => {
394 assert_eq!(id, 0);
395 }
396 );
397 }
398
399 #[test]
400 fn test_from_last_chunk_err_is_not_last_chunk() {
401 let last_chunk = RawChunk {
402 previous: None,
403 identifier: ChunkIdentifier::new(0),
404 next: Some(ChunkIdentifier::new(42)),
405 content: ChunkContent::Items(vec!['a']),
406 };
407 let chunk_identifier_generator =
408 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
409
410 let maybe_linked_chunk =
411 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator);
412
413 assert_matches!(
414 maybe_linked_chunk,
415 Err(LazyLoaderError::ChunkIsNotLast { id }) => {
416 assert_eq!(id, 0);
417 }
418 );
419 }
420
421 #[test]
422 fn test_from_last_chunk_none() {
423 let chunk_identifier_generator =
424 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
425
426 let maybe_linked_chunk =
427 from_last_chunk::<2, char, ()>(None, chunk_identifier_generator).unwrap();
428
429 assert!(maybe_linked_chunk.is_none());
430 }
431
432 #[test]
433 fn test_from_last_chunk() {
434 let last_chunk = RawChunk {
435 previous: Some(ChunkIdentifier::new(42)),
436 identifier: ChunkIdentifier::new(0),
437 next: None,
438 content: ChunkContent::Items(vec!['a']),
439 };
440 let chunk_identifier_generator =
441 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
442
443 let maybe_linked_chunk =
444 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator).unwrap();
445
446 assert_matches!(maybe_linked_chunk, Some(mut linked_chunk) => {
447 let mut chunks = linked_chunk.chunks();
448
449 assert_matches!(chunks.next(), Some(chunk) => {
450 assert_eq!(chunk.identifier(), 0);
451 assert!(chunk.previous().is_none());
453 });
454 assert!(chunks.next().is_none());
455
456 assert!(linked_chunk.updates().is_some());
458 });
459 }
460
461 #[test]
462 fn test_insert_new_first_chunk_err_too_much_items() {
463 let new_first_chunk = RawChunk {
464 previous: None,
465 identifier: ChunkIdentifier::new(0),
466 next: None,
467 content: ChunkContent::Items(vec!['a', 'b', 'c']),
468 };
469
470 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
471
472 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
473
474 assert_matches!(result, Err(LazyLoaderError::ChunkTooLarge { id }) => {
475 assert_eq!(id, 0);
476 });
477 }
478
479 #[test]
480 fn test_insert_new_first_chunk_err_cycle() {
481 let new_first_chunk = RawChunk {
482 previous: Some(ChunkIdentifier::new(0)),
483 identifier: ChunkIdentifier::new(1),
484 next: Some(ChunkIdentifier::new(0)),
485 content: ChunkContent::Gap(()),
486 };
487
488 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
489 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
490
491 assert_matches!(result, Err(LazyLoaderError::Cycle { new_chunk, with_chunk }) => {
492 assert_eq!(new_chunk, 1);
493 assert_eq!(with_chunk, 0);
494 });
495 }
496
497 #[test]
498 fn test_insert_new_first_chunk_err_missing_next_chunk() {
499 let new_first_chunk = RawChunk {
500 previous: None,
501 identifier: ChunkIdentifier::new(0),
502 next: None,
503 content: ChunkContent::Gap(()),
504 };
505
506 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
507
508 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
509
510 assert_matches!(result, Err(LazyLoaderError::MissingNextChunk { id }) => {
511 assert_eq!(id, 0);
512 });
513 }
514
515 #[test]
516 fn test_insert_new_first_chunk_err_cannot_connect_two_chunks() {
517 let new_first_chunk = RawChunk {
518 previous: None,
519 identifier: ChunkIdentifier::new(1),
520 next: Some(ChunkIdentifier::new(42)),
521 content: ChunkContent::Gap(()),
522 };
523
524 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
525 linked_chunk.push_gap_back(());
526
527 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
528
529 assert_matches!(result, Err(LazyLoaderError::CannotConnectTwoChunks { new_chunk, with_chunk }) => {
530 assert_eq!(new_chunk, 1);
531 assert_eq!(with_chunk, 0);
532 });
533 }
534
535 #[test]
536 fn test_insert_new_first_chunk_err_cannot_connect_two_chunks_before_no_lazy_previous() {
537 let new_first_chunk = RawChunk {
538 previous: None,
539 identifier: ChunkIdentifier::new(1),
540 next: Some(ChunkIdentifier::new(0)),
541 content: ChunkContent::Gap(()),
542 };
543
544 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
545 linked_chunk.push_gap_back(());
546
547 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
548
549 assert_matches!(result, Err(LazyLoaderError::CannotConnectTwoChunks { new_chunk, with_chunk }) => {
550 assert_eq!(new_chunk, 0);
551 assert_eq!(with_chunk, 1);
552 });
553 }
554
555 #[test]
556 fn test_insert_new_first_chunk_gap() {
557 let new_first_chunk = RawChunk {
558 previous: None,
559 identifier: ChunkIdentifier::new(1),
560 next: Some(ChunkIdentifier::new(0)),
561 content: ChunkContent::Gap(()),
562 };
563
564 let mut linked_chunk = LinkedChunk::<5, char, ()>::new_with_update_history();
565 linked_chunk.push_items_back(vec!['a', 'b']);
566 linked_chunk.links.first_chunk_mut().lazy_previous = Some(ChunkIdentifier::new(1));
567
568 let _ = linked_chunk.updates().unwrap().take();
570
571 insert_new_first_chunk(&mut linked_chunk, new_first_chunk).unwrap();
572
573 {
575 let mut chunks = linked_chunk.chunks();
576
577 assert_matches!(chunks.next(), Some(chunk) => {
578 assert_eq!(chunk.identifier(), 1);
579 assert!(chunk.is_gap());
580 });
581 assert_matches!(chunks.next(), Some(chunk) => {
582 assert_eq!(chunk.identifier(), 0);
583 assert!(chunk.is_items());
584 });
585 assert!(chunks.next().is_none());
586 }
587
588 {
590 let mut rchunks = linked_chunk.rchunks();
591
592 assert_eq!(rchunks.next().unwrap().identifier(), 0);
593 assert_eq!(rchunks.next().unwrap().identifier(), 1);
594 assert!(rchunks.next().is_none());
595 }
596
597 {
599 let updates = linked_chunk.updates().unwrap().take();
600
601 assert_eq!(updates.len(), 1);
602 assert_eq!(
603 updates,
604 [Update::NewGapChunk {
605 previous: None,
606 new: ChunkIdentifier::new(1),
607 next: Some(ChunkIdentifier::new(0)),
608 gap: (),
609 }]
610 );
611 }
612 }
613
614 #[test]
615 fn test_insert_new_first_chunk_items() {
616 let new_first_chunk = RawChunk {
617 previous: None,
618 identifier: ChunkIdentifier::new(1),
619 next: Some(ChunkIdentifier::new(0)),
620 content: ChunkContent::Items(vec!['c', 'd']),
621 };
622
623 let mut linked_chunk = LinkedChunk::<5, char, ()>::new_with_update_history();
624 linked_chunk.push_items_back(vec!['a', 'b']);
625 linked_chunk.links.first_chunk_mut().lazy_previous = Some(ChunkIdentifier::new(1));
626
627 let _ = linked_chunk.updates().unwrap().take();
629
630 insert_new_first_chunk(&mut linked_chunk, new_first_chunk).unwrap();
631
632 {
634 let mut chunks = linked_chunk.chunks();
635
636 assert_matches!(chunks.next(), Some(chunk) => {
637 assert_eq!(chunk.identifier(), 1);
638 assert!(chunk.is_items());
639 });
640 assert_matches!(chunks.next(), Some(chunk) => {
641 assert_eq!(chunk.identifier(), 0);
642 assert!(chunk.is_items());
643 });
644 assert!(chunks.next().is_none());
645 }
646
647 {
649 let mut rchunks = linked_chunk.rchunks();
650
651 assert_eq!(rchunks.next().unwrap().identifier(), 0);
652 assert_eq!(rchunks.next().unwrap().identifier(), 1);
653 assert!(rchunks.next().is_none());
654 }
655
656 {
658 let updates = linked_chunk.updates().unwrap().take();
659
660 assert_eq!(updates.len(), 2);
661 assert_eq!(
662 updates,
663 [
664 Update::NewItemsChunk {
665 previous: None,
666 new: ChunkIdentifier::new(1),
667 next: Some(ChunkIdentifier::new(0)),
668 },
669 Update::PushItems {
670 at: Position::new(ChunkIdentifier::new(1), 0),
671 items: vec!['c', 'd']
672 }
673 ]
674 );
675 }
676 }
677
678 #[test]
679 fn test_replace_with_chunk_too_large() {
680 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
682 linked_chunk.push_items_back(vec!['a', 'b']);
683 linked_chunk.push_gap_back(());
684 linked_chunk.push_items_back(vec!['c', 'd']);
685
686 let chunk_identifier_generator = ChunkIdentifierGenerator::new_from_scratch();
688
689 let chunk_id = ChunkIdentifier::new(1);
690 let raw_chunk = RawChunk {
691 previous: Some(ChunkIdentifier::new(0)),
692 identifier: chunk_id,
693 next: None,
694 content: ChunkContent::Items(vec!['e', 'f', 'g', 'h']),
695 };
696
697 let err = replace_with(&mut linked_chunk, Some(raw_chunk), chunk_identifier_generator)
698 .unwrap_err();
699 assert_matches!(err, LazyLoaderError::ChunkTooLarge { id } => {
700 assert_eq!(chunk_id, id);
701 });
702 }
703
704 #[test]
705 fn test_replace_with_next_chunk() {
706 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
708 linked_chunk.push_items_back(vec!['a', 'b']);
709 linked_chunk.push_gap_back(());
710 linked_chunk.push_items_back(vec!['c', 'd']);
711
712 let chunk_identifier_generator = ChunkIdentifierGenerator::new_from_scratch();
714
715 let chunk_id = ChunkIdentifier::new(1);
716 let raw_chunk = RawChunk {
717 previous: Some(ChunkIdentifier::new(0)),
718 identifier: chunk_id,
719 next: Some(ChunkIdentifier::new(2)),
720 content: ChunkContent::Items(vec!['e', 'f']),
721 };
722
723 let err = replace_with(&mut linked_chunk, Some(raw_chunk), chunk_identifier_generator)
724 .unwrap_err();
725 assert_matches!(err, LazyLoaderError::ChunkIsNotLast { id } => {
726 assert_eq!(chunk_id, id);
727 });
728 }
729
730 #[test]
731 fn test_replace_with_empty() {
732 let mut linked_chunk = LinkedChunk::<2, char, ()>::new_with_update_history();
734 linked_chunk.push_items_back(vec!['a', 'b']);
735 linked_chunk.push_gap_back(());
736 linked_chunk.push_items_back(vec!['c', 'd']);
737
738 let _ = linked_chunk.updates().unwrap().take();
740
741 let chunk_identifier_generator =
743 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(
744 ChunkIdentifierGenerator::FIRST_IDENTIFIER,
745 );
746 replace_with(&mut linked_chunk, None, chunk_identifier_generator).unwrap();
747
748 assert!(linked_chunk.updates().is_some());
750
751 let mut it = linked_chunk.chunks();
753
754 assert_matches!(it.next(), Some(chunk) => {
755 assert_eq!(chunk.identifier(), ChunkIdentifier::new(0));
756 assert!(chunk.is_items());
757 assert!(chunk.next().is_none());
758 assert_matches!(chunk.content(), ChunkContent::Items(items) => {
759 assert!(items.is_empty());
760 });
761 });
762
763 assert_matches!(it.next(), None);
765
766 {
768 let updates = linked_chunk.updates().unwrap().take();
769
770 assert_eq!(updates.len(), 2);
771 assert_eq!(
772 updates,
773 [
774 Update::Clear,
775 Update::NewItemsChunk {
776 previous: None,
777 new: ChunkIdentifier::new(0),
778 next: None,
779 },
780 ]
781 );
782 }
783 }
784
785 #[test]
786 fn test_replace_with_non_empty() {
787 let mut linked_chunk = LinkedChunk::<2, char, ()>::new_with_update_history();
789 linked_chunk.push_items_back(vec!['a', 'b']);
790 linked_chunk.push_gap_back(());
791 linked_chunk.push_items_back(vec!['c', 'd']);
792
793 let _ = linked_chunk.updates().unwrap().take();
795
796 let chunk_identifier_generator =
798 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(42));
799
800 let chunk_id = ChunkIdentifier::new(1);
801 let chunk = RawChunk {
802 previous: Some(ChunkIdentifier::new(0)),
803 identifier: chunk_id,
804 next: None,
805 content: ChunkContent::Items(vec!['e', 'f']),
806 };
807 replace_with(&mut linked_chunk, Some(chunk), chunk_identifier_generator).unwrap();
808
809 assert!(linked_chunk.updates().is_some());
811
812 let mut it = linked_chunk.chunks();
813
814 assert_matches!(it.next(), Some(chunk) => {
816 assert_eq!(chunk.identifier(), chunk_id);
817 assert!(chunk.next().is_none());
818 assert_matches!(chunk.content(), ChunkContent::Items(items) => {
819 assert_eq!(*items, vec!['e', 'f']);
820 });
821 });
822
823 assert!(it.next().is_none());
825
826 {
828 let updates = linked_chunk.updates().unwrap().take();
829
830 assert_eq!(updates.len(), 3);
831 assert_eq!(
832 updates,
833 [
834 Update::Clear,
835 Update::NewItemsChunk {
836 previous: Some(ChunkIdentifier::new(0)),
837 new: chunk_id,
838 next: None,
839 },
840 Update::PushItems {
841 at: Position::new(ChunkIdentifier::new(1), 0),
842 items: vec!['e', 'f']
843 }
844 ]
845 );
846 }
847 }
848
849 #[test]
850 fn test_from_all_chunks_empty() {
851 let lc = from_all_chunks::<3, char, ()>(vec![]).unwrap();
853 assert!(lc.is_none());
854 }
855
856 #[test]
857 fn test_from_all_chunks_success() {
858 let cid0 = ChunkIdentifier::new(0);
859 let cid1 = ChunkIdentifier::new(1);
860 let cid3 = ChunkIdentifier::new(3);
863
864 let chunks = vec![
870 RawChunk {
872 previous: None,
873 identifier: cid0,
874 next: Some(cid1),
875 content: ChunkContent::Items(vec!['a', 'b', 'c']),
876 },
877 RawChunk {
879 previous: Some(cid1),
880 identifier: cid3,
881 next: None,
882 content: ChunkContent::Items(vec!['d', 'e']),
883 },
884 RawChunk {
886 previous: Some(cid0),
887 identifier: cid1,
888 next: Some(cid3),
889 content: ChunkContent::Gap('g'),
890 },
891 ];
892
893 let mut lc = from_all_chunks::<3, _, _>(chunks)
894 .expect("building works")
895 .expect("returns a non-empty linked chunk");
896
897 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e']);
899
900 let mut chunks = lc.chunks();
902 let first_chunk = chunks.next().unwrap();
903 {
904 assert!(first_chunk.previous().is_none());
905 assert_eq!(first_chunk.identifier(), cid0);
906 }
907
908 let second_chunk = chunks.next().unwrap();
910 {
911 assert_eq!(second_chunk.identifier(), first_chunk.next().unwrap().identifier());
912 assert_eq!(second_chunk.previous().unwrap().identifier(), first_chunk.identifier());
913 assert_eq!(second_chunk.identifier(), cid1);
914 }
915
916 let third_chunk = chunks.next().unwrap();
918 {
919 assert_eq!(third_chunk.identifier(), second_chunk.next().unwrap().identifier());
920 assert_eq!(third_chunk.previous().unwrap().identifier(), second_chunk.identifier());
921 assert!(third_chunk.next().is_none());
922 assert_eq!(third_chunk.identifier(), cid3);
923 }
924
925 assert!(chunks.next().is_none());
927
928 assert_eq!(lc.num_items(), 5);
930
931 lc.push_gap_back('h');
934
935 let last_chunk = lc.chunks().last().unwrap();
936 assert_eq!(last_chunk.identifier(), ChunkIdentifier::new(cid3.index() + 1));
937 }
938
939 #[test]
940 fn test_from_all_chunks_chunk_too_large() {
941 let cid0 = ChunkIdentifier::new(0);
942
943 let res = from_all_chunks::<3, char, ()>(vec![RawChunk {
946 previous: None,
947 identifier: cid0,
948 next: None,
949 content: ChunkContent::Items(vec!['a', 'b', 'c', 'd']),
950 }]);
951 assert_matches!(res, Err(LazyLoaderError::ChunkTooLarge { id }) => {
952 assert_eq!(id, cid0);
953 });
954 }
955
956 #[test]
957 fn test_from_all_chunks_missing_first_chunk() {
958 let cid0 = ChunkIdentifier::new(0);
959 let cid1 = ChunkIdentifier::new(1);
960 let cid2 = ChunkIdentifier::new(2);
961
962 let res = from_all_chunks::<3, char, char>(vec![
963 RawChunk {
964 previous: Some(cid2),
965 identifier: cid0,
966 next: Some(cid1),
967 content: ChunkContent::Gap('g'),
968 },
969 RawChunk {
970 previous: Some(cid0),
971 identifier: cid1,
972 next: None,
973 content: ChunkContent::Items(vec!['a', 'b', 'c']),
974 },
975 ]);
976 assert_matches!(res, Err(LazyLoaderError::ChunkIsNotFirst { id }) => {
977 assert_eq!(id, cid0);
978 });
979 }
980
981 #[test]
982 fn test_from_all_chunks_multiple_first_chunks() {
983 let cid0 = ChunkIdentifier::new(0);
984 let cid1 = ChunkIdentifier::new(1);
985
986 let res = from_all_chunks::<3, char, char>(vec![
987 RawChunk {
988 previous: None,
989 identifier: cid0,
990 next: None,
991 content: ChunkContent::Gap('g'),
992 },
993 RawChunk {
995 previous: None,
996 identifier: cid1,
997 next: None,
998 content: ChunkContent::Gap('G'),
999 },
1000 ]);
1001
1002 assert_matches!(res, Err(LazyLoaderError::MultipleConnectedComponents));
1003 }
1004
1005 #[test]
1006 fn test_from_all_chunks_cycle() {
1007 let cid0 = ChunkIdentifier::new(0);
1008 let cid1 = ChunkIdentifier::new(1);
1009
1010 let res = from_all_chunks::<3, char, char>(vec![
1011 RawChunk {
1012 previous: None,
1013 identifier: cid0,
1014 next: None,
1015 content: ChunkContent::Gap('g'),
1016 },
1017 RawChunk {
1018 previous: Some(cid0),
1019 identifier: cid1,
1020 next: Some(cid0),
1021 content: ChunkContent::Gap('G'),
1022 },
1023 ]);
1024
1025 assert_matches!(res, Err(LazyLoaderError::Cycle { new_chunk, with_chunk }) => {
1026 assert_eq!(new_chunk, cid1);
1027 assert_eq!(with_chunk, cid0);
1028 });
1029 }
1030
1031 #[test]
1032 fn test_from_all_chunks_multiple_connected_components() {
1033 let cid0 = ChunkIdentifier::new(0);
1034 let cid1 = ChunkIdentifier::new(1);
1035 let cid2 = ChunkIdentifier::new(2);
1036
1037 let res = from_all_chunks::<3, char, char>(vec![
1038 RawChunk {
1040 previous: None,
1041 identifier: cid0,
1042 next: Some(cid1),
1043 content: ChunkContent::Gap('g'),
1044 },
1045 RawChunk {
1046 previous: Some(cid0),
1047 identifier: cid1,
1048 next: None,
1049 content: ChunkContent::Gap('G'),
1050 },
1051 RawChunk {
1053 previous: None,
1054 identifier: cid2,
1055 next: None,
1056 content: ChunkContent::Gap('h'),
1057 },
1058 ]);
1059
1060 assert_matches!(res, Err(LazyLoaderError::MultipleConnectedComponents));
1061 }
1062}