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 /// - `required`: Current minimum total input units required before retrying
75 /// from the current input position. A later retry may raise this lower
76 /// bound.
77 /// - `read`: Number of consumed input units.
78 /// - `written`: Number of produced output units.
79 ///
80 /// # Returns
81 ///
82 /// Returns a progress value with [`TranscodeStatus::NeedInput`].
83 #[inline(always)]
84 #[must_use]
85 pub const fn need_input(
86 required: NonZeroUsize,
87 read: usize,
88 written: usize,
89 ) -> Self {
90 Self::new(TranscodeStatus::need_input(required), read, written)
91 }
92
93 /// Creates progress that stopped because more output capacity is needed.
94 ///
95 /// # Parameters
96 ///
97 /// - `required`: Total output units required from the current output
98 /// position.
99 /// - `read`: Number of consumed input units.
100 /// - `written`: Number of produced output units.
101 ///
102 /// # Returns
103 ///
104 /// Returns a progress value with [`TranscodeStatus::NeedOutput`].
105 #[inline(always)]
106 #[must_use]
107 pub const fn need_output(
108 required: NonZeroUsize,
109 read: usize,
110 written: usize,
111 ) -> Self {
112 Self::new(TranscodeStatus::need_output(required), read, written)
113 }
114
115 /// Returns the status that stopped conversion.
116 ///
117 /// # Returns
118 ///
119 /// Returns the stored [`TranscodeStatus`].
120 #[inline(always)]
121 #[must_use]
122 pub const fn status(self) -> TranscodeStatus {
123 self.status
124 }
125
126 /// Returns whether conversion consumed all currently supplied input.
127 ///
128 /// # Returns
129 ///
130 /// Returns `true` when the stored status is
131 /// [`TranscodeStatus::Complete`].
132 #[inline(always)]
133 #[must_use]
134 pub const fn is_complete(self) -> bool {
135 matches!(self.status, TranscodeStatus::Complete)
136 }
137
138 /// Returns whether conversion stopped because more input is needed.
139 ///
140 /// # Returns
141 ///
142 /// Returns `true` when the stored status is
143 /// [`TranscodeStatus::NeedInput`].
144 #[inline(always)]
145 #[must_use]
146 pub const fn is_need_input(self) -> bool {
147 matches!(self.status, TranscodeStatus::NeedInput { .. })
148 }
149
150 /// Returns whether conversion stopped because more output capacity is
151 /// needed.
152 ///
153 /// # Returns
154 ///
155 /// Returns `true` when the stored status is
156 /// [`TranscodeStatus::NeedOutput`].
157 #[inline(always)]
158 #[must_use]
159 pub const fn is_need_output(self) -> bool {
160 matches!(self.status, TranscodeStatus::NeedOutput { .. })
161 }
162
163 /// Returns the number of input units consumed by the call.
164 ///
165 /// # Returns
166 ///
167 /// Returns a count relative to the input index passed to the conversion
168 /// call.
169 #[inline(always)]
170 #[must_use]
171 pub const fn read(self) -> usize {
172 self.read
173 }
174
175 /// Returns the number of output units written by the call.
176 ///
177 /// # Returns
178 ///
179 /// Returns a count relative to the output index passed to the conversion
180 /// call.
181 #[inline(always)]
182 #[must_use]
183 pub const fn written(self) -> usize {
184 self.written
185 }
186
187 /// Validates this progress against the call bounds supplied to a
188 /// transcoder.
189 ///
190 /// Buffered drivers should call this before using [`Self::read`] or
191 /// [`Self::written`] to advance unchecked input or output cursors. The
192 /// method checks relative counters, unsatisfied `NeedInput` / `NeedOutput`
193 /// requirements, and the `Complete` invariant that all visible input was
194 /// consumed.
195 ///
196 /// This is a contract checker, not a semantic recovery policy. It performs
197 /// the same validation in every build profile. Drivers that advance unsafe
198 /// cursors may convert failures into their own error type, while
199 /// convenience helpers may assert that validation succeeds. Custom
200 /// [`crate::Transcoder`] implementations must therefore return progress
201 /// that satisfies the documented contract in all builds.
202 ///
203 /// # Parameters
204 ///
205 /// - `input_index`: Input index originally passed to the transcoder.
206 /// - `available_input`: Number of input units visible from `input_index`.
207 /// - `output_index`: Output index originally passed to the transcoder.
208 /// - `available_output`: Number of output slots visible from
209 /// `output_index`.
210 ///
211 /// # Returns
212 ///
213 /// Returns `Ok(())` when progress is internally consistent with the
214 /// supplied call bounds.
215 ///
216 /// # Errors
217 ///
218 /// Returns [`TranscodeContractError`] when a custom transcoder reports
219 /// counters or missing-capacity requirements that do not match the buffers
220 /// supplied by the caller.
221 pub fn validate(
222 &self,
223 input_index: usize,
224 available_input: usize,
225 output_index: usize,
226 available_output: usize,
227 ) -> Result<(), TranscodeContractError> {
228 if self.read > available_input {
229 return Err(TranscodeContractError::OverRead {
230 read: self.read,
231 available: available_input,
232 });
233 }
234 if self.written > available_output {
235 return Err(TranscodeContractError::OverWritten {
236 written: self.written,
237 available: available_output,
238 });
239 }
240
241 input_index.checked_add(self.read).ok_or(
242 TranscodeContractError::ProgressIndexOverflow {
243 index: input_index,
244 advanced: self.read,
245 },
246 )?;
247 output_index.checked_add(self.written).ok_or(
248 TranscodeContractError::ProgressIndexOverflow {
249 index: output_index,
250 advanced: self.written,
251 },
252 )?;
253 match self.status {
254 TranscodeStatus::Complete => {
255 if self.read != available_input {
256 return Err(
257 TranscodeContractError::CompleteWithRemainingInput {
258 read: self.read,
259 available: available_input,
260 },
261 );
262 }
263 Ok(())
264 }
265 TranscodeStatus::NeedInput { required } => {
266 let available = available_input - self.read;
267 if required.get() <= available {
268 return Err(TranscodeContractError::SatisfiedNeed {
269 required: required.get(),
270 available,
271 });
272 }
273 Ok(())
274 }
275 TranscodeStatus::NeedOutput { required } => {
276 let available = available_output - self.written;
277 if required.get() <= available {
278 return Err(TranscodeContractError::SatisfiedNeed {
279 required: required.get(),
280 available,
281 });
282 }
283 Ok(())
284 }
285 }
286 }
287}