qubit_codec/transcode/transcode_progress.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use core::num::NonZeroUsize;
9
10use super::{
11 TranscodeContractError,
12 TranscodeStatus,
13};
14
15/// Counts how much work a [`crate::Transcoder`] completed before
16/// returning.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct TranscodeProgress {
19 /// Stop reason reported by the transcoder.
20 status: TranscodeStatus,
21 /// Number of input units consumed from the requested input index.
22 read: usize,
23 /// Number of output units written from the requested output index.
24 written: usize,
25}
26
27impl TranscodeProgress {
28 /// Creates a progress value.
29 ///
30 /// # Parameters
31 ///
32 /// - `status`: The reason conversion stopped.
33 /// - `read`: Number of input units consumed from the call's input index.
34 /// - `written`: Number of output units written from the call's output
35 /// index.
36 ///
37 /// # Returns
38 ///
39 /// Returns a progress value carrying the supplied counters.
40 #[inline(always)]
41 #[must_use]
42 pub const fn new(
43 status: TranscodeStatus,
44 read: usize,
45 written: usize,
46 ) -> Self {
47 Self {
48 status,
49 read,
50 written,
51 }
52 }
53
54 /// Creates a completed progress value.
55 ///
56 /// # Parameters
57 ///
58 /// - `read`: Number of consumed input units.
59 /// - `written`: Number of produced output units.
60 ///
61 /// # Returns
62 ///
63 /// Returns a progress value whose status is [`TranscodeStatus::Complete`].
64 #[inline(always)]
65 #[must_use]
66 pub const fn complete(read: usize, written: usize) -> Self {
67 Self::new(TranscodeStatus::Complete, read, written)
68 }
69
70 /// Creates progress that stopped because more input is needed.
71 ///
72 /// # Parameters
73 ///
74 /// - `input_index`: Absolute input boundary where conversion stopped.
75 /// - `required`: Total input units required from the current input
76 /// position.
77 /// - `available`: Input units currently available at the boundary.
78 /// - `read`: Number of consumed input units.
79 /// - `written`: Number of produced output units.
80 ///
81 /// # Returns
82 ///
83 /// Returns a progress value with [`TranscodeStatus::NeedInput`].
84 #[inline(always)]
85 #[must_use]
86 pub const fn need_input(
87 input_index: usize,
88 required: NonZeroUsize,
89 available: usize,
90 read: usize,
91 written: usize,
92 ) -> Self {
93 Self::new(
94 TranscodeStatus::need_input(input_index, required, available),
95 read,
96 written,
97 )
98 }
99
100 /// Creates progress that stopped because more output capacity is needed.
101 ///
102 /// # Parameters
103 ///
104 /// - `output_index`: Absolute output boundary where conversion stopped.
105 /// - `required`: Total output units required from the current output
106 /// position.
107 /// - `available`: Output units currently available at the boundary.
108 /// - `read`: Number of consumed input units.
109 /// - `written`: Number of produced output units.
110 ///
111 /// # Returns
112 ///
113 /// Returns a progress value with [`TranscodeStatus::NeedOutput`].
114 #[inline(always)]
115 #[must_use]
116 pub const fn need_output(
117 output_index: usize,
118 required: NonZeroUsize,
119 available: usize,
120 read: usize,
121 written: usize,
122 ) -> Self {
123 Self::new(
124 TranscodeStatus::need_output(output_index, required, available),
125 read,
126 written,
127 )
128 }
129
130 /// Returns the status that stopped conversion.
131 ///
132 /// # Returns
133 ///
134 /// Returns the stored [`TranscodeStatus`].
135 #[inline(always)]
136 #[must_use]
137 pub const fn status(self) -> TranscodeStatus {
138 self.status
139 }
140
141 /// Returns whether conversion consumed all currently supplied input.
142 ///
143 /// # Returns
144 ///
145 /// Returns `true` when the stored status is
146 /// [`TranscodeStatus::Complete`].
147 #[inline(always)]
148 #[must_use]
149 pub const fn is_complete(self) -> bool {
150 matches!(self.status, TranscodeStatus::Complete)
151 }
152
153 /// Returns whether conversion stopped because more input is needed.
154 ///
155 /// # Returns
156 ///
157 /// Returns `true` when the stored status is
158 /// [`TranscodeStatus::NeedInput`].
159 #[inline(always)]
160 #[must_use]
161 pub const fn is_need_input(self) -> bool {
162 matches!(self.status, TranscodeStatus::NeedInput { .. })
163 }
164
165 /// Returns whether conversion stopped because more output capacity is
166 /// needed.
167 ///
168 /// # Returns
169 ///
170 /// Returns `true` when the stored status is
171 /// [`TranscodeStatus::NeedOutput`].
172 #[inline(always)]
173 #[must_use]
174 pub const fn is_need_output(self) -> bool {
175 matches!(self.status, TranscodeStatus::NeedOutput { .. })
176 }
177
178 /// Returns the number of input units consumed by the call.
179 ///
180 /// # Returns
181 ///
182 /// Returns a count relative to the input index passed to the conversion
183 /// call.
184 #[inline(always)]
185 #[must_use]
186 pub const fn read(self) -> usize {
187 self.read
188 }
189
190 /// Returns the number of output units written by the call.
191 ///
192 /// # Returns
193 ///
194 /// Returns a count relative to the output index passed to the conversion
195 /// call.
196 #[inline(always)]
197 #[must_use]
198 pub const fn written(self) -> usize {
199 self.written
200 }
201
202 /// Validates this progress against the call bounds supplied to a
203 /// transcoder.
204 ///
205 /// Buffered drivers should call this before using [`Self::read`] or
206 /// [`Self::written`] to advance unchecked input or output cursors. The
207 /// method checks relative counters, absolute status indices, and
208 /// unsatisfied `NeedInput` / `NeedOutput` requirements.
209 ///
210 /// # Parameters
211 ///
212 /// - `input_index`: Input index originally passed to the transcoder.
213 /// - `available_input`: Number of input units visible from `input_index`.
214 /// - `output_index`: Output index originally passed to the transcoder.
215 /// - `available_output`: Number of output slots visible from
216 /// `output_index`.
217 ///
218 /// # Returns
219 ///
220 /// Returns `Ok(())` when progress is internally consistent with the
221 /// supplied call bounds.
222 ///
223 /// # Errors
224 ///
225 /// Returns [`TranscodeContractError`] when a custom transcoder reports
226 /// counters, status indices, or missing-capacity requirements that do not
227 /// match the buffers supplied by the caller.
228 pub fn validate(
229 &self,
230 input_index: usize,
231 available_input: usize,
232 output_index: usize,
233 available_output: usize,
234 ) -> Result<(), TranscodeContractError> {
235 if self.read > available_input {
236 return Err(TranscodeContractError::OverRead {
237 read: self.read,
238 available: available_input,
239 });
240 }
241 if self.written > available_output {
242 return Err(TranscodeContractError::OverWritten {
243 written: self.written,
244 available: available_output,
245 });
246 }
247
248 let expected_input_index = input_index.checked_add(self.read).ok_or(
249 TranscodeContractError::ProgressIndexOverflow {
250 index: input_index,
251 advanced: self.read,
252 },
253 )?;
254 let expected_output_index = output_index
255 .checked_add(self.written)
256 .ok_or(TranscodeContractError::ProgressIndexOverflow {
257 index: output_index,
258 advanced: self.written,
259 })?;
260
261 match self.status {
262 TranscodeStatus::Complete => Ok(()),
263 TranscodeStatus::NeedInput {
264 input_index,
265 required,
266 available,
267 } => {
268 if input_index != expected_input_index {
269 return Err(TranscodeContractError::StatusIndexMismatch {
270 reported: input_index,
271 expected: expected_input_index,
272 });
273 }
274 if required.get() <= available {
275 return Err(TranscodeContractError::SatisfiedNeed {
276 required: required.get(),
277 available,
278 });
279 }
280 Ok(())
281 }
282 TranscodeStatus::NeedOutput {
283 output_index,
284 required,
285 available,
286 } => {
287 if output_index != expected_output_index {
288 return Err(TranscodeContractError::StatusIndexMismatch {
289 reported: output_index,
290 expected: expected_output_index,
291 });
292 }
293 if required.get() <= available {
294 return Err(TranscodeContractError::SatisfiedNeed {
295 required: required.get(),
296 available,
297 });
298 }
299 Ok(())
300 }
301 }
302 }
303}