1use std::{
16 collections::{BTreeMap, HashSet},
17 marker::PhantomData,
18};
19
20use tracing::error;
21
22use super::{
23 Chunk, ChunkContent, ChunkIdentifier, ChunkIdentifierGenerator, Ends, LinkedChunk,
24 ObservableUpdates, RawChunk,
25};
26
27struct TemporaryChunk<Item, Gap> {
34 previous: Option<ChunkIdentifier>,
35 next: Option<ChunkIdentifier>,
36 content: ChunkContent<Item, Gap>,
37}
38
39#[allow(missing_debug_implementations)]
48pub struct LinkedChunkBuilder<const CAP: usize, Item, Gap> {
49 chunks: BTreeMap<ChunkIdentifier, TemporaryChunk<Item, Gap>>,
51
52 build_with_update_history: bool,
55}
56
57impl<const CAP: usize, Item, Gap> Default for LinkedChunkBuilder<CAP, Item, Gap> {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63impl<const CAP: usize, Item, Gap> LinkedChunkBuilder<CAP, Item, Gap> {
64 pub fn new() -> Self {
66 Self { chunks: Default::default(), build_with_update_history: false }
67 }
68
69 pub fn push_gap(
75 &mut self,
76 previous: Option<ChunkIdentifier>,
77 id: ChunkIdentifier,
78 next: Option<ChunkIdentifier>,
79 content: Gap,
80 ) {
81 let chunk = TemporaryChunk { previous, next, content: ChunkContent::Gap(content) };
82 self.chunks.insert(id, chunk);
83 }
84
85 pub fn push_items(
91 &mut self,
92 previous: Option<ChunkIdentifier>,
93 id: ChunkIdentifier,
94 next: Option<ChunkIdentifier>,
95 items: impl IntoIterator<Item = Item>,
96 ) {
97 let chunk = TemporaryChunk {
98 previous,
99 next,
100 content: ChunkContent::Items(items.into_iter().collect()),
101 };
102 self.chunks.insert(id, chunk);
103 }
104
105 pub fn with_update_history(&mut self) {
108 self.build_with_update_history = true;
109 }
110
111 fn check_consistency(&mut self) -> Result<ChunkIdentifier, LinkedChunkBuilderError> {
118 let first_id =
120 self.chunks.iter().find_map(|(id, chunk)| chunk.previous.is_none().then_some(*id));
121
122 let Some(first_id) = first_id else {
125 return Err(LinkedChunkBuilderError::MissingFirstChunk);
126 };
127
128 let mut visited = HashSet::new();
131
132 let mut maybe_cur = Some(first_id);
134
135 while let Some(cur) = maybe_cur {
136 let Some(chunk) = self.chunks.get(&cur) else {
138 return Err(LinkedChunkBuilderError::MissingChunk { id: cur });
139 };
140
141 if let ChunkContent::Items(items) = &chunk.content {
142 if items.len() > CAP {
143 return Err(LinkedChunkBuilderError::ChunkTooLarge { id: cur });
144 }
145 }
146
147 if cur != first_id {
149 let Some(prev) = chunk.previous else {
151 return Err(LinkedChunkBuilderError::MultipleFirstChunks {
152 first_candidate: first_id,
153 second_candidate: cur,
154 });
155 };
156
157 if !visited.contains(&prev) {
160 return Err(LinkedChunkBuilderError::MissingChunk { id: prev });
161 }
162 }
163
164 if !visited.insert(cur) {
166 return Err(LinkedChunkBuilderError::Cycle { repeated: cur });
168 }
169
170 maybe_cur = chunk.next;
172 }
173
174 if visited.len() != self.chunks.len() {
178 return Err(LinkedChunkBuilderError::MultipleConnectedComponents);
179 }
180
181 Ok(first_id)
182 }
183
184 pub fn build(mut self) -> Result<Option<LinkedChunk<CAP, Item, Gap>>, LinkedChunkBuilderError> {
185 if self.chunks.is_empty() {
186 return Ok(None);
187 }
188
189 let first_id = self.check_consistency()?;
191
192 let mut max_chunk_id = first_id.index();
201
202 let mut graduate_chunk = |id: ChunkIdentifier| {
207 let temp = self.chunks.remove(&id)?;
208
209 max_chunk_id = max_chunk_id.max(id.index());
211
212 let chunk_ptr = Chunk::new_leaked(id, temp.content);
214
215 Some((temp.next, chunk_ptr))
216 };
217
218 let Some((mut next_chunk_id, first_chunk_ptr)) = graduate_chunk(first_id) else {
219 return Err(LinkedChunkBuilderError::MissingFirstChunk);
221 };
222
223 let mut prev_chunk_ptr = first_chunk_ptr;
224
225 while let Some(id) = next_chunk_id {
226 let Some((new_next, mut chunk_ptr)) = graduate_chunk(id) else {
227 return Err(LinkedChunkBuilderError::MissingChunk { id });
229 };
230
231 let chunk = unsafe { chunk_ptr.as_mut() };
232
233 let prev_chunk = unsafe { prev_chunk_ptr.as_mut() };
235 prev_chunk.next = Some(chunk_ptr);
236 chunk.previous = Some(prev_chunk_ptr);
237
238 prev_chunk_ptr = chunk_ptr;
240 next_chunk_id = new_next;
241 }
242
243 debug_assert!(self.chunks.is_empty());
244
245 let last_chunk_ptr = prev_chunk_ptr;
247 let last_chunk_ptr =
248 if first_chunk_ptr == last_chunk_ptr { None } else { Some(last_chunk_ptr) };
249 let links = Ends { first: first_chunk_ptr, last: last_chunk_ptr };
250
251 let chunk_identifier_generator =
252 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(ChunkIdentifier::new(
253 max_chunk_id,
254 ));
255
256 let updates =
257 if self.build_with_update_history { Some(ObservableUpdates::new()) } else { None };
258
259 Ok(Some(LinkedChunk { links, chunk_identifier_generator, updates, marker: PhantomData }))
260 }
261
262 pub fn from_raw_parts(raws: Vec<RawChunk<Item, Gap>>) -> Self {
264 let mut this = Self::new();
265 for raw in raws {
266 match raw.content {
267 ChunkContent::Gap(gap) => {
268 this.push_gap(raw.previous, raw.identifier, raw.next, gap);
269 }
270 ChunkContent::Items(vec) => {
271 this.push_items(raw.previous, raw.identifier, raw.next, vec);
272 }
273 }
274 }
275 this
276 }
277}
278
279#[derive(thiserror::Error, Debug)]
280pub enum LinkedChunkBuilderError {
281 #[error("chunk with id {} is too large", id.index())]
282 ChunkTooLarge { id: ChunkIdentifier },
283
284 #[error("there's no first chunk")]
285 MissingFirstChunk,
286
287 #[error("there are multiple first chunks")]
288 MultipleFirstChunks { first_candidate: ChunkIdentifier, second_candidate: ChunkIdentifier },
289
290 #[error("unable to resolve chunk with id {}", id.index())]
291 MissingChunk { id: ChunkIdentifier },
292
293 #[error("rebuilt chunks form a cycle: repeated identifier: {}", repeated.index())]
294 Cycle { repeated: ChunkIdentifier },
295
296 #[error("multiple connected components")]
297 MultipleConnectedComponents,
298}
299
300#[cfg(test)]
301mod tests {
302 use assert_matches::assert_matches;
303
304 use super::LinkedChunkBuilder;
305 use crate::linked_chunk::{ChunkIdentifier, LinkedChunkBuilderError};
306
307 #[test]
308 fn test_empty() {
309 let lcb = LinkedChunkBuilder::<3, char, char>::new();
310
311 let lc = lcb.build().unwrap();
313 assert!(lc.is_none());
314 }
315
316 #[test]
317 fn test_success() {
318 let mut lcb = LinkedChunkBuilder::<3, char, char>::new();
319
320 let cid0 = ChunkIdentifier::new(0);
321 let cid1 = ChunkIdentifier::new(1);
322 let cid3 = ChunkIdentifier::new(3);
325
326 lcb.push_items(None, cid0, Some(cid1), vec!['a', 'b', 'c']);
333 lcb.push_items(Some(cid1), cid3, None, vec!['d', 'e']);
335 lcb.push_gap(Some(cid0), cid1, Some(cid3), 'g');
337
338 let mut lc =
339 lcb.build().expect("building works").expect("returns a non-empty linked chunk");
340
341 assert_items_eq!(lc, ['a', 'b', 'c'] [-] ['d', 'e']);
343
344 let mut chunks = lc.chunks();
346 let first_chunk = chunks.next().unwrap();
347 {
348 assert!(first_chunk.previous().is_none());
349 assert_eq!(first_chunk.identifier(), cid0);
350 }
351
352 let second_chunk = chunks.next().unwrap();
354 {
355 assert_eq!(second_chunk.identifier(), first_chunk.next().unwrap().identifier());
356 assert_eq!(second_chunk.previous().unwrap().identifier(), first_chunk.identifier());
357 assert_eq!(second_chunk.identifier(), cid1);
358 }
359
360 let third_chunk = chunks.next().unwrap();
362 {
363 assert_eq!(third_chunk.identifier(), second_chunk.next().unwrap().identifier());
364 assert_eq!(third_chunk.previous().unwrap().identifier(), second_chunk.identifier());
365 assert!(third_chunk.next().is_none());
366 assert_eq!(third_chunk.identifier(), cid3);
367 }
368
369 assert!(chunks.next().is_none());
371
372 assert_eq!(lc.num_items(), 5);
374
375 lc.push_gap_back('h');
378
379 let last_chunk = lc.chunks().last().unwrap();
380 assert_eq!(last_chunk.identifier(), ChunkIdentifier::new(cid3.index() + 1));
381 }
382
383 #[test]
384 fn test_chunk_too_large() {
385 let mut lcb = LinkedChunkBuilder::<3, char, char>::new();
386
387 let cid0 = ChunkIdentifier::new(0);
388
389 lcb.push_items(None, cid0, None, vec!['a', 'b', 'c', 'd']);
392
393 let res = lcb.build();
394 assert_matches!(res, Err(LinkedChunkBuilderError::ChunkTooLarge { id }) => {
395 assert_eq!(id, cid0);
396 });
397 }
398
399 #[test]
400 fn test_missing_first_chunk() {
401 let mut lcb = LinkedChunkBuilder::<3, char, char>::new();
402
403 let cid0 = ChunkIdentifier::new(0);
404 let cid1 = ChunkIdentifier::new(1);
405 let cid2 = ChunkIdentifier::new(2);
406
407 lcb.push_gap(Some(cid2), cid0, Some(cid1), 'g');
408 lcb.push_items(Some(cid0), cid1, Some(cid2), ['a', 'b', 'c']);
409 lcb.push_items(Some(cid1), cid2, Some(cid0), ['d', 'e', 'f']);
410
411 let res = lcb.build();
412 assert_matches!(res, Err(LinkedChunkBuilderError::MissingFirstChunk));
413 }
414
415 #[test]
416 fn test_multiple_first_chunks() {
417 let mut lcb = LinkedChunkBuilder::<3, char, char>::new();
418
419 let cid0 = ChunkIdentifier::new(0);
420 let cid1 = ChunkIdentifier::new(1);
421
422 lcb.push_gap(None, cid0, Some(cid1), 'g');
423 lcb.push_items(None, cid1, Some(cid0), ['a', 'b', 'c']);
425
426 let res = lcb.build();
427 assert_matches!(res, Err(LinkedChunkBuilderError::MultipleFirstChunks { first_candidate, second_candidate }) => {
428 assert_eq!(first_candidate, cid0);
429 assert_eq!(second_candidate, cid1);
430 });
431 }
432
433 #[test]
434 fn test_missing_chunk() {
435 let mut lcb = LinkedChunkBuilder::<3, char, char>::new();
436
437 let cid0 = ChunkIdentifier::new(0);
438 let cid1 = ChunkIdentifier::new(1);
439 lcb.push_gap(None, cid0, Some(cid1), 'g');
440
441 let res = lcb.build();
442 assert_matches!(res, Err(LinkedChunkBuilderError::MissingChunk { id }) => {
443 assert_eq!(id, cid1);
444 });
445 }
446
447 #[test]
448 fn test_cycle() {
449 let mut lcb = LinkedChunkBuilder::<3, char, char>::new();
450
451 let cid0 = ChunkIdentifier::new(0);
452 let cid1 = ChunkIdentifier::new(1);
453 lcb.push_gap(None, cid0, Some(cid1), 'g');
454 lcb.push_gap(Some(cid0), cid1, Some(cid0), 'g');
455
456 let res = lcb.build();
457 assert_matches!(res, Err(LinkedChunkBuilderError::Cycle { repeated }) => {
458 assert_eq!(repeated, cid0);
459 });
460 }
461
462 #[test]
463 fn test_multiple_connected_components() {
464 let mut lcb = LinkedChunkBuilder::<3, char, char>::new();
465
466 let cid0 = ChunkIdentifier::new(0);
467 let cid1 = ChunkIdentifier::new(1);
468 let cid2 = ChunkIdentifier::new(2);
469
470 lcb.push_gap(None, cid0, Some(cid1), 'g');
472 lcb.push_items(Some(cid0), cid1, None, ['a', 'b', 'c']);
473 lcb.push_items(None, cid2, None, ['d', 'e', 'f']);
475
476 let res = lcb.build();
477 assert_matches!(res, Err(LinkedChunkBuilderError::MultipleConnectedComponents));
478 }
479}