@@ -170,26 +170,93 @@ where
170
170
// Emit the updates.
171
171
if let Some ( updates) = linked_chunk. updates . as_mut ( ) {
172
172
let first_chunk = linked_chunk. links . first_chunk ( ) ;
173
+ emit_new_first_chunk_updates ( first_chunk, updates) ;
174
+ }
173
175
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) ;
176
+ Ok ( ( ) )
177
+ }
177
178
178
- match first_chunk. content ( ) {
179
- ChunkContent :: Gap ( gap) => {
180
- updates. push ( Update :: NewGapChunk { previous, new, next, gap : gap. clone ( ) } ) ;
181
- }
179
+ /// Emit updates whenever a new first chunk is inserted at the front of a
180
+ /// `LinkedChunk`.
181
+ fn emit_new_first_chunk_updates < const CAP : usize , Item , Gap > (
182
+ chunk : & Chunk < CAP , Item , Gap > ,
183
+ updates : & mut ObservableUpdates < Item , Gap > ,
184
+ ) where
185
+ Item : Clone ,
186
+ Gap : Clone ,
187
+ {
188
+ let previous = chunk. previous ( ) . map ( Chunk :: identifier) . or ( chunk. lazy_previous ) ;
189
+ let new = chunk. identifier ( ) ;
190
+ let next = chunk. next ( ) . map ( Chunk :: identifier) ;
182
191
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
- }
192
+ match chunk. content ( ) {
193
+ ChunkContent :: Gap ( gap) => {
194
+ updates. push ( Update :: NewGapChunk { previous, new, next, gap : gap. clone ( ) } ) ;
195
+ }
196
+ ChunkContent :: Items ( items) => {
197
+ updates. push ( Update :: NewItemsChunk { previous, new, next } ) ;
198
+ updates. push ( Update :: PushItems { at : chunk. first_position ( ) , items : items. clone ( ) } ) ;
199
+ }
200
+ }
201
+ }
202
+
203
+ /// Replace the items with the given last chunk of items and generator.
204
+ ///
205
+ /// This clears all the chunks in memory before resetting to the new chunk,
206
+ /// if provided.
207
+ pub fn replace_with < const CAP : usize , Item , Gap > (
208
+ linked_chunk : & mut LinkedChunk < CAP , Item , Gap > ,
209
+ chunk : Option < RawChunk < Item , Gap > > ,
210
+ chunk_identifier_generator : ChunkIdentifierGenerator ,
211
+ ) -> Result < ( ) , LazyLoaderError >
212
+ where
213
+ Item : Clone ,
214
+ Gap : Clone ,
215
+ {
216
+ let Some ( mut chunk) = chunk else {
217
+ // This is equivalent to clearing the linked chunk, and overriding the chunk ID
218
+ // generator afterwards. But, if there was no chunks in the DB, the generator
219
+ // should be reset too, so it's entirely equivalent to a clear.
220
+ linked_chunk. clear ( ) ;
221
+ return Ok ( ( ) ) ;
222
+ } ;
223
+
224
+ // Check consistency before replacing the `LinkedChunk`.
225
+ // The number of items is not too large.
226
+ if let ChunkContent :: Items ( items) = & chunk. content {
227
+ if items. len ( ) > CAP {
228
+ return Err ( LazyLoaderError :: ChunkTooLarge { id : chunk. identifier } ) ;
190
229
}
191
230
}
192
231
232
+ // Chunk has no next chunk.
233
+ if chunk. next . is_some ( ) {
234
+ return Err ( LazyLoaderError :: ChunkIsNotLast { id : chunk. identifier } ) ;
235
+ }
236
+
237
+ // The last chunk is now valid.
238
+ linked_chunk. chunk_identifier_generator = chunk_identifier_generator;
239
+
240
+ // Take the `previous` chunk and consider it becomes the `lazy_previous`.
241
+ let lazy_previous = chunk. previous . take ( ) ;
242
+
243
+ // Transform the `RawChunk` into a `Chunk`.
244
+ let mut chunk_ptr = Chunk :: new_leaked ( chunk. identifier , chunk. content ) ;
245
+
246
+ // Set the `lazy_previous` value!
247
+ //
248
+ // SAFETY: Pointer is convertible to a reference.
249
+ unsafe { chunk_ptr. as_mut ( ) } . lazy_previous = lazy_previous;
250
+
251
+ // Replace the first link with the new pointer.
252
+ linked_chunk. links . replace_with ( chunk_ptr) ;
253
+
254
+ if let Some ( updates) = linked_chunk. updates . as_mut ( ) {
255
+ // TODO: clear updates first? (see same comment in `clear`).
256
+ updates. push ( Update :: Clear ) ;
257
+ emit_new_first_chunk_updates ( linked_chunk. links . first_chunk ( ) , updates) ;
258
+ }
259
+
193
260
Ok ( ( ) )
194
261
}
195
262
@@ -291,8 +358,9 @@ mod tests {
291
358
use assert_matches:: assert_matches;
292
359
293
360
use super :: {
294
- super :: Position , from_all_chunks, from_last_chunk, insert_new_first_chunk, ChunkContent ,
295
- ChunkIdentifier , ChunkIdentifierGenerator , LazyLoaderError , LinkedChunk , RawChunk , Update ,
361
+ super :: Position , from_all_chunks, from_last_chunk, insert_new_first_chunk, replace_with,
362
+ ChunkContent , ChunkIdentifier , ChunkIdentifierGenerator , LazyLoaderError , LinkedChunk ,
363
+ RawChunk , Update ,
296
364
} ;
297
365
298
366
#[ test]
@@ -574,6 +642,177 @@ mod tests {
574
642
}
575
643
}
576
644
645
+ #[ test]
646
+ fn test_replace_with_chunk_too_large ( ) {
647
+ // Start with a linked chunk with 3 chunks: one item, one gap, one item.
648
+ let mut linked_chunk = LinkedChunk :: < 2 , char , ( ) > :: new ( ) ;
649
+ linked_chunk. push_items_back ( vec ! [ 'a' , 'b' ] ) ;
650
+ linked_chunk. push_gap_back ( ( ) ) ;
651
+ linked_chunk. push_items_back ( vec ! [ 'c' , 'd' ] ) ;
652
+
653
+ // Try to replace it with a last chunk that has too many items.
654
+ let chunk_identifier_generator = ChunkIdentifierGenerator :: new_from_scratch ( ) ;
655
+
656
+ let chunk_id = ChunkIdentifier :: new ( 1 ) ;
657
+ let raw_chunk = RawChunk {
658
+ previous : Some ( ChunkIdentifier :: new ( 0 ) ) ,
659
+ identifier : chunk_id,
660
+ next : None ,
661
+ content : ChunkContent :: Items ( vec ! [ 'e' , 'f' , 'g' , 'h' ] ) ,
662
+ } ;
663
+
664
+ let err = replace_with ( & mut linked_chunk, Some ( raw_chunk) , chunk_identifier_generator)
665
+ . unwrap_err ( ) ;
666
+ assert_matches ! ( err, LazyLoaderError :: ChunkTooLarge { id } => {
667
+ assert_eq!( chunk_id, id) ;
668
+ } ) ;
669
+ }
670
+
671
+ #[ test]
672
+ fn test_replace_with_next_chunk ( ) {
673
+ // Start with a linked chunk with 3 chunks: one item, one gap, one item.
674
+ let mut linked_chunk = LinkedChunk :: < 2 , char , ( ) > :: new ( ) ;
675
+ linked_chunk. push_items_back ( vec ! [ 'a' , 'b' ] ) ;
676
+ linked_chunk. push_gap_back ( ( ) ) ;
677
+ linked_chunk. push_items_back ( vec ! [ 'c' , 'd' ] ) ;
678
+
679
+ // Try to replace it with a last chunk that has too many items.
680
+ let chunk_identifier_generator = ChunkIdentifierGenerator :: new_from_scratch ( ) ;
681
+
682
+ let chunk_id = ChunkIdentifier :: new ( 1 ) ;
683
+ let raw_chunk = RawChunk {
684
+ previous : Some ( ChunkIdentifier :: new ( 0 ) ) ,
685
+ identifier : chunk_id,
686
+ next : Some ( ChunkIdentifier :: new ( 2 ) ) ,
687
+ content : ChunkContent :: Items ( vec ! [ 'e' , 'f' ] ) ,
688
+ } ;
689
+
690
+ let err = replace_with ( & mut linked_chunk, Some ( raw_chunk) , chunk_identifier_generator)
691
+ . unwrap_err ( ) ;
692
+ assert_matches ! ( err, LazyLoaderError :: ChunkIsNotLast { id } => {
693
+ assert_eq!( chunk_id, id) ;
694
+ } ) ;
695
+ }
696
+
697
+ #[ test]
698
+ fn test_replace_with_empty ( ) {
699
+ // Start with a linked chunk with 3 chunks: one item, one gap, one item.
700
+ let mut linked_chunk = LinkedChunk :: < 2 , char , ( ) > :: new_with_update_history ( ) ;
701
+ linked_chunk. push_items_back ( vec ! [ 'a' , 'b' ] ) ;
702
+ linked_chunk. push_gap_back ( ( ) ) ;
703
+ linked_chunk. push_items_back ( vec ! [ 'c' , 'd' ] ) ;
704
+
705
+ // Drain initial updates.
706
+ let _ = linked_chunk. updates ( ) . unwrap ( ) . take ( ) ;
707
+
708
+ // Replace it with… you know, nothing (jon snow).
709
+ let chunk_identifier_generator =
710
+ ChunkIdentifierGenerator :: new_from_previous_chunk_identifier (
711
+ ChunkIdentifierGenerator :: FIRST_IDENTIFIER ,
712
+ ) ;
713
+ replace_with ( & mut linked_chunk, None , chunk_identifier_generator) . unwrap ( ) ;
714
+
715
+ // The linked chunk still has updates enabled.
716
+ assert ! ( linked_chunk. updates( ) . is_some( ) ) ;
717
+
718
+ // Check the linked chunk only contains the default empty events chunk.
719
+ let mut it = linked_chunk. chunks ( ) ;
720
+
721
+ assert_matches ! ( it. next( ) , Some ( chunk) => {
722
+ assert_eq!( chunk. identifier( ) , ChunkIdentifier :: new( 0 ) ) ;
723
+ assert!( chunk. is_items( ) ) ;
724
+ assert!( chunk. next( ) . is_none( ) ) ;
725
+ assert_matches!( chunk. content( ) , ChunkContent :: Items ( items) => {
726
+ assert!( items. is_empty( ) ) ;
727
+ } ) ;
728
+ } ) ;
729
+
730
+ // And there's no other chunk.
731
+ assert_matches ! ( it. next( ) , None ) ;
732
+
733
+ // Check updates.
734
+ {
735
+ let updates = linked_chunk. updates ( ) . unwrap ( ) . take ( ) ;
736
+
737
+ assert_eq ! ( updates. len( ) , 2 ) ;
738
+ assert_eq ! (
739
+ updates,
740
+ [
741
+ Update :: Clear ,
742
+ Update :: NewItemsChunk {
743
+ previous: None ,
744
+ new: ChunkIdentifier :: new( 0 ) ,
745
+ next: None ,
746
+ } ,
747
+ ]
748
+ ) ;
749
+ }
750
+ }
751
+
752
+ #[ test]
753
+ fn test_replace_with_non_empty ( ) {
754
+ // Start with a linked chunk with 3 chunks: one item, one gap, one item.
755
+ let mut linked_chunk = LinkedChunk :: < 2 , char , ( ) > :: new_with_update_history ( ) ;
756
+ linked_chunk. push_items_back ( vec ! [ 'a' , 'b' ] ) ;
757
+ linked_chunk. push_gap_back ( ( ) ) ;
758
+ linked_chunk. push_items_back ( vec ! [ 'c' , 'd' ] ) ;
759
+
760
+ // Drain initial updates.
761
+ let _ = linked_chunk. updates ( ) . unwrap ( ) . take ( ) ;
762
+
763
+ // Replace it with a single chunk (sorry, jon).
764
+ let chunk_identifier_generator =
765
+ ChunkIdentifierGenerator :: new_from_previous_chunk_identifier ( ChunkIdentifier :: new ( 42 ) ) ;
766
+
767
+ let chunk_id = ChunkIdentifier :: new ( 1 ) ;
768
+ let chunk = RawChunk {
769
+ previous : Some ( ChunkIdentifier :: new ( 0 ) ) ,
770
+ identifier : chunk_id,
771
+ next : None ,
772
+ content : ChunkContent :: Items ( vec ! [ 'e' , 'f' ] ) ,
773
+ } ;
774
+ replace_with ( & mut linked_chunk, Some ( chunk) , chunk_identifier_generator) . unwrap ( ) ;
775
+
776
+ // The linked chunk still has updates enabled.
777
+ assert ! ( linked_chunk. updates( ) . is_some( ) ) ;
778
+
779
+ let mut it = linked_chunk. chunks ( ) ;
780
+
781
+ // The first chunk is an event chunks with the expected items.
782
+ assert_matches ! ( it. next( ) , Some ( chunk) => {
783
+ assert_eq!( chunk. identifier( ) , chunk_id) ;
784
+ assert!( chunk. next( ) . is_none( ) ) ;
785
+ assert_matches!( chunk. content( ) , ChunkContent :: Items ( items) => {
786
+ assert_eq!( * items, vec![ 'e' , 'f' ] ) ;
787
+ } ) ;
788
+ } ) ;
789
+
790
+ // Nothing more.
791
+ assert ! ( it. next( ) . is_none( ) ) ;
792
+
793
+ // Check updates.
794
+ {
795
+ let updates = linked_chunk. updates ( ) . unwrap ( ) . take ( ) ;
796
+
797
+ assert_eq ! ( updates. len( ) , 3 ) ;
798
+ assert_eq ! (
799
+ updates,
800
+ [
801
+ Update :: Clear ,
802
+ Update :: NewItemsChunk {
803
+ previous: Some ( ChunkIdentifier :: new( 0 ) ) ,
804
+ new: chunk_id,
805
+ next: None ,
806
+ } ,
807
+ Update :: PushItems {
808
+ at: Position :: new( ChunkIdentifier :: new( 1 ) , 0 ) ,
809
+ items: vec![ 'e' , 'f' ]
810
+ }
811
+ ]
812
+ ) ;
813
+ }
814
+ }
815
+
577
816
#[ test]
578
817
fn test_from_all_chunks_empty ( ) {
579
818
// Building an empty linked chunk works, and returns `None`.
0 commit comments