dspk_core/window/
schedule.rs1use std::ops::Range;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct HaloWindow {
12 pub index: usize,
14 pub valid_global: Range<u64>,
16 pub read_global: Range<u64>,
18 pub valid_local: Range<usize>,
20}
21
22impl HaloWindow {
23 #[inline]
25 pub fn read_len(&self) -> usize {
26 (self.read_global.end - self.read_global.start) as usize
27 }
28
29 #[inline]
31 pub fn valid_len(&self) -> usize {
32 self.valid_local.len()
33 }
34
35 #[inline]
38 pub fn is_interior_local(&self, local_sample: usize) -> bool {
39 self.valid_local.contains(&local_sample)
40 }
41
42 #[inline]
44 pub fn to_global_sample(&self, local_sample: usize) -> u64 {
45 self.read_global.start + local_sample as u64
46 }
47
48 #[inline]
51 pub fn remap_event(&self, local_sample: usize) -> Option<u64> {
52 self.is_interior_local(local_sample).then(|| self.to_global_sample(local_sample))
53 }
54
55 pub fn around(index: usize, valid: Range<u64>, left_halo: u64, right_halo: u64, total_samples: u64) -> Self {
58 let end = valid.end.min(total_samples);
59 let start = valid.start.min(end);
60 let read_start = start.saturating_sub(left_halo);
61 let read_end = (end + right_halo).min(total_samples);
62 let left_pad = (start - read_start) as usize;
63 Self {
64 index,
65 valid_global: start..end,
66 read_global: read_start..read_end,
67 valid_local: left_pad..left_pad + (end - start) as usize,
68 }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct ChunkSchedule {
75 windows: Vec<HaloWindow>,
76 max_read_samples: usize,
77}
78
79impl ChunkSchedule {
80 pub fn new(
82 target_range: Range<u64>,
83 batch_samples: u64,
84 left_halo: u64,
85 right_halo: u64,
86 total_samples: u64,
87 ) -> Self {
88 let start = target_range.start.min(total_samples);
89 let end = target_range.end.min(total_samples);
90 let step = batch_samples.max(1);
91
92 let mut windows = Vec::new();
93 let mut max_read_samples = 0usize;
94 let mut s0 = start;
95 let mut index = 0usize;
96
97 while s0 < end {
98 let s1 = (s0 + step).min(end);
99 let win = HaloWindow::around(index, s0..s1, left_halo, right_halo, total_samples);
100 max_read_samples = max_read_samples.max(win.read_len());
101 windows.push(win);
102
103 s0 = s1;
104 index += 1;
105 }
106
107 Self {
108 windows,
109 max_read_samples,
110 }
111 }
112
113 pub fn full_recording(
115 total_samples: u64,
116 batch_samples: u64,
117 left_halo: u64,
118 right_halo: u64,
119 ) -> Self {
120 Self::new(
121 0..total_samples,
122 batch_samples,
123 left_halo,
124 right_halo,
125 total_samples,
126 )
127 }
128
129 pub fn windows(&self) -> &[HaloWindow] {
131 &self.windows
132 }
133
134 pub fn into_windows(self) -> Vec<HaloWindow> {
136 self.windows
137 }
138
139 pub fn len(&self) -> usize {
141 self.windows.len()
142 }
143
144 pub fn is_empty(&self) -> bool {
146 self.windows.is_empty()
147 }
148
149 pub fn max_read_samples(&self) -> usize {
151 self.max_read_samples
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_chunk_schedule_partitions_without_gaps_or_overlap() {
161 let total = 10_000u64;
162 let sched = ChunkSchedule::full_recording(total, 3_000, 500, 200);
163 assert_eq!(sched.len(), 4);
164 assert_eq!(sched.max_read_samples(), 3_700);
165
166 let w = sched.windows();
167 assert_eq!(w[0].valid_global, 0..3_000);
169 assert_eq!(w[0].read_global, 0..3_200);
170 assert_eq!(w[0].valid_local, 0..3_000);
171 assert_eq!(w[0].to_global_sample(w[0].valid_local.start), 0);
172
173 assert_eq!(w[1].valid_global, 3_000..6_000);
175 assert_eq!(w[1].read_global, 2_500..6_200);
176 assert_eq!(w[1].valid_local, 500..3_500);
177 assert!(!w[1].is_interior_local(499));
178 assert!(w[1].is_interior_local(500));
179 assert_eq!(w[1].to_global_sample(500), 3_000);
180 assert_eq!(w[1].to_global_sample(3_499), 5_999);
181
182 assert_eq!(w[3].valid_global, 9_000..10_000);
184 assert_eq!(w[3].read_global, 8_500..10_000);
185 assert_eq!(w[3].valid_local, 500..1_500);
186 assert_eq!(w[3].to_global_sample(1_499), 9_999);
187 }
188
189 #[test]
190 fn around_clamps_and_remaps_events() {
191 let w = HaloWindow::around(7, 30..60, 5, 5, 62);
192 assert_eq!((w.index, w.read_global.clone(), w.valid_local.clone()), (7, 25..62, 5..35));
193 assert_eq!(w.remap_event(4), None);
194 assert_eq!(w.remap_event(5), Some(30));
195 assert_eq!(w.remap_event(34), Some(59));
196 assert_eq!(w.remap_event(35), None);
197 assert_eq!(HaloWindow::around(0, 90..120, 5, 5, 100).valid_global, 90..100);
198 }
199}