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