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    /// - `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}