Skip to main content

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::TranscodeContractError;
11use super::TranscodeStatus;
12
13/// Counts how much work a [`crate::Transcoder`] completed before
14/// returning.
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16pub struct TranscodeProgress {
17    /// Stop reason reported by the transcoder.
18    status: TranscodeStatus,
19    /// Number of input units consumed from the requested input index.
20    read: usize,
21    /// Number of output units written from the requested output index.
22    written: usize,
23}
24
25impl TranscodeProgress {
26    /// Creates a progress value.
27    ///
28    /// # Parameters
29    ///
30    /// - `status`: The reason conversion stopped.
31    /// - `read`: Number of input units consumed from the call's input index.
32    /// - `written`: Number of output units written from the call's output
33    ///   index.
34    ///
35    /// # Returns
36    ///
37    /// Returns a progress value carrying the supplied counters.
38    #[inline(always)]
39    #[must_use]
40    pub const fn new(status: TranscodeStatus, read: usize, written: usize) -> Self {
41        Self { status, read, written }
42    }
43
44    /// Creates a completed progress value.
45    ///
46    /// # Parameters
47    ///
48    /// - `read`: Number of consumed input units.
49    /// - `written`: Number of produced output units.
50    ///
51    /// # Returns
52    ///
53    /// Returns a progress value whose status is [`TranscodeStatus::Complete`].
54    #[inline(always)]
55    #[must_use]
56    pub const fn complete(read: usize, written: usize) -> Self {
57        Self::new(TranscodeStatus::Complete, read, written)
58    }
59
60    /// Creates progress that stopped because more input is needed.
61    ///
62    /// # Parameters
63    ///
64    /// - `required`: Current minimum total input units required before retrying
65    ///   from the current input position. A later retry may raise this lower
66    ///   bound.
67    /// - `read`: Number of consumed input units.
68    /// - `written`: Number of produced output units.
69    ///
70    /// # Returns
71    ///
72    /// Returns a progress value with [`TranscodeStatus::NeedInput`].
73    #[inline(always)]
74    #[must_use]
75    pub const fn need_input(required: NonZeroUsize, read: usize, written: usize) -> Self {
76        Self::new(TranscodeStatus::need_input(required), read, written)
77    }
78
79    /// Creates progress that stopped because more output capacity is needed.
80    ///
81    /// # Parameters
82    ///
83    /// - `required`: Total output units required from the current output
84    ///   position.
85    /// - `read`: Number of consumed input units.
86    /// - `written`: Number of produced output units.
87    ///
88    /// # Returns
89    ///
90    /// Returns a progress value with [`TranscodeStatus::NeedOutput`].
91    #[inline(always)]
92    #[must_use]
93    pub const fn need_output(required: NonZeroUsize, read: usize, written: usize) -> Self {
94        Self::new(TranscodeStatus::need_output(required), read, written)
95    }
96
97    /// Returns the status that stopped conversion.
98    ///
99    /// # Returns
100    ///
101    /// Returns the stored [`TranscodeStatus`].
102    #[inline(always)]
103    #[must_use]
104    pub const fn status(self) -> TranscodeStatus {
105        self.status
106    }
107
108    /// Returns whether conversion consumed all currently supplied input.
109    ///
110    /// # Returns
111    ///
112    /// Returns `true` when the stored status is
113    /// [`TranscodeStatus::Complete`].
114    #[inline(always)]
115    #[must_use]
116    pub const fn is_complete(self) -> bool {
117        matches!(self.status, TranscodeStatus::Complete)
118    }
119
120    /// Returns whether conversion stopped because more input is needed.
121    ///
122    /// # Returns
123    ///
124    /// Returns `true` when the stored status is
125    /// [`TranscodeStatus::NeedInput`].
126    #[inline(always)]
127    #[must_use]
128    pub const fn is_need_input(self) -> bool {
129        matches!(self.status, TranscodeStatus::NeedInput { .. })
130    }
131
132    /// Returns whether conversion stopped because more output capacity is
133    /// needed.
134    ///
135    /// # Returns
136    ///
137    /// Returns `true` when the stored status is
138    /// [`TranscodeStatus::NeedOutput`].
139    #[inline(always)]
140    #[must_use]
141    pub const fn is_need_output(self) -> bool {
142        matches!(self.status, TranscodeStatus::NeedOutput { .. })
143    }
144
145    /// Returns the number of input units consumed by the call.
146    ///
147    /// # Returns
148    ///
149    /// Returns a count relative to the input index passed to the conversion
150    /// call.
151    #[inline(always)]
152    #[must_use]
153    pub const fn read(self) -> usize {
154        self.read
155    }
156
157    /// Returns the number of output units written by the call.
158    ///
159    /// # Returns
160    ///
161    /// Returns a count relative to the output index passed to the conversion
162    /// call.
163    #[inline(always)]
164    #[must_use]
165    pub const fn written(self) -> usize {
166        self.written
167    }
168
169    /// Validates this progress against the call bounds supplied to a
170    /// transcoder.
171    ///
172    /// Buffered drivers should call this before using [`Self::read`] or
173    /// [`Self::written`] to advance unchecked input or output cursors. The
174    /// method checks relative counters, unsatisfied `NeedInput` / `NeedOutput`
175    /// requirements, and the `Complete` invariant that all visible input was
176    /// consumed.
177    ///
178    /// This is a contract checker, not a semantic recovery policy. It performs
179    /// the same validation in every build profile. Drivers that advance unsafe
180    /// cursors may convert failures into their own error type, while
181    /// convenience helpers may assert that validation succeeds. Custom
182    /// [`crate::Transcoder`] implementations must therefore return progress
183    /// that satisfies the documented contract in all builds.
184    ///
185    /// # Parameters
186    ///
187    /// - `input_index`: Input index originally passed to the transcoder.
188    /// - `available_input`: Number of input units visible from `input_index`.
189    /// - `output_index`: Output index originally passed to the transcoder.
190    /// - `available_output`: Number of output slots visible from
191    ///   `output_index`.
192    ///
193    /// # Returns
194    ///
195    /// Returns `Ok(())` when progress is internally consistent with the
196    /// supplied call bounds.
197    ///
198    /// # Errors
199    ///
200    /// Returns [`TranscodeContractError`] when a custom transcoder reports
201    /// counters or missing-capacity requirements that do not match the buffers
202    /// supplied by the caller.
203    pub fn validate(
204        &self,
205        input_index: usize,
206        available_input: usize,
207        output_index: usize,
208        available_output: usize,
209    ) -> Result<(), TranscodeContractError> {
210        if self.read > available_input {
211            return Err(TranscodeContractError::OverRead {
212                read: self.read,
213                available: available_input,
214            });
215        }
216        if self.written > available_output {
217            return Err(TranscodeContractError::OverWritten {
218                written: self.written,
219                available: available_output,
220            });
221        }
222
223        input_index
224            .checked_add(self.read)
225            .ok_or(TranscodeContractError::ProgressIndexOverflow {
226                index: input_index,
227                advanced: self.read,
228            })?;
229        output_index
230            .checked_add(self.written)
231            .ok_or(TranscodeContractError::ProgressIndexOverflow {
232                index: output_index,
233                advanced: self.written,
234            })?;
235        match self.status {
236            TranscodeStatus::Complete => {
237                if self.read != available_input {
238                    return Err(TranscodeContractError::CompleteWithRemainingInput {
239                        read: self.read,
240                        available: available_input,
241                    });
242                }
243                Ok(())
244            }
245            TranscodeStatus::NeedInput { required } => {
246                let available = available_input - self.read;
247                if required.get() <= available {
248                    return Err(TranscodeContractError::SatisfiedNeed {
249                        required: required.get(),
250                        available,
251                    });
252                }
253                Ok(())
254            }
255            TranscodeStatus::NeedOutput { required } => {
256                let available = available_output - self.written;
257                if required.get() <= available {
258                    return Err(TranscodeContractError::SatisfiedNeed {
259                        required: required.get(),
260                        available,
261                    });
262                }
263                Ok(())
264            }
265        }
266    }
267}