1use std::marker::PhantomData;
16
17use tracing::error;
18
19use super::{
20 Chunk, ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, Ends, LinkedChunk,
21 ObservableUpdates, RawChunk, Update,
22};
23
24pub fn from_last_chunk<const CAP: usize, Item, Gap>(
27 chunk: Option<RawChunk<Item, Gap>>,
28 chunk_identifier_generator: ChunkIdentifierGenerator,
29) -> Result<Option<LinkedChunk<CAP, Item, Gap>>, LazyLoaderError> {
30 let Some(mut chunk) = chunk else {
31 return Ok(None);
32 };
33
34 {
36 if let ChunkContent::Items(items) = &chunk.content {
38 if items.len() > CAP {
39 return Err(LazyLoaderError::ChunkTooLarge { id: chunk.identifier });
40 }
41 }
42
43 if chunk.next.is_some() {
45 return Err(LazyLoaderError::ChunkIsNotLast { id: chunk.identifier });
46 }
47 }
48
49 {
51 let lazy_previous = chunk.previous.take();
53
54 let mut chunk_ptr = Chunk::new_leaked(chunk.identifier, chunk.content);
56
57 unsafe { chunk_ptr.as_mut() }.lazy_previous = lazy_previous;
61
62 Ok(Some(LinkedChunk {
63 links: Ends { first: chunk_ptr, last: None },
64 chunk_identifier_generator,
65 updates: Some(ObservableUpdates::new()),
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 if items.len() > CAP {
85 return Err(LazyLoaderError::ChunkTooLarge { id: new_first_chunk.identifier });
86 }
87 }
88
89 if let Some(previous_chunk) = new_first_chunk.previous {
91 if linked_chunk.chunks().any(|chunk| chunk.identifier() == previous_chunk) {
92 return Err(LazyLoaderError::Cycle {
93 new_chunk: new_first_chunk.identifier,
94 with_chunk: previous_chunk,
95 });
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;
158
159 links.first = 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 if items.len() > CAP {
238 return Err(LazyLoaderError::ChunkTooLarge { id: chunk.identifier });
239 }
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;
249
250 let lazy_previous = chunk.previous.take();
252
253 let mut chunk_ptr = Chunk::new_leaked(chunk.identifier, chunk.content);
255
256 unsafe { chunk_ptr.as_mut() }.lazy_previous = lazy_previous;
260
261 linked_chunk.links.replace_with(chunk_ptr);
263
264 if let Some(updates) = linked_chunk.updates.as_mut() {
265 updates.clear_pending();
268 updates.push(Update::Clear);
269
270 emit_new_first_chunk_updates(linked_chunk.links.first_chunk(), updates);
271 }
272
273 Ok(())
274}
275
276#[doc(hidden)]
278pub fn from_all_chunks<const CAP: usize, Item, Gap>(
279 mut chunks: Vec<RawChunk<Item, Gap>>,
280) -> Result<Option<LinkedChunk<CAP, Item, Gap>>, LazyLoaderError>
281where
282 Item: Clone,
283 Gap: Clone,
284{
285 if chunks.is_empty() {
286 return Ok(None);
287 }
288
289 chunks.sort_by(|a, b| b.next.cmp(&a.next));
293
294 let last_chunk = chunks
295 .pop()
296 .expect("`chunks` is supposed to not be empty, we must be able to `pop` an item");
298 let last_chunk_identifier = last_chunk.identifier;
299 let chunk_identifier_generator =
300 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier);
301
302 let Some(mut linked_chunk) = from_last_chunk(Some(last_chunk), chunk_identifier_generator)?
303 else {
304 return Ok(None);
305 };
306
307 let mut next_chunk = last_chunk_identifier;
308
309 while let Some(chunk) = chunks
310 .iter()
311 .position(|chunk| chunk.next == Some(next_chunk))
312 .map(|index| chunks.remove(index))
313 {
314 next_chunk = chunk.identifier;
315 insert_new_first_chunk(&mut linked_chunk, chunk)?;
316 }
317
318 let first_chunk = linked_chunk.links.first_chunk();
319
320 if first_chunk.previous().is_some() || first_chunk.lazy_previous.is_some() {
325 return Err(LazyLoaderError::ChunkIsNotFirst { id: first_chunk.identifier() });
326 }
327
328 if !chunks.is_empty() {
329 return Err(LazyLoaderError::MultipleConnectedComponents);
330 }
331
332 Ok(Some(linked_chunk))
333}
334
335#[derive(thiserror::Error, Debug)]
336pub enum LazyLoaderError {
337 #[error("chunk with id {} has a next chunk, it is supposed to be the last chunk", id.index())]
338 ChunkIsNotLast { id: ChunkIdentifier },
339
340 #[error("chunk with id {} forms a cycle with chunk with id {}", new_chunk.index(), with_chunk.index())]
341 Cycle { new_chunk: ChunkIdentifier, with_chunk: ChunkIdentifier },
342
343 #[error("chunk with id {} is supposed to have a next chunk", id.index())]
344 MissingNextChunk { id: ChunkIdentifier },
345
346 #[error(
347 "chunk with id {} cannot be connected to chunk with id {} because the identifiers do not match",
348 new_chunk.index(),
349 with_chunk.index()
350 )]
351 CannotConnectTwoChunks { new_chunk: ChunkIdentifier, with_chunk: ChunkIdentifier },
352
353 #[error("chunk with id {} is too large", id.index())]
354 ChunkTooLarge { id: ChunkIdentifier },
355
356 #[doc(hidden)]
357 #[error("the last chunk is missing")]
358 MissingLastChunk,
359
360 #[doc(hidden)]
361 #[error("chunk with id {} has a previous chunk, it is supposed to be the first chunk", id.index())]
362 ChunkIsNotFirst { id: ChunkIdentifier },
363
364 #[doc(hidden)]
365 #[error("multiple connected components")]
366 MultipleConnectedComponents,
367}
368
369#[cfg(test)]
370mod tests {
371 use assert_matches::assert_matches;
372
373 use super::{
374 super::Position, from_all_chunks, from_last_chunk, insert_new_first_chunk, replace_with,
375 ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, LazyLoaderError, LinkedChunk,
376 RawChunk, Update,
377 };
378
379 #[test]
380 fn test_from_last_chunk_err_too_much_items() {
381 let last_chunk = RawChunk {
382 previous: None,
383 identifier: ChunkIdentifier::new(0),
384 next: None,
385 content: ChunkContent::Items(vec!['a', 'b', 'c']),
386 };
387 let chunk_identifier_generator =
388 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
389
390 let maybe_linked_chunk =
391 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator);
392
393 assert_matches!(
394 maybe_linked_chunk,
395 Err(LazyLoaderError::ChunkTooLarge { id }) => {
396 assert_eq!(id, 0);
397 }
398 );
399 }
400
401 #[test]
402 fn test_from_last_chunk_err_is_not_last_chunk() {
403 let last_chunk = RawChunk {
404 previous: None,
405 identifier: ChunkIdentifier::new(0),
406 next: Some(ChunkIdentifier::new(42)),
407 content: ChunkContent::Items(vec!['a']),
408 };
409 let chunk_identifier_generator =
410 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
411
412 let maybe_linked_chunk =
413 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator);
414
415 assert_matches!(
416 maybe_linked_chunk,
417 Err(LazyLoaderError::ChunkIsNotLast { id }) => {
418 assert_eq!(id, 0);
419 }
420 );
421 }
422
423 #[test]
424 fn test_from_last_chunk_none() {
425 let chunk_identifier_generator =
426 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
427
428 let maybe_linked_chunk =
429 from_last_chunk::<2, char, ()>(None, chunk_identifier_generator).unwrap();
430
431 assert!(maybe_linked_chunk.is_none());
432 }
433
434 #[test]
435 fn test_from_last_chunk() {
436 let last_chunk = RawChunk {
437 previous: Some(ChunkIdentifier::new(42)),
438 identifier: ChunkIdentifier::new(0),
439 next: None,
440 content: ChunkContent::Items(vec!['a']),
441 };
442 let chunk_identifier_generator =
443 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
444
445 let maybe_linked_chunk =
446 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator).unwrap();
447
448 assert_matches!(maybe_linked_chunk, Some(mut linked_chunk) => {
449 let mut chunks = linked_chunk.chunks();
450
451 assert_matches!(chunks.next(), Some(chunk) => {
452 assert_eq!(chunk.identifier(), 0);
453 assert!(chunk.previous().is_none());
455 });
456 assert!(chunks.next().is_none());
457
458 assert!(linked_chunk.updates().is_some());
460 });
461 }
462
463 #[test]
464 fn test_insert_new_first_chunk_err_too_much_items() {
465 let new_first_chunk = RawChunk {
466 previous: None,
467 identifier: ChunkIdentifier::new(0),
468 next: None,
469 content: ChunkContent::Items(vec!['a', 'b', 'c']),
470 };
471
472 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
473
474 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
475
476 assert_matches!(result, Err(LazyLoaderError::ChunkTooLarge { id }) => {
477 assert_eq!(id, 0);
478 });
479 }
480
481 #[test]
482 fn test_insert_new_first_chunk_err_cycle() {
483 let new_first_chunk = RawChunk {
484 previous: Some(ChunkIdentifier::new(0)),
485 identifier: ChunkIdentifier::new(1),
486 next: Some(ChunkIdentifier::new(0)),
487 content: ChunkContent::Gap(()),
488 };
489
490 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
491 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
492
493 assert_matches!(result, Err(LazyLoaderError::Cycle { new_chunk, with_chunk }) => {
494 assert_eq!(new_chunk, 1);
495 assert_eq!(with_chunk, 0);
496 });
497 }
498
499 #[test]
500 fn test_insert_new_first_chunk_err_missing_next_chunk() {
501 let new_first_chunk = RawChunk {
502 previous: None,
503 identifier: ChunkIdentifier::new(0),
504 next: None,
505 content: ChunkContent::Gap(()),
506 };
507
508 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
509
510 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
511
512 assert_matches!(result, Err(LazyLoaderError::MissingNextChunk { id }) => {
513 assert_eq!(id, 0);
514 });
515 }
516
517 #[test]
518 fn test_insert_new_first_chunk_err_cannot_connect_two_chunks() {
519 let new_first_chunk = RawChunk {
520 previous: None,
521 identifier: ChunkIdentifier::new(1),
522 next: Some(ChunkIdentifier::new(42)),
523 content: ChunkContent::Gap(()),
524 };
525
526 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
527 linked_chunk.push_gap_back(());
528
529 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
530
531 assert_matches!(result, Err(LazyLoaderError::CannotConnectTwoChunks { new_chunk, with_chunk }) => {
532 assert_eq!(new_chunk, 1);
533 assert_eq!(with_chunk, 0);
534 });
535 }
536
537 #[test]
538 fn test_insert_new_first_chunk_err_cannot_connect_two_chunks_before_no_lazy_previous() {
539 let new_first_chunk = RawChunk {
540 previous: None,
541 identifier: ChunkIdentifier::new(1),
542 next: Some(ChunkIdentifier::new(0)),
543 content: ChunkContent::Gap(()),
544 };
545
546 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
547 linked_chunk.push_gap_back(());
548
549 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
550
551 assert_matches!(result, Err(LazyLoaderError::CannotConnectTwoChunks { new_chunk, with_chunk }) => {
552 assert_eq!(new_chunk, 0);
553 assert_eq!(with_chunk, 1);
554 });
555 }
556
557 #[test]
558 fn test_insert_new_first_chunk_gap() {
559 let new_first_chunk = RawChunk {
560 previous: None,
561 identifier: ChunkIdentifier::new(1),
562 next: Some(ChunkIdentifier::new(0)),
563 content: ChunkContent::Gap(()),
564 };
565
566 let mut linked_chunk = LinkedChunk::<5, char, ()>::new_with_update_history();
567 linked_chunk.push_items_back(vec!['a', 'b']);
568 linked_chunk.links.first_chunk_mut().lazy_previous = Some(ChunkIdentifier::new(1));
569
570 let _ = linked_chunk.updates().unwrap().take();
572
573 insert_new_first_chunk(&mut linked_chunk, new_first_chunk).unwrap();
574
575 {
577 let mut chunks = linked_chunk.chunks();
578
579 assert_matches!(chunks.next(), Some(chunk) => {
580 assert_eq!(chunk.identifier(), 1);
581 assert!(chunk.is_gap());
582 });
583 assert_matches!(chunks.next(), Some(chunk) => {
584 assert_eq!(chunk.identifier(), 0);
585 assert!(chunk.is_items());
586 });
587 assert!(chunks.next().is_none());
588 }
589
590 {
592 let mut rchunks = linked_chunk.rchunks();
593
594 assert_eq!(rchunks.next().unwrap().identifier(), 0);
595 assert_eq!(rchunks.next().unwrap().identifier(), 1);
596 assert!(rchunks.next().is_none());
597 }
598
599 {
601 let updates = linked_chunk.updates().unwrap().take();
602
603 assert_eq!(updates.len(), 1);
604 assert_eq!(
605 updates,
606 [Update::NewGapChunk {
607 previous: None,
608 new: ChunkIdentifier::new(1),
609 next: Some(ChunkIdentifier::new(0)),
610 gap: (),
611 }]
612 );
613 }
614 }
615
616 #[test]
617 fn test_insert_new_first_chunk_items() {
618 let new_first_chunk = RawChunk {
619 previous: None,
620 identifier: ChunkIdentifier::new(1),
621 next: Some(ChunkIdentifier::new(0)),
622 content: ChunkContent::Items(vec!['c', 'd']),
623 };
624
625 let mut linked_chunk = LinkedChunk::<5, char, ()>::new_with_update_history();
626 linked_chunk.push_items_back(vec!['a', 'b']);
627 linked_chunk.links.first_chunk_mut().lazy_previous = Some(ChunkIdentifier::new(1));
628
629 let _ = linked_chunk.updates().unwrap().take();
631
632 insert_new_first_chunk(&mut linked_chunk, new_first_chunk).unwrap();
633
634 {
636 let mut chunks = linked_chunk.chunks();
637
638 assert_matches!(chunks.next(), Some(chunk) => {
639 assert_eq!(chunk.identifier(), 1);
640 assert!(chunk.is_items());
641 });
642 assert_matches!(chunks.next(), Some(chunk) => {
643 assert_eq!(chunk.identifier(), 0);
644 assert!(chunk.is_items());
645 });
646 assert!(chunks.next().is_none());
647 }
648
649 {
651 let mut rchunks = linked_chunk.rchunks();
652
653 assert_eq!(rchunks.next().unwrap().identifier(), 0);
654 assert_eq!(rchunks.next().unwrap().identifier(), 1);
655 assert!(rchunks.next().is_none());
656 }
657
658 {
660 let updates = linked_chunk.updates().unwrap().take();
661
662 assert_eq!(updates.len(), 2);
663 assert_eq!(
664 updates,
665 [
666 Update::NewItemsChunk {
667 previous: None,
668 new: ChunkIdentifier::new(1),
669 next: Some(ChunkIdentifier::new(0)),
670 },
671 Update::PushItems {
672 at: Position::new(ChunkIdentifier::new(1), 0),
673 items: vec!['c', 'd']
674 }
675 ]
676 );
677 }
678 }
679
680 #[test]
681 fn test_replace_with_chunk_too_large() {
682 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
684 linked_chunk.push_items_back(vec!['a', 'b']);
685 linked_chunk.push_gap_back(());
686 linked_chunk.push_items_back(vec!['c', 'd']);
687
688 let chunk_identifier_generator = ChunkIdentifierGenerator::new_from_scratch();
690
691 let chunk_id = ChunkIdentifier::new(1);
692 let raw_chunk = RawChunk {
693 previous: Some(ChunkIdentifier::new(0)),
694 identifier: chunk_id,
695 next: None,
696 content: ChunkContent::Items(vec!['e', 'f', 'g', 'h']),
697 };
698
699 let err = replace_with(&mut linked_chunk, Some(raw_chunk), chunk_identifier_generator)
700 .unwrap_err();
701 assert_matches!(err, LazyLoaderError::ChunkTooLarge { id } => {
702 assert_eq!(chunk_id, id);
703 });
704 }
705
706 #[test]
707 fn test_replace_with_next_chunk() {
708 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
710 linked_chunk.push_items_back(vec!['a', 'b']);
711 linked_chunk.push_gap_back(());
712 linked_chunk.push_items_back(vec!['c', 'd']);
713
714 let chunk_identifier_generator = ChunkIdentifierGenerator::new_from_scratch();
716
717 let chunk_id = ChunkIdentifier::new(1);
718 let raw_chunk = RawChunk {
719 previous: Some(ChunkIdentifier::new(0)),
720 identifier: chunk_id,
721 next: Some(ChunkIdentifier::new(2)),
722 content: ChunkContent::Items(vec!['e', 'f']),
723 };
724
725 let err = replace_with(&mut linked_chunk, Some(raw_chunk), chunk_identifier_generator)
726 .unwrap_err();
727 assert_matches!(err, LazyLoaderError::ChunkIsNotLast { id } => {
728 assert_eq!(chunk_id, id);
729 });
730 }
731
732 #[test]
733 fn test_replace_with_empty() {
734 let mut linked_chunk = LinkedChunk::<2, char, ()>::new_with_update_history();
736 linked_chunk.push_items_back(vec!['a', 'b']);
737 linked_chunk.push_gap_back(());
738 linked_chunk.push_items_back(vec!['c', 'd']);
739
740 let _ = linked_chunk.updates().unwrap().take();
742
743 let chunk_identifier_generator =
745 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(
746 ChunkIdentifierGenerator::FIRST_IDENTIFIER,
747 );
748 replace_with(&mut linked_chunk, None, chunk_identifier_generator).unwrap();
749
750 assert!(linked_chunk.updates().is_some());
752
753 let mut it = linked_chunk.chunks();
755
756 assert_matches!(it.next(), Some(chunk) => {
757 assert_eq!(chunk.identifier(), ChunkIdentifier::new(0));
758 assert!(chunk.is_items());
759 assert!(chunk.next().is_none());
760 assert_matches!(chunk.content(), ChunkContent::Items(items) => {
761 assert!(items.is_empty());
762 });
763 });
764
765 assert_matches!(it.next(), None);
767
768 {
770 let updates = linked_chunk.updates().unwrap().take();
771
772 assert_eq!(updates.len(), 2);
773 assert_eq!(
774 updates,
775 [
776 Update::Clear,
777 Update::NewItemsChunk {
778 previous: None,
779 new: ChunkIdentifier::new(0),
780 next: None,
781 },
782 ]
783 );
784 }
785 }
786
787 #[test]
788 fn test_replace_with_non_empty() {
789 let mut linked_chunk = LinkedChunk::<2, char, ()>::new_with_update_history();
791 linked_chunk.push_items_back(vec!['a', 'b']);
792 linked_chunk.push_gap_back(());
793 linked_chunk.push_items_back(vec!['c', 'd']);
794
795 let _ = linked_chunk.updates().unwrap().take();
797
798 let chunk_identifier_generator =
800 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(42));
801
802 let chunk_id = ChunkIdentifier::new(1);
803 let chunk = RawChunk {
804 previous: Some(ChunkIdentifier::new(0)),
805 identifier: chunk_id,
806 next: None,
807 content: ChunkContent::Items(vec!['e', 'f']),
808 };
809 replace_with(&mut linked_chunk, Some(chunk), chunk_identifier_generator).unwrap();
810
811 assert!(linked_chunk.updates().is_some());
813
814 let mut it = linked_chunk.chunks();
815
816 assert_matches!(it.next(), Some(chunk) => {
818 assert_eq!(chunk.identifier(), chunk_id);
819 assert!(chunk.next().is_none());
820 assert_matches!(chunk.content(), ChunkContent::Items(items) => {
821 assert_eq!(*items, vec!['e', 'f']);
822 });
823 });
824
825 assert!(it.next().is_none());
827
828 {
830 let updates = linked_chunk.updates().unwrap().take();
831
832 assert_eq!(updates.len(), 3);
833 assert_eq!(
834 updates,
835 [
836 Update::Clear,
837 Update::NewItemsChunk {
838 previous: Some(ChunkIdentifier::new(0)),
839 new: chunk_id,
840 next: None,
841 },
842 Update::PushItems {
843 at: Position::new(ChunkIdentifier::new(1), 0),
844 items: vec!['e', 'f']
845 }
846 ]
847 );
848 }
849 }
850
851 #[test]
852 fn test_from_all_chunks_empty() {
853 let lc = from_all_chunks::<3, char, ()>(vec![]).unwrap();
855 assert!(lc.is_none());
856 }
857
858 #[test]
859 fn test_from_all_chunks_success() {
860 let cid0 = ChunkIdentifier::new(0);
861 let cid1 = ChunkIdentifier::new(1);
862 let cid3 = ChunkIdentifier::new(3);
865
866 let chunks = vec![
872 RawChunk {
874 previous: None,
875 identifier: cid0,
876 next: Some(cid1),
877 content: ChunkContent::Items(vec!['a', 'b', 'c']),
878 },
879 RawChunk {
881 previous: Some(cid1),
882 identifier: cid3,
883 next: None,
884 content: ChunkContent::Items(vec!['d', 'e']),
885 },
886 RawChunk {
888 previous: Some(cid0),
889 identifier: cid1,
890 next: Some(cid3),
891 content: ChunkContent::Gap('g'),
892 },
893 ];
894
895 let mut lc = from_all_chunks::<3, _, _>(chunks)
896 .expect("building works")
897 .expect("returns a non-empty linked chunk");
898
899 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e']);
901
902 let mut chunks = lc.chunks();
904 let first_chunk = chunks.next().unwrap();
905 {
906 assert!(first_chunk.previous().is_none());
907 assert_eq!(first_chunk.identifier(), cid0);
908 }
909
910 let second_chunk = chunks.next().unwrap();
912 {
913 assert_eq!(second_chunk.identifier(), first_chunk.next().unwrap().identifier());
914 assert_eq!(second_chunk.previous().unwrap().identifier(), first_chunk.identifier());
915 assert_eq!(second_chunk.identifier(), cid1);
916 }
917
918 let third_chunk = chunks.next().unwrap();
920 {
921 assert_eq!(third_chunk.identifier(), second_chunk.next().unwrap().identifier());
922 assert_eq!(third_chunk.previous().unwrap().identifier(), second_chunk.identifier());
923 assert!(third_chunk.next().is_none());
924 assert_eq!(third_chunk.identifier(), cid3);
925 }
926
927 assert!(chunks.next().is_none());
929
930 assert_eq!(lc.num_items(), 5);
932
933 lc.push_gap_back('h');
936
937 let last_chunk = lc.chunks().last().unwrap();
938 assert_eq!(last_chunk.identifier(), ChunkIdentifier::new(cid3.index() + 1));
939 }
940
941 #[test]
942 fn test_from_all_chunks_chunk_too_large() {
943 let cid0 = ChunkIdentifier::new(0);
944
945 let res = from_all_chunks::<3, char, ()>(vec![RawChunk {
948 previous: None,
949 identifier: cid0,
950 next: None,
951 content: ChunkContent::Items(vec!['a', 'b', 'c', 'd']),
952 }]);
953 assert_matches!(res, Err(LazyLoaderError::ChunkTooLarge { id }) => {
954 assert_eq!(id, cid0);
955 });
956 }
957
958 #[test]
959 fn test_from_all_chunks_missing_first_chunk() {
960 let cid0 = ChunkIdentifier::new(0);
961 let cid1 = ChunkIdentifier::new(1);
962 let cid2 = ChunkIdentifier::new(2);
963
964 let res = from_all_chunks::<3, char, char>(vec![
965 RawChunk {
966 previous: Some(cid2),
967 identifier: cid0,
968 next: Some(cid1),
969 content: ChunkContent::Gap('g'),
970 },
971 RawChunk {
972 previous: Some(cid0),
973 identifier: cid1,
974 next: None,
975 content: ChunkContent::Items(vec!['a', 'b', 'c']),
976 },
977 ]);
978 assert_matches!(res, Err(LazyLoaderError::ChunkIsNotFirst { id }) => {
979 assert_eq!(id, cid0);
980 });
981 }
982
983 #[test]
984 fn test_from_all_chunks_multiple_first_chunks() {
985 let cid0 = ChunkIdentifier::new(0);
986 let cid1 = ChunkIdentifier::new(1);
987
988 let res = from_all_chunks::<3, char, char>(vec![
989 RawChunk {
990 previous: None,
991 identifier: cid0,
992 next: None,
993 content: ChunkContent::Gap('g'),
994 },
995 RawChunk {
997 previous: None,
998 identifier: cid1,
999 next: None,
1000 content: ChunkContent::Gap('G'),
1001 },
1002 ]);
1003
1004 assert_matches!(res, Err(LazyLoaderError::MultipleConnectedComponents));
1005 }
1006
1007 #[test]
1008 fn test_from_all_chunks_cycle() {
1009 let cid0 = ChunkIdentifier::new(0);
1010 let cid1 = ChunkIdentifier::new(1);
1011
1012 let res = from_all_chunks::<3, char, char>(vec![
1013 RawChunk {
1014 previous: None,
1015 identifier: cid0,
1016 next: None,
1017 content: ChunkContent::Gap('g'),
1018 },
1019 RawChunk {
1020 previous: Some(cid0),
1021 identifier: cid1,
1022 next: Some(cid0),
1023 content: ChunkContent::Gap('G'),
1024 },
1025 ]);
1026
1027 assert_matches!(res, Err(LazyLoaderError::Cycle { new_chunk, with_chunk }) => {
1028 assert_eq!(new_chunk, cid1);
1029 assert_eq!(with_chunk, cid0);
1030 });
1031 }
1032
1033 #[test]
1034 fn test_from_all_chunks_multiple_connected_components() {
1035 let cid0 = ChunkIdentifier::new(0);
1036 let cid1 = ChunkIdentifier::new(1);
1037 let cid2 = ChunkIdentifier::new(2);
1038
1039 let res = from_all_chunks::<3, char, char>(vec![
1040 RawChunk {
1042 previous: None,
1043 identifier: cid0,
1044 next: Some(cid1),
1045 content: ChunkContent::Gap('g'),
1046 },
1047 RawChunk {
1048 previous: Some(cid0),
1049 identifier: cid1,
1050 next: None,
1051 content: ChunkContent::Gap('G'),
1052 },
1053 RawChunk {
1055 previous: None,
1056 identifier: cid2,
1057 next: None,
1058 content: ChunkContent::Gap('h'),
1059 },
1060 ]);
1061
1062 assert_matches!(res, Err(LazyLoaderError::MultipleConnectedComponents));
1063 }
1064}