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 expected_next_chunk = linked_chunk.links.first_chunk().identifier();
100
101 let Some(next_chunk) = new_first_chunk.next else {
103 return Err(LazyLoaderError::MissingNextChunk { id: new_first_chunk.identifier });
104 };
105
106 if next_chunk != expected_next_chunk {
108 return Err(LazyLoaderError::CannotConnectTwoChunks {
109 new_chunk: new_first_chunk.identifier,
110 with_chunk: expected_next_chunk,
111 });
112 }
113
114 }
116
117 {
119 let lazy_previous = new_first_chunk.previous.take();
121 let mut new_first_chunk =
122 Chunk::new_leaked(new_first_chunk.identifier, new_first_chunk.content);
123
124 let links = &mut linked_chunk.links;
125
126 {
128 let first_chunk = links.first_chunk_mut();
129
130 debug_assert!(
131 first_chunk.previous.is_none(),
132 "The first chunk is not supposed to have a previous chunk"
133 );
134
135 first_chunk.lazy_previous = None;
137 unsafe { new_first_chunk.as_mut() }.lazy_previous = lazy_previous;
138
139 first_chunk.previous = Some(new_first_chunk);
142 }
143
144 {
146 let old_first_chunk = links.first;
148
149 links.first = new_first_chunk;
151
152 links.first_chunk_mut().next = Some(old_first_chunk);
155
156 debug_assert!(
157 links.first_chunk().previous.is_none(),
158 "The new first chunk is not supposed to have a previous chunk"
159 );
160
161 if links.last.is_none() {
165 links.last = Some(old_first_chunk);
166 }
167 }
168 }
169
170 if let Some(updates) = linked_chunk.updates.as_mut() {
172 let first_chunk = linked_chunk.links.first_chunk();
173
174 let previous = first_chunk.previous().map(Chunk::identifier).or(first_chunk.lazy_previous);
175 let new = first_chunk.identifier();
176 let next = first_chunk.next().map(Chunk::identifier);
177
178 match first_chunk.content() {
179 ChunkContent::Gap(gap) => {
180 updates.push(Update::NewGapChunk { previous, new, next, gap: gap.clone() });
181 }
182
183 ChunkContent::Items(items) => {
184 updates.push(Update::NewItemsChunk { previous, new, next });
185 updates.push(Update::PushItems {
186 at: first_chunk.first_position(),
187 items: items.clone(),
188 });
189 }
190 }
191 }
192
193 Ok(())
194}
195
196#[doc(hidden)]
198pub fn from_all_chunks<const CAP: usize, Item, Gap>(
199 mut chunks: Vec<RawChunk<Item, Gap>>,
200) -> Result<Option<LinkedChunk<CAP, Item, Gap>>, LazyLoaderError>
201where
202 Item: Clone,
203 Gap: Clone,
204{
205 if chunks.is_empty() {
206 return Ok(None);
207 }
208
209 chunks.sort_by(|a, b| b.next.cmp(&a.next));
213
214 let last_chunk = chunks
215 .pop()
216 .expect("`chunks` is supposed to not be empty, we must be able to `pop` an item");
218 let last_chunk_identifier = last_chunk.identifier;
219 let chunk_identifier_generator =
220 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(last_chunk_identifier);
221
222 let Some(mut linked_chunk) = from_last_chunk(Some(last_chunk), chunk_identifier_generator)?
223 else {
224 return Ok(None);
225 };
226
227 let mut next_chunk = last_chunk_identifier;
228
229 while let Some(chunk) = chunks
230 .iter()
231 .position(|chunk| chunk.next == Some(next_chunk))
232 .map(|index| chunks.remove(index))
233 {
234 next_chunk = chunk.identifier;
235 insert_new_first_chunk(&mut linked_chunk, chunk)?;
236 }
237
238 let first_chunk = linked_chunk.links.first_chunk();
239
240 if first_chunk.previous().is_some() || first_chunk.lazy_previous.is_some() {
245 return Err(LazyLoaderError::ChunkIsNotFirst { id: first_chunk.identifier() });
246 }
247
248 if !chunks.is_empty() {
249 return Err(LazyLoaderError::MultipleConnectedComponents);
250 }
251
252 Ok(Some(linked_chunk))
253}
254
255#[derive(thiserror::Error, Debug)]
256pub enum LazyLoaderError {
257 #[error("chunk with id {} has a next chunk, it is supposed to be the last chunk", id.index())]
258 ChunkIsNotLast { id: ChunkIdentifier },
259
260 #[error("chunk with id {} forms a cycle with chunk with id {}", new_chunk.index(), with_chunk.index())]
261 Cycle { new_chunk: ChunkIdentifier, with_chunk: ChunkIdentifier },
262
263 #[error("chunk with id {} is supposed to have a next chunk", id.index())]
264 MissingNextChunk { id: ChunkIdentifier },
265
266 #[error(
267 "chunk with id {} cannot be connected to chunk with id {} because the identifiers do not match",
268 new_chunk.index(),
269 with_chunk.index()
270 )]
271 CannotConnectTwoChunks { new_chunk: ChunkIdentifier, with_chunk: ChunkIdentifier },
272
273 #[error("chunk with id {} is too large", id.index())]
274 ChunkTooLarge { id: ChunkIdentifier },
275
276 #[doc(hidden)]
277 #[error("the last chunk is missing")]
278 MissingLastChunk,
279
280 #[doc(hidden)]
281 #[error("chunk with id {} has a previous chunk, it is supposed to be the first chunk", id.index())]
282 ChunkIsNotFirst { id: ChunkIdentifier },
283
284 #[doc(hidden)]
285 #[error("multiple connected components")]
286 MultipleConnectedComponents,
287}
288
289#[cfg(test)]
290mod tests {
291 use assert_matches::assert_matches;
292
293 use super::{
294 super::Position, from_all_chunks, from_last_chunk, insert_new_first_chunk, ChunkContent,
295 ChunkIdentifier, ChunkIdentifierGenerator, LazyLoaderError, LinkedChunk, RawChunk, Update,
296 };
297
298 #[test]
299 fn test_from_last_chunk_err_too_much_items() {
300 let last_chunk = RawChunk {
301 previous: None,
302 identifier: ChunkIdentifier::new(0),
303 next: None,
304 content: ChunkContent::Items(vec!['a', 'b', 'c']),
305 };
306 let chunk_identifier_generator =
307 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
308
309 let maybe_linked_chunk =
310 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator);
311
312 assert_matches!(
313 maybe_linked_chunk,
314 Err(LazyLoaderError::ChunkTooLarge { id }) => {
315 assert_eq!(id, 0);
316 }
317 );
318 }
319
320 #[test]
321 fn test_from_last_chunk_err_is_not_last_chunk() {
322 let last_chunk = RawChunk {
323 previous: None,
324 identifier: ChunkIdentifier::new(0),
325 next: Some(ChunkIdentifier::new(42)),
326 content: ChunkContent::Items(vec!['a']),
327 };
328 let chunk_identifier_generator =
329 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
330
331 let maybe_linked_chunk =
332 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator);
333
334 assert_matches!(
335 maybe_linked_chunk,
336 Err(LazyLoaderError::ChunkIsNotLast { id }) => {
337 assert_eq!(id, 0);
338 }
339 );
340 }
341
342 #[test]
343 fn test_from_last_chunk_none() {
344 let chunk_identifier_generator =
345 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
346
347 let maybe_linked_chunk =
348 from_last_chunk::<2, char, ()>(None, chunk_identifier_generator).unwrap();
349
350 assert!(maybe_linked_chunk.is_none());
351 }
352
353 #[test]
354 fn test_from_last_chunk() {
355 let last_chunk = RawChunk {
356 previous: Some(ChunkIdentifier::new(42)),
357 identifier: ChunkIdentifier::new(0),
358 next: None,
359 content: ChunkContent::Items(vec!['a']),
360 };
361 let chunk_identifier_generator =
362 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(0));
363
364 let maybe_linked_chunk =
365 from_last_chunk::<2, char, ()>(Some(last_chunk), chunk_identifier_generator).unwrap();
366
367 assert_matches!(maybe_linked_chunk, Some(mut linked_chunk) => {
368 let mut chunks = linked_chunk.chunks();
369
370 assert_matches!(chunks.next(), Some(chunk) => {
371 assert_eq!(chunk.identifier(), 0);
372 assert!(chunk.previous().is_none());
374 });
375 assert!(chunks.next().is_none());
376
377 assert!(linked_chunk.updates().is_some());
379 });
380 }
381
382 #[test]
383 fn test_insert_new_first_chunk_err_too_much_items() {
384 let new_first_chunk = RawChunk {
385 previous: None,
386 identifier: ChunkIdentifier::new(0),
387 next: None,
388 content: ChunkContent::Items(vec!['a', 'b', 'c']),
389 };
390
391 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
392
393 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
394
395 assert_matches!(result, Err(LazyLoaderError::ChunkTooLarge { id }) => {
396 assert_eq!(id, 0);
397 });
398 }
399
400 #[test]
401 fn test_insert_new_first_chunk_err_cycle() {
402 let new_first_chunk = RawChunk {
403 previous: Some(ChunkIdentifier::new(0)),
404 identifier: ChunkIdentifier::new(1),
405 next: Some(ChunkIdentifier::new(0)),
406 content: ChunkContent::Gap(()),
407 };
408
409 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
410 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
411
412 assert_matches!(result, Err(LazyLoaderError::Cycle { new_chunk, with_chunk }) => {
413 assert_eq!(new_chunk, 1);
414 assert_eq!(with_chunk, 0);
415 });
416 }
417
418 #[test]
419 fn test_insert_new_first_chunk_err_missing_next_chunk() {
420 let new_first_chunk = RawChunk {
421 previous: None,
422 identifier: ChunkIdentifier::new(0),
423 next: None,
424 content: ChunkContent::Gap(()),
425 };
426
427 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
428
429 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
430
431 assert_matches!(result, Err(LazyLoaderError::MissingNextChunk { id }) => {
432 assert_eq!(id, 0);
433 });
434 }
435
436 #[test]
437 fn test_insert_new_first_chunk_err_cannot_connect_two_chunks() {
438 let new_first_chunk = RawChunk {
439 previous: None,
440 identifier: ChunkIdentifier::new(1),
441 next: Some(ChunkIdentifier::new(42)),
442 content: ChunkContent::Gap(()),
443 };
444
445 let mut linked_chunk = LinkedChunk::<2, char, ()>::new();
446 linked_chunk.push_gap_back(());
447
448 let result = insert_new_first_chunk(&mut linked_chunk, new_first_chunk);
449
450 assert_matches!(result, Err(LazyLoaderError::CannotConnectTwoChunks { new_chunk, with_chunk }) => {
451 assert_eq!(new_chunk, 1);
452 assert_eq!(with_chunk, 0);
453 });
454 }
455
456 #[test]
457 fn test_insert_new_first_chunk_gap() {
458 let new_first_chunk = RawChunk {
459 previous: None,
460 identifier: ChunkIdentifier::new(1),
461 next: Some(ChunkIdentifier::new(0)),
462 content: ChunkContent::Gap(()),
463 };
464
465 let mut linked_chunk = LinkedChunk::<5, char, ()>::new_with_update_history();
466 linked_chunk.push_items_back(vec!['a', 'b']);
467
468 let _ = linked_chunk.updates().unwrap().take();
470
471 insert_new_first_chunk(&mut linked_chunk, new_first_chunk).unwrap();
472
473 {
475 let mut chunks = linked_chunk.chunks();
476
477 assert_matches!(chunks.next(), Some(chunk) => {
478 assert_eq!(chunk.identifier(), 1);
479 assert!(chunk.is_gap());
480 });
481 assert_matches!(chunks.next(), Some(chunk) => {
482 assert_eq!(chunk.identifier(), 0);
483 assert!(chunk.is_items());
484 });
485 assert!(chunks.next().is_none());
486 }
487
488 {
490 let mut rchunks = linked_chunk.rchunks();
491
492 assert_eq!(rchunks.next().unwrap().identifier(), 0);
493 assert_eq!(rchunks.next().unwrap().identifier(), 1);
494 assert!(rchunks.next().is_none());
495 }
496
497 {
499 let updates = linked_chunk.updates().unwrap().take();
500
501 assert_eq!(updates.len(), 1);
502 assert_eq!(
503 updates,
504 [Update::NewGapChunk {
505 previous: None,
506 new: ChunkIdentifier::new(1),
507 next: Some(ChunkIdentifier::new(0)),
508 gap: (),
509 }]
510 );
511 }
512 }
513
514 #[test]
515 fn test_insert_new_first_chunk_items() {
516 let new_first_chunk = RawChunk {
517 previous: None,
518 identifier: ChunkIdentifier::new(1),
519 next: Some(ChunkIdentifier::new(0)),
520 content: ChunkContent::Items(vec!['c', 'd']),
521 };
522
523 let mut linked_chunk = LinkedChunk::<5, char, ()>::new_with_update_history();
524 linked_chunk.push_items_back(vec!['a', 'b']);
525
526 let _ = linked_chunk.updates().unwrap().take();
528
529 insert_new_first_chunk(&mut linked_chunk, new_first_chunk).unwrap();
530
531 {
533 let mut chunks = linked_chunk.chunks();
534
535 assert_matches!(chunks.next(), Some(chunk) => {
536 assert_eq!(chunk.identifier(), 1);
537 assert!(chunk.is_items());
538 });
539 assert_matches!(chunks.next(), Some(chunk) => {
540 assert_eq!(chunk.identifier(), 0);
541 assert!(chunk.is_items());
542 });
543 assert!(chunks.next().is_none());
544 }
545
546 {
548 let mut rchunks = linked_chunk.rchunks();
549
550 assert_eq!(rchunks.next().unwrap().identifier(), 0);
551 assert_eq!(rchunks.next().unwrap().identifier(), 1);
552 assert!(rchunks.next().is_none());
553 }
554
555 {
557 let updates = linked_chunk.updates().unwrap().take();
558
559 assert_eq!(updates.len(), 2);
560 assert_eq!(
561 updates,
562 [
563 Update::NewItemsChunk {
564 previous: None,
565 new: ChunkIdentifier::new(1),
566 next: Some(ChunkIdentifier::new(0)),
567 },
568 Update::PushItems {
569 at: Position::new(ChunkIdentifier::new(1), 0),
570 items: vec!['c', 'd']
571 }
572 ]
573 );
574 }
575 }
576
577 #[test]
578 fn test_from_all_chunks_empty() {
579 let lc = from_all_chunks::<3, char, ()>(vec![]).unwrap();
581 assert!(lc.is_none());
582 }
583
584 #[test]
585 fn test_from_all_chunks_success() {
586 let cid0 = ChunkIdentifier::new(0);
587 let cid1 = ChunkIdentifier::new(1);
588 let cid3 = ChunkIdentifier::new(3);
591
592 let chunks = vec![
598 RawChunk {
600 previous: None,
601 identifier: cid0,
602 next: Some(cid1),
603 content: ChunkContent::Items(vec!['a', 'b', 'c']),
604 },
605 RawChunk {
607 previous: Some(cid1),
608 identifier: cid3,
609 next: None,
610 content: ChunkContent::Items(vec!['d', 'e']),
611 },
612 RawChunk {
614 previous: Some(cid0),
615 identifier: cid1,
616 next: Some(cid3),
617 content: ChunkContent::Gap('g'),
618 },
619 ];
620
621 let mut lc = from_all_chunks::<3, _, _>(chunks)
622 .expect("building works")
623 .expect("returns a non-empty linked chunk");
624
625 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e']);
627
628 let mut chunks = lc.chunks();
630 let first_chunk = chunks.next().unwrap();
631 {
632 assert!(first_chunk.previous().is_none());
633 assert_eq!(first_chunk.identifier(), cid0);
634 }
635
636 let second_chunk = chunks.next().unwrap();
638 {
639 assert_eq!(second_chunk.identifier(), first_chunk.next().unwrap().identifier());
640 assert_eq!(second_chunk.previous().unwrap().identifier(), first_chunk.identifier());
641 assert_eq!(second_chunk.identifier(), cid1);
642 }
643
644 let third_chunk = chunks.next().unwrap();
646 {
647 assert_eq!(third_chunk.identifier(), second_chunk.next().unwrap().identifier());
648 assert_eq!(third_chunk.previous().unwrap().identifier(), second_chunk.identifier());
649 assert!(third_chunk.next().is_none());
650 assert_eq!(third_chunk.identifier(), cid3);
651 }
652
653 assert!(chunks.next().is_none());
655
656 assert_eq!(lc.num_items(), 5);
658
659 lc.push_gap_back('h');
662
663 let last_chunk = lc.chunks().last().unwrap();
664 assert_eq!(last_chunk.identifier(), ChunkIdentifier::new(cid3.index() + 1));
665 }
666
667 #[test]
668 fn test_from_all_chunks_chunk_too_large() {
669 let cid0 = ChunkIdentifier::new(0);
670
671 let res = from_all_chunks::<3, char, ()>(vec![RawChunk {
674 previous: None,
675 identifier: cid0,
676 next: None,
677 content: ChunkContent::Items(vec!['a', 'b', 'c', 'd']),
678 }]);
679 assert_matches!(res, Err(LazyLoaderError::ChunkTooLarge { id }) => {
680 assert_eq!(id, cid0);
681 });
682 }
683
684 #[test]
685 fn test_from_all_chunks_missing_first_chunk() {
686 let cid0 = ChunkIdentifier::new(0);
687 let cid1 = ChunkIdentifier::new(1);
688 let cid2 = ChunkIdentifier::new(2);
689
690 let res = from_all_chunks::<3, char, char>(vec![
691 RawChunk {
692 previous: Some(cid2),
693 identifier: cid0,
694 next: Some(cid1),
695 content: ChunkContent::Gap('g'),
696 },
697 RawChunk {
698 previous: Some(cid0),
699 identifier: cid1,
700 next: None,
701 content: ChunkContent::Items(vec!['a', 'b', 'c']),
702 },
703 ]);
704 assert_matches!(res, Err(LazyLoaderError::ChunkIsNotFirst { id }) => {
705 assert_eq!(id, cid0);
706 });
707 }
708
709 #[test]
710 fn test_from_all_chunks_multiple_first_chunks() {
711 let cid0 = ChunkIdentifier::new(0);
712 let cid1 = ChunkIdentifier::new(1);
713
714 let res = from_all_chunks::<3, char, char>(vec![
715 RawChunk {
716 previous: None,
717 identifier: cid0,
718 next: None,
719 content: ChunkContent::Gap('g'),
720 },
721 RawChunk {
723 previous: None,
724 identifier: cid1,
725 next: None,
726 content: ChunkContent::Gap('G'),
727 },
728 ]);
729
730 assert_matches!(res, Err(LazyLoaderError::MultipleConnectedComponents));
731 }
732
733 #[test]
734 fn test_from_all_chunks_cycle() {
735 let cid0 = ChunkIdentifier::new(0);
736 let cid1 = ChunkIdentifier::new(1);
737
738 let res = from_all_chunks::<3, char, char>(vec![
739 RawChunk {
740 previous: None,
741 identifier: cid0,
742 next: None,
743 content: ChunkContent::Gap('g'),
744 },
745 RawChunk {
746 previous: Some(cid0),
747 identifier: cid1,
748 next: Some(cid0),
749 content: ChunkContent::Gap('G'),
750 },
751 ]);
752
753 assert_matches!(res, Err(LazyLoaderError::Cycle { new_chunk, with_chunk }) => {
754 assert_eq!(new_chunk, cid1);
755 assert_eq!(with_chunk, cid0);
756 });
757 }
758
759 #[test]
760 fn test_from_all_chunks_multiple_connected_components() {
761 let cid0 = ChunkIdentifier::new(0);
762 let cid1 = ChunkIdentifier::new(1);
763 let cid2 = ChunkIdentifier::new(2);
764
765 let res = from_all_chunks::<3, char, char>(vec![
766 RawChunk {
768 previous: None,
769 identifier: cid0,
770 next: Some(cid1),
771 content: ChunkContent::Gap('g'),
772 },
773 RawChunk {
774 previous: Some(cid0),
775 identifier: cid1,
776 next: None,
777 content: ChunkContent::Gap('G'),
778 },
779 RawChunk {
781 previous: None,
782 identifier: cid2,
783 next: None,
784 content: ChunkContent::Gap('h'),
785 },
786 ]);
787
788 assert_matches!(res, Err(LazyLoaderError::MultipleConnectedComponents));
789 }
790}