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::{
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}