Skip to main content

qubit_codec/transcode/io/
async_transcode_encode_output.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// =============================================================================
8//! Buffered asynchronous output driver for streaming transcoders.
9
10use core::fmt;
11use std::collections::TryReserveError;
12use std::future::poll_fn;
13use std::io::Error;
14use std::io::ErrorKind;
15use std::io::Result;
16use std::pin::Pin;
17use std::task::Context;
18use std::task::Poll;
19
20use qubit_io::AsyncBufferedOutput;
21use qubit_io::AsyncOutput;
22use qubit_io::Buffer;
23use qubit_utils::SliceRange;
24use qubit_utils::allocation_error;
25
26use super::transcode_progress_validation::validate_encode_progress;
27use crate::CapacityError;
28use crate::TranscodeEncoder;
29use crate::TranscodeProgress;
30use crate::TranscodeStatus;
31use crate::Transcoder;
32
33/// Buffers asynchronous transcoder output while preserving pending units.
34///
35/// This adapter is the asynchronous counterpart to
36/// [`super::TranscodeEncodeOutput`]. It owns the unit-level buffer, so a
37/// future cancelled while the wrapped output is pending retains every unit
38/// that the transcoder has already produced. Each transcode operation returns
39/// after exactly one encoder invocation, so its progress is the explicit
40/// source-consumption commit boundary.
41///
42/// # Type Parameters
43///
44/// * `O` - Wrapped asynchronous unit output.
45///
46/// # Examples
47///
48/// ```
49/// use qubit_codec::AsyncTranscodeEncodeOutput;
50/// use qubit_io::AsyncOutput;
51///
52/// fn make_output<O: AsyncOutput<Item = u8>>(inner: O) -> AsyncTranscodeEncodeOutput<O> {
53///     AsyncTranscodeEncodeOutput::new(inner)
54/// }
55/// ```
56pub struct AsyncTranscodeEncodeOutput<O>
57where
58    O: AsyncOutput,
59    O::Item: Clone + Default,
60{
61    output: AsyncBufferedOutput<O>,
62    required_spare: usize,
63}
64
65impl<O> AsyncTranscodeEncodeOutput<O>
66where
67    O: AsyncOutput,
68    O::Item: Clone + Default,
69{
70    /// Creates an asynchronous encoder output with the default unit capacity.
71    ///
72    /// # Parameters
73    ///
74    /// * `inner` - Unit output receiving transcoder data.
75    ///
76    /// # Returns
77    ///
78    /// Returns a buffered asynchronous output adapter.
79    #[must_use]
80    pub fn new(inner: O) -> Self {
81        Self {
82            output: AsyncBufferedOutput::new(inner),
83            required_spare: 1,
84        }
85    }
86
87    /// Creates an asynchronous encoder output with at least `capacity` units.
88    ///
89    /// # Parameters
90    ///
91    /// * `inner` - Unit output receiving transcoder data.
92    /// * `capacity` - Requested internal unit capacity.
93    ///
94    /// # Returns
95    ///
96    /// Returns a buffered asynchronous output adapter.
97    #[must_use]
98    pub fn with_capacity(inner: O, capacity: usize) -> Self {
99        Self {
100            output: AsyncBufferedOutput::with_capacity(inner, capacity),
101            required_spare: 1,
102        }
103    }
104
105    /// Tries to create an adapter with at least `capacity` buffered units.
106    ///
107    /// # Errors
108    ///
109    /// Returns an allocation error when the internal unit buffer cannot be
110    /// allocated.
111    pub fn try_with_capacity(inner: O, capacity: usize) -> std::result::Result<Self, TryReserveError> {
112        Ok(Self {
113            output: AsyncBufferedOutput::try_with_capacity(inner, capacity)?,
114            required_spare: 1,
115        })
116    }
117
118    /// Returns the total internal unit-buffer capacity.
119    #[must_use]
120    pub fn capacity(&self) -> usize {
121        self.output.capacity()
122    }
123
124    /// Returns a shared reference to the wrapped asynchronous output.
125    ///
126    /// # Returns
127    ///
128    /// Returns the wrapped output. Units can still be pending in this adapter.
129    #[must_use]
130    pub const fn inner(&self) -> &O {
131        self.output.inner()
132    }
133
134    /// Returns a mutable reference to the wrapped asynchronous output.
135    ///
136    /// Pending adapter units are not automatically delivered before direct
137    /// output operations. Call [`Self::flush_async`] first when ordering with
138    /// previously transcoded data matters.
139    ///
140    /// # Returns
141    ///
142    /// Returns the wrapped output.
143    pub fn inner_mut(&mut self) -> &mut O {
144        self.output.inner_mut()
145    }
146
147    /// Returns the number of units retained pending delivery.
148    ///
149    /// # Returns
150    ///
151    /// Returns the length of the pending unit window.
152    #[must_use]
153    pub const fn pending_len(&self) -> usize {
154        self.output.pending_len()
155    }
156
157    /// Consumes this adapter without flushing the wrapped output.
158    ///
159    /// This method performs no asynchronous I/O. The returned buffer holds
160    /// units that must be delivered before the logical stream can continue.
161    ///
162    /// # Returns
163    ///
164    /// Returns the wrapped output and its pending unit buffer.
165    #[must_use = "the returned output and pending buffer must be handled"]
166    pub fn into_parts(self) -> (O, Buffer<O::Item>) {
167        self.output.into_parts()
168    }
169}
170
171impl<O> AsyncTranscodeEncodeOutput<O>
172where
173    O: AsyncOutput + Unpin,
174    O::Item: Clone + Default + Unpin,
175{
176    /// Flushes pending transcoded units and the wrapped asynchronous output.
177    ///
178    /// # Errors
179    ///
180    /// Returns output-delivery or flush errors from the wrapped output. A
181    /// failed delivery retains every unit that was not accepted.
182    pub async fn flush_async(&mut self) -> Result<()> {
183        self.output.flush_async().await
184    }
185
186    /// Delivers pending transcoded units without flushing the wrapped output.
187    ///
188    /// # Errors
189    ///
190    /// Returns output-delivery errors from the wrapped output. A failed
191    /// delivery retains every unit that was not accepted.
192    pub async fn drain_async(&mut self) -> Result<()> {
193        let capacity = self.output.capacity();
194        self.output.ensure_spare_capacity_async(capacity).await
195    }
196
197    /// Runs encoder reset and buffers its stream-prefix units.
198    ///
199    /// # Parameters
200    ///
201    /// * `encoder` - Streaming encoder whose lifecycle is being started.
202    /// * `map_error` - Maps transcoder errors into I/O errors.
203    ///
204    /// # Errors
205    ///
206    /// Returns capacity planning, output delivery, allocation, or mapped reset
207    /// errors. Pending units remain retained if asynchronous delivery fails.
208    ///
209    /// # Panics
210    ///
211    /// Panics when `encoder` writes more units than its declared reset bound.
212    pub async fn reset_async<E, M, Value>(&mut self, encoder: &mut E, map_error: &mut M) -> Result<()>
213    where
214        E: Transcoder<Input = Value, Output = O::Item>,
215        M: FnMut(E::Error) -> Error,
216    {
217        let required = encoder.max_reset_output_len().map_err(capacity_error_to_invalid_data)?;
218        self.ensure_spare_capacity_async(required).await?;
219        let (units, output_index, available) = self.output.spare_raw_parts_mut();
220        debug_assert!(available >= required);
221        let written = encoder.reset(units, output_index).map_err(&mut *map_error)?;
222        assert!(written <= required, "reset wrote beyond its bound");
223        // SAFETY: `written` is bounded by the reserved spare range above.
224        unsafe {
225            self.output.advance(written);
226        }
227        Ok(())
228    }
229
230    /// Polls one cancellation-safe encoder operation.
231    ///
232    /// The adapter may first deliver previously pending output to make a
233    /// conservative spare range available. Once it invokes `encoder`, it
234    /// advances the produced units and returns immediately without another
235    /// poll. The returned [`TranscodeProgress`] is therefore the exact source
236    /// range committed by this call.
237    ///
238    /// # Parameters
239    ///
240    /// * `encoder` - Streaming encoder used for the conversion.
241    /// * `map_error` - Maps transcoder errors into I/O errors.
242    /// * `input` - Source values.
243    /// * `input_index` - First source value to transcode.
244    /// * `count` - Number of source values available from `input_index`.
245    ///
246    /// # Returns
247    ///
248    /// Returns one validated encoder progress report.
249    ///
250    /// # Errors
251    ///
252    /// Returns invalid input ranges, output delivery, allocation, contract, or
253    /// mapped transcoder errors. Pending units remain retained after a failed
254    /// asynchronous delivery.
255    pub fn poll_transcode<E, M, Value>(
256        mut self: Pin<&mut Self>,
257        cx: &mut Context<'_>,
258        encoder: &mut E,
259        map_error: &mut M,
260        input: &[Value],
261        input_index: usize,
262        count: usize,
263    ) -> Poll<Result<TranscodeProgress>>
264    where
265        E: TranscodeEncoder<Input = Value, Output = O::Item>,
266        M: FnMut(E::Error) -> Error,
267    {
268        let input_end = SliceRange::checked_range_end(
269            input.len(),
270            input_index,
271            count,
272            "encode input range exceeds source buffer",
273        )?;
274        if count == 0 {
275            return Poll::Ready(Ok(TranscodeProgress::complete(0, 0)));
276        }
277        let input = &input[..input_end];
278        let this = self.as_mut().get_mut();
279        let per_value = match encoder.max_transcode_output_len(1) {
280            Ok(required) => required.max(1),
281            Err(error) => {
282                return Poll::Ready(Err(capacity_error_to_invalid_data(error)));
283            }
284        };
285        let required_spare = this.required_spare.max(per_value);
286        let required_capacity = this.output.pending_len().saturating_add(required_spare);
287        if let Err(error) = this.output.try_reserve_capacity(required_capacity) {
288            return Poll::Ready(Err(allocation_error(error)));
289        }
290        match Pin::new(&mut this.output).poll_ensure_spare_capacity(cx, required_spare) {
291            Poll::Ready(Ok(())) => {}
292            Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
293            Poll::Pending => return Poll::Pending,
294        }
295        let (units, output_index, available_output) = this.output.spare_raw_parts_mut();
296        let progress = encoder
297            .transcode(input, input_index, units, output_index)
298            .map_err(&mut *map_error)
299            .and_then(|progress| {
300                validate_encode_progress(progress, input_index, count, output_index, available_output)
301            });
302        let progress = match progress {
303            Ok(progress) => progress,
304            Err(error) => return Poll::Ready(Err(error)),
305        };
306        // SAFETY: Progress validation proves the initialized output count fits
307        // the spare window provided to the encoder.
308        unsafe {
309            this.output.advance(progress.written());
310        }
311        this.required_spare = if let TranscodeStatus::NeedOutput { required, .. } = progress.status() {
312            required.get()
313        } else {
314            // `validate_encode_progress` rejects `NeedInput` before this
315            // point.
316            debug_assert!(progress.is_complete());
317            1
318        };
319        Poll::Ready(Ok(progress))
320    }
321
322    /// Transcodes one cancellation-safe progress step into the retained buffer.
323    ///
324    /// This is the async wrapper for [`Self::poll_transcode`]. It returns after
325    /// one encoder invocation; callers that need to consume a whole source
326    /// range must advance the source index by [`TranscodeProgress::read`] and
327    /// call it again.
328    pub async fn transcode_async<E, M, Value>(
329        &mut self,
330        encoder: &mut E,
331        map_error: &mut M,
332        input: &[Value],
333        input_index: usize,
334        count: usize,
335    ) -> Result<TranscodeProgress>
336    where
337        E: TranscodeEncoder<Input = Value, Output = O::Item>,
338        M: FnMut(E::Error) -> Error,
339    {
340        poll_fn(|cx| Pin::new(&mut *self).poll_transcode(cx, encoder, map_error, input, input_index, count)).await
341    }
342
343    /// Finishes the encoder and retains its final output for delivery.
344    ///
345    /// # Parameters
346    ///
347    /// * `encoder` - Streaming encoder whose lifecycle is being finished.
348    /// * `map_error` - Maps transcoder errors into I/O errors.
349    ///
350    /// # Errors
351    ///
352    /// Returns capacity planning, allocation, or mapped finish errors. Call
353    /// [`Self::flush_async`] after this method to deliver final units. Keeping
354    /// finalization separate from delivery lets a caller record the completed
355    /// lifecycle before any later suspension point.
356    ///
357    /// # Panics
358    ///
359    /// Panics when `encoder` writes more units than its declared finish bound.
360    pub async fn finish_async<E, M, Value>(&mut self, encoder: &mut E, map_error: &mut M) -> Result<()>
361    where
362        E: Transcoder<Input = Value, Output = O::Item>,
363        M: FnMut(E::Error) -> Error,
364    {
365        let required = encoder
366            .max_finish_output_len()
367            .map_err(capacity_error_to_invalid_data)?;
368        self.ensure_spare_capacity_async(required).await?;
369        let (units, output_index, available) = self.output.spare_raw_parts_mut();
370        debug_assert!(available >= required);
371        let written = encoder.finish(units, output_index).map_err(&mut *map_error)?;
372        assert!(written <= required, "finish wrote beyond its bound");
373        // SAFETY: `written` is bounded by the reserved spare range above.
374        unsafe {
375            self.output.advance(written);
376        }
377        Ok(())
378    }
379
380    /// Reserves enough total buffer capacity and makes `count` spare slots
381    /// available for one transcoder operation.
382    async fn ensure_spare_capacity_async(&mut self, count: usize) -> Result<()> {
383        let required_capacity = self.output.pending_len().saturating_add(count);
384        self.output
385            .try_reserve_capacity(required_capacity)
386            .map_err(allocation_error)?;
387        self.output.ensure_spare_capacity_async(count).await
388    }
389}
390
391/// Converts capacity planning failures into invalid asynchronous stream data.
392fn capacity_error_to_invalid_data(error: CapacityError) -> Error {
393    Error::new(ErrorKind::InvalidData, error)
394}
395
396impl<O> fmt::Debug for AsyncTranscodeEncodeOutput<O>
397where
398    O: AsyncOutput,
399    O::Item: Clone + Default,
400    AsyncBufferedOutput<O>: fmt::Debug,
401{
402    /// Formats this asynchronous output adapter for debugging.
403    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
404        formatter
405            .debug_struct("AsyncTranscodeEncodeOutput")
406            .field("output", &self.output)
407            .finish()
408    }
409}