1use std::marker::PhantomData;
39use std::path::Path;
40
41use crate::shared_region::{OffsetPtr, RegionError, SharedRegion};
42
43pub const NIL_INDEX: u32 = u32::MAX;
45
46#[derive(Debug)]
48#[repr(C)]
49pub struct KTowerCascade<T, const DEPTH: usize> {
50 pub indices: [u32; DEPTH],
51 _phantom: PhantomData<T>,
52}
53
54impl<T, const DEPTH: usize> Clone for KTowerCascade<T, DEPTH> {
55 fn clone(&self) -> Self { *self }
56}
57impl<T, const DEPTH: usize> Copy for KTowerCascade<T, DEPTH> {}
58impl<T, const DEPTH: usize> PartialEq for KTowerCascade<T, DEPTH> {
59 fn eq(&self, other: &Self) -> bool { self.indices == other.indices }
60}
61impl<T, const DEPTH: usize> Eq for KTowerCascade<T, DEPTH> {}
62impl<T, const DEPTH: usize> std::hash::Hash for KTowerCascade<T, DEPTH> {
63 fn hash<H: std::hash::Hasher>(&self, s: &mut H) { self.indices.hash(s); }
64}
65unsafe impl<T, const DEPTH: usize> Send for KTowerCascade<T, DEPTH> {}
70unsafe impl<T, const DEPTH: usize> Sync for KTowerCascade<T, DEPTH> {}
71
72impl<T, const DEPTH: usize> Default for KTowerCascade<T, DEPTH> {
73 fn default() -> Self { Self::NIL }
74}
75
76impl<T, const DEPTH: usize> KTowerCascade<T, DEPTH> {
77 pub const NIL: Self = Self {
79 indices: [NIL_INDEX; DEPTH],
80 _phantom: PhantomData,
81 };
82
83 pub const fn new(indices: [u32; DEPTH]) -> Self {
84 Self { indices, _phantom: PhantomData }
85 }
86
87 pub const fn from_raw(indices: [u32; DEPTH]) -> Self { Self::new(indices) }
90
91 pub const fn raw(self) -> [u32; DEPTH] { self.indices }
93
94 pub fn is_nil(self) -> bool {
96 self.indices.iter().all(|&i| i == NIL_INDEX)
97 }
98
99 pub fn level(self, level: usize) -> u32 {
101 self.indices[level]
102 }
103
104 pub fn leaf(self) -> u32 { self.indices[DEPTH - 1] }
106
107 pub fn with_level(mut self, level: usize, value: u32) -> Self {
109 self.indices[level] = value;
110 self
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum CascadeError {
117 Region(RegionError),
118 NilAtLevel(usize),
119 OutOfBounds,
120 IoError(std::io::ErrorKind),
121}
122
123impl From<RegionError> for CascadeError {
124 fn from(e: RegionError) -> Self { Self::Region(e) }
125}
126impl From<std::io::Error> for CascadeError {
127 fn from(e: std::io::Error) -> Self { Self::IoError(e.kind()) }
128}
129
130pub struct CascadeResolver2<T: Copy + Default + 'static> {
142 pub outer: SharedRegion<KTowerCascade<T, 1>>,
143 pub inner: SharedRegion<T>,
144 header_sidecar: subetha_core::HandshakeHeader,
145 ring_sidecar: Box<subetha_core::ObservationRing>,
146}
147
148impl<T: Copy + Default + Send + Sync + 'static>
149 subetha_sidecar::AdaptiveInstance for CascadeResolver2<T>
150{
151 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
152 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
153 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
154 Box::new(subetha_sidecar::NoMigrationPolicy)
155 }
156}
157
158impl<T: Copy + Default + 'static> CascadeResolver2<T> {
159 pub fn create(
160 outer_path: impl AsRef<Path>, outer_capacity: usize,
161 inner_path: impl AsRef<Path>, inner_capacity: usize,
162 ) -> Result<Self, CascadeError> {
163 let outer = SharedRegion::create(outer_path, outer_capacity)?;
164 let inner = SharedRegion::create(inner_path, inner_capacity)?;
165 Ok(Self {
166 outer, inner,
167 header_sidecar: subetha_core::HandshakeHeader::new(),
168 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
169 })
170 }
171
172 pub fn open(
173 outer_path: impl AsRef<Path>, outer_capacity: usize,
174 inner_path: impl AsRef<Path>, inner_capacity: usize,
175 ) -> Result<Self, CascadeError> {
176 let outer = SharedRegion::open(outer_path, outer_capacity)?;
177 let inner = SharedRegion::open(inner_path, inner_capacity)?;
178 Ok(Self {
179 outer, inner,
180 header_sidecar: subetha_core::HandshakeHeader::new(),
181 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
182 })
183 }
184
185 pub fn insert(
188 &self, outer_idx: u32, value: T,
189 ) -> Result<KTowerCascade<T, 2>, CascadeError> {
190 let r = self.insert_inner(outer_idx, value);
191 self.ring_sidecar.push_op(
192 crate::sidecar_ops::cascade::OP_INSERT,
193 if r.is_err() { 1 } else { 0 },
194 );
195 r
196 }
197
198 fn insert_inner(
199 &self, outer_idx: u32, value: T,
200 ) -> Result<KTowerCascade<T, 2>, CascadeError> {
201 let leaf = self.inner.allocate(value)?;
203 let inner_cascade = KTowerCascade::<T, 1>::new([leaf.index]);
205 let outer_slot = if outer_idx < self.outer.capacity() as u32 {
207 let cur_len = self.outer.len() as u32;
213 if outer_idx == cur_len {
214 let p = self.outer.allocate(inner_cascade)?;
215 p.index
216 } else if outer_idx < cur_len {
217 self.outer.set(OffsetPtr::new(outer_idx), inner_cascade)?;
218 outer_idx
219 } else {
220 return Err(CascadeError::OutOfBounds);
221 }
222 } else {
223 return Err(CascadeError::OutOfBounds);
224 };
225 Ok(KTowerCascade::<T, 2>::new([outer_slot, leaf.index]))
226 }
227
228 pub fn get(&self, c: KTowerCascade<T, 2>) -> Result<T, CascadeError> {
230 let r = self.get_inner(c);
231 self.ring_sidecar.push_op(
232 crate::sidecar_ops::cascade::OP_GET,
233 if r.is_err() { 1 } else { 0 },
234 );
235 r
236 }
237
238 fn get_inner(&self, c: KTowerCascade<T, 2>) -> Result<T, CascadeError> {
239 if c.indices[0] == NIL_INDEX { return Err(CascadeError::NilAtLevel(0)); }
240 let inner_cascade = self.outer.get(OffsetPtr::new(c.indices[0]))?;
241 if inner_cascade.indices[0] != c.indices[1] {
242 }
247 if c.indices[1] == NIL_INDEX { return Err(CascadeError::NilAtLevel(1)); }
248 Ok(self.inner.get(OffsetPtr::new(c.indices[1]))?)
249 }
250
251 pub fn flush(&self) -> Result<(), CascadeError> {
252 self.outer.flush()?;
253 self.inner.flush()?;
254 Ok(())
255 }
256}
257
258pub struct CascadeResolverN<T: Copy + Default + 'static, const DEPTH: usize> {
266 pub intermediate: Vec<SharedRegion<u32>>,
270 pub leaf: SharedRegion<T>,
272 header_sidecar: subetha_core::HandshakeHeader,
273 ring_sidecar: Box<subetha_core::ObservationRing>,
274}
275
276impl<T: Copy + Default + Send + Sync + 'static, const DEPTH: usize>
277 subetha_sidecar::AdaptiveInstance for CascadeResolverN<T, DEPTH>
278{
279 fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
280 fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
281 fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
282 Box::new(subetha_sidecar::NoMigrationPolicy)
283 }
284}
285
286impl<T: Copy + Default + 'static, const DEPTH: usize> CascadeResolverN<T, DEPTH> {
287 pub fn create(
288 leaf_path: impl AsRef<Path>, leaf_capacity: usize,
289 intermediate_paths_and_caps: Vec<(std::path::PathBuf, usize)>,
290 ) -> Result<Self, CascadeError> {
291 assert_eq!(intermediate_paths_and_caps.len(), DEPTH.saturating_sub(1),
292 "must supply DEPTH-1 intermediate region descriptors");
293 let leaf = SharedRegion::create(leaf_path, leaf_capacity)?;
294 let intermediate: Vec<SharedRegion<u32>> = intermediate_paths_and_caps
295 .into_iter()
296 .map(|(p, cap)| SharedRegion::create(p, cap))
297 .collect::<Result<Vec<_>, _>>()?;
298 Ok(Self {
299 intermediate, leaf,
300 header_sidecar: subetha_core::HandshakeHeader::new(),
301 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
302 })
303 }
304
305 pub fn open(
306 leaf_path: impl AsRef<Path>, leaf_capacity: usize,
307 intermediate_paths_and_caps: Vec<(std::path::PathBuf, usize)>,
308 ) -> Result<Self, CascadeError> {
309 assert_eq!(intermediate_paths_and_caps.len(), DEPTH.saturating_sub(1));
310 let leaf = SharedRegion::open(leaf_path, leaf_capacity)?;
311 let intermediate: Vec<SharedRegion<u32>> = intermediate_paths_and_caps
312 .into_iter()
313 .map(|(p, cap)| SharedRegion::open(p, cap))
314 .collect::<Result<Vec<_>, _>>()?;
315 Ok(Self {
316 intermediate, leaf,
317 header_sidecar: subetha_core::HandshakeHeader::new(),
318 ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
319 })
320 }
321
322 pub fn allocate_leaf(&self, value: T) -> Result<u32, CascadeError> {
331 Ok(self.leaf.allocate(value)?.index)
332 }
333
334 pub fn insert_at_top(
345 &self, top_idx: u32, value: T,
346 ) -> Result<KTowerCascade<T, DEPTH>, CascadeError> {
347 let r = self.insert_at_top_inner(top_idx, value);
348 self.ring_sidecar.push_op(
349 crate::sidecar_ops::cascade::OP_INSERT,
350 if r.is_err() { 1 } else { 0 },
351 );
352 r
353 }
354
355 fn insert_at_top_inner(
356 &self, top_idx: u32, value: T,
357 ) -> Result<KTowerCascade<T, DEPTH>, CascadeError> {
358 let mut full = [0u32; DEPTH];
359 full[DEPTH - 1] = self.leaf.allocate(value)?.index;
361 for level in (1..DEPTH - 1).rev() {
365 let p = self.intermediate[level].allocate(full[level + 1])?;
366 full[level] = p.index;
367 }
368 if DEPTH > 1 {
371 self.write_intermediate(0, top_idx, full[1])?;
372 full[0] = top_idx;
373 } else {
374 full[0] = top_idx;
375 }
376 Ok(KTowerCascade::<T, DEPTH>::new(full))
377 }
378
379 pub fn append(
384 &self, value: T,
385 ) -> Result<KTowerCascade<T, DEPTH>, CascadeError> {
386 if DEPTH == 1 {
387 let leaf_idx = self.leaf.allocate(value)?.index;
388 return Ok(KTowerCascade::<T, DEPTH>::new([leaf_idx; DEPTH]));
389 }
390 let next_top = self.intermediate[0].len() as u32;
391 self.insert_at_top(next_top, value)
392 }
393
394 fn write_intermediate(
395 &self, level: usize, slot: u32, value: u32,
396 ) -> Result<(), CascadeError> {
397 let region = &self.intermediate[level];
398 let cur_len = region.len() as u32;
399 if slot == cur_len {
400 region.allocate(value)?;
401 } else if slot < cur_len {
402 region.set(OffsetPtr::new(slot), value)?;
403 } else {
404 return Err(CascadeError::OutOfBounds);
405 }
406 Ok(())
407 }
408
409 pub fn get(&self, c: KTowerCascade<T, DEPTH>) -> Result<T, CascadeError> {
411 let r = self.get_inner(c);
412 self.ring_sidecar.push_op(
413 crate::sidecar_ops::cascade::OP_GET,
414 if r.is_err() { 1 } else { 0 },
415 );
416 r
417 }
418
419 fn get_inner(&self, c: KTowerCascade<T, DEPTH>) -> Result<T, CascadeError> {
420 for level in 0..DEPTH {
421 if c.indices[level] == NIL_INDEX {
422 return Err(CascadeError::NilAtLevel(level));
423 }
424 }
425 for level in 0..DEPTH - 1 {
428 let stored = self.intermediate[level].get(OffsetPtr::new(c.indices[level]))?;
429 if stored != c.indices[level + 1] {
430 return Err(CascadeError::NilAtLevel(level + 1));
431 }
432 }
433 Ok(self.leaf.get(OffsetPtr::new(c.indices[DEPTH - 1]))?)
434 }
435
436 pub fn flush(&self) -> Result<(), CascadeError> {
437 self.leaf.flush()?;
438 for r in &self.intermediate { r.flush()?; }
439 Ok(())
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 fn tmp(name: &str) -> std::path::PathBuf {
448 let mut p = std::env::temp_dir();
449 let pid = std::process::id();
450 p.push(format!("subetha-cascade-{name}-{pid}.bin"));
451 p
452 }
453
454 #[test]
457 fn nil_is_all_ones() {
458 let n = KTowerCascade::<u64, 4>::NIL;
459 assert!(n.is_nil());
460 assert_eq!(n.raw(), [NIL_INDEX; 4]);
461 }
462
463 #[test]
464 fn new_and_level_access() {
465 let c = KTowerCascade::<u64, 3>::new([1, 2, 3]);
466 assert_eq!(c.level(0), 1);
467 assert_eq!(c.level(1), 2);
468 assert_eq!(c.level(2), 3);
469 assert_eq!(c.leaf(), 3);
470 assert!(!c.is_nil());
471 }
472
473 #[test]
474 fn with_level_updates_only_one() {
475 let c = KTowerCascade::<u64, 4>::new([1, 2, 3, 4]);
476 let c2 = c.with_level(2, 99);
477 assert_eq!(c2.raw(), [1, 2, 99, 4]);
478 assert_eq!(c.raw(), [1, 2, 3, 4]);
480 }
481
482 #[test]
483 fn equality_and_hash() {
484 use std::collections::HashSet;
485 let a = KTowerCascade::<u64, 2>::new([5, 10]);
486 let b = KTowerCascade::<u64, 2>::new([5, 10]);
487 let c = KTowerCascade::<u64, 2>::new([5, 11]);
488 assert_eq!(a, b);
489 assert_ne!(a, c);
490 let mut s = HashSet::new();
491 s.insert(a);
492 assert!(s.contains(&b));
493 assert!(!s.contains(&c));
494 }
495
496 #[test]
497 fn raw_round_trip_preserves_indices() {
498 let c = KTowerCascade::<u64, 4>::new([10, 20, 30, 40]);
499 let raw = c.raw();
500 let c2 = KTowerCascade::<u64, 4>::from_raw(raw);
501 assert_eq!(c, c2);
502 }
503
504 #[test]
505 fn depth_1_degenerates_to_offset_ptr() {
506 let c = KTowerCascade::<u64, 1>::new([42]);
507 assert_eq!(c.leaf(), 42);
508 assert_eq!(c.level(0), 42);
509 }
510
511 #[test]
512 fn size_grows_linearly_with_depth() {
513 assert_eq!(std::mem::size_of::<KTowerCascade<u64, 1>>(), 4);
514 assert_eq!(std::mem::size_of::<KTowerCascade<u64, 2>>(), 8);
515 assert_eq!(std::mem::size_of::<KTowerCascade<u64, 4>>(), 16);
516 assert_eq!(std::mem::size_of::<KTowerCascade<u64, 8>>(), 32);
517 }
518
519 #[test]
522 fn resolver2_insert_get_round_trip() {
523 let op = tmp("r2-outer");
524 let ip = tmp("r2-inner");
525 let r: CascadeResolver2<u64> = CascadeResolver2::create(&op, 16, &ip, 64).unwrap();
526 let c1 = r.insert(0, 100).unwrap();
527 let c2 = r.insert(1, 200).unwrap();
528 let c3 = r.insert(2, 300).unwrap();
529 assert_eq!(r.get(c1).unwrap(), 100);
530 assert_eq!(r.get(c2).unwrap(), 200);
531 assert_eq!(r.get(c3).unwrap(), 300);
532 std::fs::remove_file(&op).ok();
533 std::fs::remove_file(&ip).ok();
534 }
535
536 #[test]
537 fn resolver2_cross_handle_visibility() {
538 let op = tmp("r2-cross-outer");
539 let ip = tmp("r2-cross-inner");
540 let writer: CascadeResolver2<u64> = CascadeResolver2::create(&op, 16, &ip, 64).unwrap();
541 let reader: CascadeResolver2<u64> = CascadeResolver2::open(&op, 16, &ip, 64).unwrap();
542 let c = writer.insert(0, 7777).unwrap();
543 let raw = c.raw();
545 let c_reconstructed: KTowerCascade<u64, 2>
547 = KTowerCascade::from_raw(raw);
548 assert_eq!(reader.get(c_reconstructed).unwrap(), 7777);
549 std::fs::remove_file(&op).ok();
550 std::fs::remove_file(&ip).ok();
551 }
552
553 #[test]
556 fn resolver_n_depth_4_full_walk() {
557 let lp = tmp("rn-leaf");
558 let i0 = tmp("rn-i0");
559 let i1 = tmp("rn-i1");
560 let i2 = tmp("rn-i2");
561 let r: CascadeResolverN<u64, 4> = CascadeResolverN::create(
562 &lp, 64,
563 vec![(i0.clone(), 16), (i1.clone(), 16), (i2.clone(), 16)],
564 ).unwrap();
565 let c1 = r.append(1111).unwrap();
567 let c2 = r.append(2222).unwrap();
568 let c3 = r.append(3333).unwrap();
569 assert_eq!(r.get(c1).unwrap(), 1111);
570 assert_eq!(r.get(c2).unwrap(), 2222);
571 assert_eq!(r.get(c3).unwrap(), 3333);
572 for p in [&lp, &i0, &i1, &i2] { std::fs::remove_file(p).ok(); }
573 }
574
575 #[test]
576 fn resolver_n_nil_at_level_rejected() {
577 let lp = tmp("rn-nil-leaf");
578 let i0 = tmp("rn-nil-i0");
579 let r: CascadeResolverN<u64, 2> = CascadeResolverN::create(
580 &lp, 8, vec![(i0.clone(), 8)],
581 ).unwrap();
582 let nil = KTowerCascade::<u64, 2>::NIL;
583 assert!(matches!(r.get(nil), Err(CascadeError::NilAtLevel(0))));
584 for p in [&lp, &i0] { std::fs::remove_file(p).ok(); }
585 }
586
587 #[test]
588 fn resolver_n_struct_value_round_trip() {
589 #[derive(Clone, Copy, Debug, PartialEq, Default)]
590 #[repr(C)]
591 struct Entry { key: u64, payload: u64 }
592 let lp = tmp("rn-struct-leaf");
593 let i0 = tmp("rn-struct-i0");
594 let r: CascadeResolverN<Entry, 2> = CascadeResolverN::create(
595 &lp, 8, vec![(i0.clone(), 8)],
596 ).unwrap();
597 let entry = Entry { key: 42, payload: 999 };
598 let c = r.append(entry).unwrap();
599 assert_eq!(r.get(c).unwrap(), entry);
600 for p in [&lp, &i0] { std::fs::remove_file(p).ok(); }
601 }
602
603 #[test]
604 fn cascade_bits_are_position_independent() {
605 let lp = tmp("pi-leaf");
608 let i0 = tmp("pi-i0");
609 let r_a: CascadeResolverN<u64, 2> = CascadeResolverN::create(
610 &lp, 8, vec![(i0.clone(), 8)],
611 ).unwrap();
612 let c = r_a.append(0xCAFE_BABE).unwrap();
613 let raw = c.raw();
614 let r_b: CascadeResolverN<u64, 2> = CascadeResolverN::open(
615 &lp, 8, vec![(i0.clone(), 8)],
616 ).unwrap();
617 let c_b = KTowerCascade::<u64, 2>::from_raw(raw);
618 assert_eq!(r_b.get(c_b).unwrap(), 0xCAFE_BABE);
619 for p in [&lp, &i0] { std::fs::remove_file(p).ok(); }
620 }
621
622 #[test]
623 fn resolver_n_disk_persistence_survives_reopen() {
624 let lp = tmp("rn-disk-leaf");
625 let i0 = tmp("rn-disk-i0");
626 let i1 = tmp("rn-disk-i1");
627 let c_raw;
628 {
629 let r: CascadeResolverN<u64, 3> = CascadeResolverN::create(
630 &lp, 8, vec![(i0.clone(), 8), (i1.clone(), 8)],
631 ).unwrap();
632 let c = r.append(5555).unwrap();
633 c_raw = c.raw();
634 r.flush().unwrap();
635 }
636 let r2: CascadeResolverN<u64, 3> = CascadeResolverN::open(
637 &lp, 8, vec![(i0.clone(), 8), (i1.clone(), 8)],
638 ).unwrap();
639 let c_restored = KTowerCascade::<u64, 3>::from_raw(c_raw);
640 assert_eq!(r2.get(c_restored).unwrap(), 5555);
641 for p in [&lp, &i0, &i1] { std::fs::remove_file(p).ok(); }
642 }
643
644 #[test]
645 fn default_is_nil() {
646 let c: KTowerCascade<u64, 4> = KTowerCascade::default();
647 assert!(c.is_nil());
648 }
649}