oxide_batch_core/
chunk.rs1use std::error::Error;
4use std::fmt;
5use std::num::NonZeroU32;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct ChunkSize(NonZeroU32);
10
11impl ChunkSize {
12 pub fn new(value: u32) -> Result<Self, ChunkError> {
18 NonZeroU32::new(value).map(Self).ok_or(ChunkError::ZeroSize)
19 }
20
21 #[must_use]
23 pub const fn get(self) -> u32 {
24 self.0.get()
25 }
26}
27
28#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
30pub struct ChunkCount(u64);
31
32impl ChunkCount {
33 pub const ZERO: Self = Self(0);
35
36 #[must_use]
38 pub const fn new(value: u64) -> Self {
39 Self(value)
40 }
41
42 #[must_use]
44 pub const fn get(self) -> u64 {
45 self.0
46 }
47
48 pub fn checked_add(self, other: Self) -> Result<Self, ChunkError> {
54 self.0
55 .checked_add(other.0)
56 .map(Self)
57 .ok_or(ChunkError::CountOverflow)
58 }
59
60 pub fn checked_increment(self) -> Result<Self, ChunkError> {
66 self.checked_add(Self(1))
67 }
68}
69
70#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
72pub struct ChunkCounts {
73 read: ChunkCount,
74 processed: ChunkCount,
75 written: ChunkCount,
76 filtered: ChunkCount,
77}
78
79impl ChunkCounts {
80 pub fn new(
90 read: ChunkCount,
91 processed: ChunkCount,
92 written: ChunkCount,
93 filtered: ChunkCount,
94 ) -> Result<Self, ChunkError> {
95 let classified = processed.checked_add(filtered)?;
96 if classified > read {
97 return Err(ChunkError::ClassifiedExceedsRead);
98 }
99 if written > processed {
100 return Err(ChunkError::WrittenExceedsProcessed);
101 }
102 Ok(Self {
103 read,
104 processed,
105 written,
106 filtered,
107 })
108 }
109
110 #[must_use]
112 pub const fn read(self) -> ChunkCount {
113 self.read
114 }
115
116 #[must_use]
118 pub const fn processed(self) -> ChunkCount {
119 self.processed
120 }
121
122 #[must_use]
124 pub const fn written(self) -> ChunkCount {
125 self.written
126 }
127
128 #[must_use]
130 pub const fn filtered(self) -> ChunkCount {
131 self.filtered
132 }
133
134 pub fn checked_add(self, other: Self) -> Result<Self, ChunkError> {
140 Self::new(
141 self.read.checked_add(other.read)?,
142 self.processed.checked_add(other.processed)?,
143 self.written.checked_add(other.written)?,
144 self.filtered.checked_add(other.filtered)?,
145 )
146 }
147}
148
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151pub struct ChunkProgress {
152 size: ChunkSize,
153 counts: ChunkCounts,
154}
155
156impl ChunkProgress {
157 #[must_use]
159 pub const fn new(size: ChunkSize) -> Self {
160 Self {
161 size,
162 counts: ChunkCounts {
163 read: ChunkCount::ZERO,
164 processed: ChunkCount::ZERO,
165 written: ChunkCount::ZERO,
166 filtered: ChunkCount::ZERO,
167 },
168 }
169 }
170
171 pub fn from_counts(size: ChunkSize, counts: ChunkCounts) -> Result<Self, ChunkError> {
178 if counts.read().get() > u64::from(size.get()) {
179 return Err(ChunkError::SizeExceeded);
180 }
181 Ok(Self { size, counts })
182 }
183
184 #[must_use]
186 pub const fn size(self) -> ChunkSize {
187 self.size
188 }
189
190 #[must_use]
192 pub const fn counts(self) -> ChunkCounts {
193 self.counts
194 }
195
196 #[must_use]
198 pub fn is_full(self) -> bool {
199 self.counts.read().get() == u64::from(self.size.get())
200 }
201
202 pub fn record_read(&mut self) -> Result<(), ChunkError> {
209 if self.is_full() {
210 return Err(ChunkError::SizeExceeded);
211 }
212 self.counts.read = self.counts.read.checked_increment()?;
213 Ok(())
214 }
215
216 pub fn record_processed(&mut self) -> Result<(), ChunkError> {
223 let next = self.counts.processed.checked_increment()?;
224 let classified = next.checked_add(self.counts.filtered)?;
225 if classified > self.counts.read {
226 return Err(ChunkError::ClassifiedExceedsRead);
227 }
228 self.counts.processed = next;
229 Ok(())
230 }
231
232 pub fn record_filtered(&mut self) -> Result<(), ChunkError> {
239 let next = self.counts.filtered.checked_increment()?;
240 let classified = self.counts.processed.checked_add(next)?;
241 if classified > self.counts.read {
242 return Err(ChunkError::ClassifiedExceedsRead);
243 }
244 self.counts.filtered = next;
245 Ok(())
246 }
247
248 pub fn record_written(&mut self, count: ChunkCount) -> Result<(), ChunkError> {
256 let next = self.counts.written.checked_add(count)?;
257 if next > self.counts.processed {
258 return Err(ChunkError::WrittenExceedsProcessed);
259 }
260 self.counts.written = next;
261 Ok(())
262 }
263}
264
265#[derive(Clone, Copy, Debug, Eq, PartialEq)]
267#[non_exhaustive]
268pub enum ChunkError {
269 ZeroSize,
271 CountOverflow,
273 ClassifiedExceedsRead,
275 WrittenExceedsProcessed,
277 SizeExceeded,
279}
280
281impl fmt::Display for ChunkError {
282 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
283 formatter.write_str(match self {
284 Self::ZeroSize => "chunk size must be nonzero",
285 Self::CountOverflow => "chunk count arithmetic overflowed",
286 Self::ClassifiedExceedsRead => "processed and filtered counts exceed the read count",
287 Self::WrittenExceedsProcessed => "written count exceeds the processed count",
288 Self::SizeExceeded => "read count exceeds the configured chunk size",
289 })
290 }
291}
292
293impl Error for ChunkError {}