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 std::io::Result;
50/// use std::pin::Pin;
51/// use std::task::Context;
52/// use std::task::Poll;
53///
54/// use qubit_codec::AsyncTranscodeEncodeOutput;
55/// use qubit_io::AsyncOutput;
56///
57/// struct Sink;
58/// impl AsyncOutput for Sink {
59///     type Item = u8;
60///     unsafe fn poll_write_unchecked(
61///         self: Pin<&mut Self>, _: &mut Context<'_>, _: &[u8], _: usize, count: usize,
62///     ) -> Poll<Result<usize>> {
63///         Poll::Ready(Ok(count))
64///     }
65///     fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<()>> {
66///         Poll::Ready(Ok(()))
67///     }
68/// }
69/// let output = AsyncTranscodeEncodeOutput::with_capacity(Sink, 8);
70/// assert!(output.capacity() >= 8);
71/// assert_eq!(output.pending_len(), 0);
72/// let (_sink, pending) = output.into_parts();
73/// assert!(pending.is_empty());
74/// ```
75pub struct AsyncTranscodeEncodeOutput<O>
76where
77    O: AsyncOutput,
78    O::Item: Clone + Default,
79{
80    /// Owned output buffer retaining units until the wrapped sink accepts them.
81    output: AsyncBufferedOutput<O>,
82    /// Minimum spare units requested by the previous encoder progress report.
83    required_spare: usize,
84}
85
86impl<O> AsyncTranscodeEncodeOutput<O>
87where
88    O: AsyncOutput,
89    O::Item: Clone + Default,
90{
91    /// Creates an asynchronous encoder output with the default unit capacity.
92    ///
93    /// # Parameters
94    ///
95    /// * `inner` - Unit output receiving transcoder data.
96    ///
97    /// # Returns
98    ///
99    /// Returns a buffered asynchronous output adapter.
100    #[must_use]
101    pub fn new(inner: O) -> Self {
102        Self {
103            output: AsyncBufferedOutput::new(inner),
104            required_spare: 1,
105        }
106    }
107
108    /// Creates an asynchronous encoder output with at least `capacity` units.
109    ///
110    /// # Parameters
111    ///
112    /// * `inner` - Unit output receiving transcoder data.
113    /// * `capacity` - Requested internal unit capacity.
114    ///
115    /// # Returns
116    ///
117    /// Returns a buffered asynchronous output adapter.
118    #[must_use]
119    pub fn with_capacity(inner: O, capacity: usize) -> Self {
120        Self {
121            output: AsyncBufferedOutput::with_capacity(inner, capacity),
122            required_spare: 1,
123        }
124    }
125
126    /// Tries to create an adapter with at least `capacity` buffered units.
127    ///
128    /// # Parameters
129    ///
130    /// * `inner` - Output moved into the adapter, including on allocation
131    ///   failure.
132    /// * `capacity` - Minimum number of units to reserve.
133    ///
134    /// # Returns
135    ///
136    /// Returns an empty buffered adapter on successful allocation.
137    ///
138    /// # Errors
139    ///
140    /// Returns an allocation error when the internal unit buffer cannot be
141    /// allocated.
142    pub fn try_with_capacity(inner: O, capacity: usize) -> std::result::Result<Self, TryReserveError> {
143        Ok(Self {
144            output: AsyncBufferedOutput::try_with_capacity(inner, capacity)?,
145            required_spare: 1,
146        })
147    }
148
149    /// Returns the total internal unit-buffer capacity.
150    ///
151    /// # Returns
152    ///
153    /// The allocated unit capacity, including occupied and spare slots.
154    #[must_use]
155    #[inline]
156    pub fn capacity(&self) -> usize {
157        self.output.capacity()
158    }
159
160    /// Returns a shared reference to the wrapped asynchronous output.
161    ///
162    /// # Returns
163    ///
164    /// Returns the wrapped output. Units can still be pending in this adapter.
165    #[must_use]
166    #[inline]
167    pub const fn inner(&self) -> &O {
168        self.output.inner()
169    }
170
171    /// Returns a mutable reference to the wrapped asynchronous output.
172    ///
173    /// Pending adapter units are not automatically delivered before direct
174    /// output operations. Call [`Self::flush_async`] first when ordering with
175    /// previously transcoded data matters.
176    ///
177    /// # Returns
178    ///
179    /// Returns the wrapped output.
180    #[must_use]
181    #[inline]
182    pub fn inner_mut(&mut self) -> &mut O {
183        self.output.inner_mut()
184    }
185
186    /// Returns the number of units retained pending delivery.
187    ///
188    /// # Returns
189    ///
190    /// Returns the length of the pending unit window.
191    #[must_use]
192    #[inline]
193    pub const fn pending_len(&self) -> usize {
194        self.output.pending_len()
195    }
196
197    /// Consumes this adapter without flushing the wrapped output.
198    ///
199    /// This method performs no asynchronous I/O. The returned buffer holds
200    /// units that must be delivered before the logical stream can continue.
201    ///
202    /// # Returns
203    ///
204    /// Returns the wrapped output and its pending unit buffer.
205    #[must_use = "the returned output and pending buffer must be handled"]
206    #[inline]
207    pub fn into_parts(self) -> (O, Buffer<O::Item>) {
208        self.output.into_parts()
209    }
210}
211
212impl<O> AsyncTranscodeEncodeOutput<O>
213where
214    O: AsyncOutput + Unpin,
215    O::Item: Clone + Default + Unpin,
216{
217    /// Flushes pending transcoded units and the wrapped asynchronous output.
218    ///
219    /// # Returns
220    ///
221    /// Returns `Ok(())` after pending units are delivered and the sink is
222    /// flushed.
223    ///
224    /// # Errors
225    ///
226    /// Returns output-delivery or flush errors from the wrapped output. A
227    /// failed delivery retains every unit that was not accepted.
228    pub async fn flush_async(&mut self) -> Result<()> {
229        self.output.flush_async().await
230    }
231
232    /// Delivers pending transcoded units without flushing the wrapped output.
233    ///
234    /// # Returns
235    ///
236    /// Returns `Ok(())` when the pending buffer is empty.
237    ///
238    /// # Errors
239    ///
240    /// Returns output-delivery errors from the wrapped output. A failed
241    /// delivery retains every unit that was not accepted.
242    pub async fn drain_async(&mut self) -> Result<()> {
243        let capacity = self.output.capacity();
244        self.output.ensure_spare_capacity_async(capacity).await
245    }
246
247    /// Runs encoder reset and buffers its stream-prefix units.
248    ///
249    /// # Type Parameters
250    ///
251    /// * `E` - Encoder producing the wrapped output item type.
252    /// * `M` - Error mapper invoked for encoder failures.
253    /// * `Value` - Logical input value type accepted by the encoder.
254    ///
255    /// # Parameters
256    ///
257    /// * `encoder` - Streaming encoder whose lifecycle is being started.
258    /// * `map_error` - Maps transcoder errors into I/O errors.
259    ///
260    /// # Returns
261    ///
262    /// Returns `Ok(())` with the reset prefix retained in the buffer.
263    ///
264    /// # Errors
265    ///
266    /// Returns capacity planning, output delivery, allocation, or mapped reset
267    /// errors. Pending units remain retained if asynchronous delivery fails.
268    ///
269    /// # Panics
270    ///
271    /// Panics when `encoder` writes more units than its declared reset bound.
272    pub async fn reset_async<E, M, Value>(&mut self, encoder: &mut E, map_error: &mut M) -> Result<()>
273    where
274        E: Transcoder<Input = Value, Output = O::Item>,
275        M: FnMut(E::Error) -> Error,
276    {
277        let required = encoder.max_reset_output_len().map_err(capacity_error_to_invalid_data)?;
278        self.ensure_spare_capacity_async(required).await?;
279        let (units, output_index, available) = self.output.spare_raw_parts_mut();
280        debug_assert!(available >= required);
281        let written = encoder.reset(units, output_index).map_err(&mut *map_error)?;
282        assert!(written <= required, "reset wrote beyond its bound");
283        // SAFETY: `written` is bounded by the reserved spare range above.
284        unsafe {
285            self.output.advance(written);
286        }
287        Ok(())
288    }
289
290    /// Polls one cancellation-safe encoder operation.
291    ///
292    /// The adapter may first deliver previously pending output to make a
293    /// conservative spare range available. Once it invokes `encoder`, it
294    /// advances the produced units and returns immediately without another
295    /// poll. The returned [`TranscodeProgress`] is therefore the exact source
296    /// range committed by this call.
297    ///
298    /// # Type Parameters
299    ///
300    /// * `E` - Encoder producing the wrapped output item type.
301    /// * `M` - Error mapper invoked for encoder failures.
302    /// * `Value` - Logical input value type accepted by the encoder.
303    ///
304    /// # Parameters
305    ///
306    /// * `cx` - Task context used by the sink to register readiness.
307    /// * `encoder` - Streaming encoder used for the conversion.
308    /// * `map_error` - Maps transcoder errors into I/O errors.
309    /// * `input` - Source values.
310    /// * `input_index` - First source value to transcode.
311    /// * `count` - Number of source values available from `input_index`.
312    ///
313    /// # Returns
314    ///
315    /// Returns `Pending` while making output space, without consuming input,
316    /// or `Ready` with one validated encoder progress report or an error.
317    ///
318    /// # Errors
319    ///
320    /// Returns invalid input ranges, output delivery, allocation, contract, or
321    /// mapped transcoder errors. Pending units remain retained after a failed
322    /// asynchronous delivery.
323    pub fn poll_transcode<E, M, Value>(
324        mut self: Pin<&mut Self>,
325        cx: &mut Context<'_>,
326        encoder: &mut E,
327        map_error: &mut M,
328        input: &[Value],
329        input_index: usize,
330        count: usize,
331    ) -> Poll<Result<TranscodeProgress>>
332    where
333        E: TranscodeEncoder<Input = Value, Output = O::Item>,
334        M: FnMut(E::Error) -> Error,
335    {
336        let input_end = SliceRange::checked_range_end(
337            input.len(),
338            input_index,
339            count,
340            "encode input range exceeds source buffer",
341        )?;
342        if count == 0 {
343            return Poll::Ready(Ok(TranscodeProgress::complete(0, 0)));
344        }
345        let input = &input[..input_end];
346        let this = self.as_mut().get_mut();
347        let per_value = match encoder.max_transcode_output_len(1) {
348            Ok(required) => required.max(1),
349            Err(error) => {
350                return Poll::Ready(Err(capacity_error_to_invalid_data(error)));
351            }
352        };
353        let required_spare = this.required_spare.max(per_value);
354        let required_capacity = this.output.pending_len().saturating_add(required_spare);
355        if let Err(error) = this.output.try_reserve_capacity(required_capacity) {
356            return Poll::Ready(Err(allocation_error(error)));
357        }
358        match Pin::new(&mut this.output).poll_ensure_spare_capacity(cx, required_spare) {
359            Poll::Ready(Ok(())) => {}
360            Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
361            Poll::Pending => return Poll::Pending,
362        }
363        let (units, output_index, available_output) = this.output.spare_raw_parts_mut();
364        let progress = encoder
365            .transcode(input, input_index, units, output_index)
366            .map_err(&mut *map_error)
367            .and_then(|progress| {
368                validate_encode_progress(progress, input_index, count, output_index, available_output)
369            });
370        let progress = match progress {
371            Ok(progress) => progress,
372            Err(error) => return Poll::Ready(Err(error)),
373        };
374        // SAFETY: Progress validation proves the initialized output count fits
375        // the spare window provided to the encoder.
376        unsafe {
377            this.output.advance(progress.written());
378        }
379        this.required_spare = if let TranscodeStatus::NeedOutput { required, .. } = progress.status() {
380            required.get()
381        } else {
382            // `validate_encode_progress` rejects `NeedInput` before this
383            // point.
384            debug_assert!(progress.is_complete());
385            1
386        };
387        Poll::Ready(Ok(progress))
388    }
389
390    /// Transcodes one cancellation-safe progress step into the retained buffer.
391    ///
392    /// This is the async wrapper for [`Self::poll_transcode`]. It returns after
393    /// one encoder invocation; callers that need to consume a whole source
394    /// range must advance the source index by [`TranscodeProgress::read`] and
395    /// call it again.
396    ///
397    /// # Type Parameters
398    ///
399    /// * `E` - Encoder producing the wrapped output item type.
400    /// * `M` - Error mapper invoked for encoder failures.
401    /// * `Value` - Logical input value type accepted by the encoder.
402    ///
403    /// # Parameters
404    ///
405    /// * `encoder` - Encoder invoked once after output space is available.
406    /// * `map_error` - Maps encoder errors to I/O errors.
407    /// * `input` - Source values borrowed for this operation.
408    /// * `input_index` - Starting source offset.
409    /// * `count` - Number of source values available at that offset.
410    ///
411    /// # Returns
412    ///
413    /// One validated progress report describing committed source consumption.
414    ///
415    /// # Errors
416    ///
417    /// Returns range, capacity, allocation, delivery, contract or mapped
418    /// encoder errors, with pending units retained as described by
419    /// `poll_transcode`.
420    pub async fn transcode_async<E, M, Value>(
421        &mut self,
422        encoder: &mut E,
423        map_error: &mut M,
424        input: &[Value],
425        input_index: usize,
426        count: usize,
427    ) -> Result<TranscodeProgress>
428    where
429        E: TranscodeEncoder<Input = Value, Output = O::Item>,
430        M: FnMut(E::Error) -> Error,
431    {
432        poll_fn(|cx| Pin::new(&mut *self).poll_transcode(cx, encoder, map_error, input, input_index, count)).await
433    }
434
435    /// Finishes the encoder and retains its final output for delivery.
436    ///
437    /// # Type Parameters
438    ///
439    /// * `E` - Encoder producing the wrapped output item type.
440    /// * `M` - Error mapper invoked for encoder failures.
441    /// * `Value` - Logical input value type accepted by the encoder.
442    ///
443    /// # Parameters
444    ///
445    /// * `encoder` - Streaming encoder whose lifecycle is being finished.
446    /// * `map_error` - Maps transcoder errors into I/O errors.
447    ///
448    /// # Returns
449    ///
450    /// Returns `Ok(())` after final units are committed to the buffer.
451    ///
452    /// # Errors
453    ///
454    /// Returns capacity planning, output delivery, allocation, or mapped finish
455    /// errors. Call [`Self::flush_async`] after this method to deliver
456    /// final units. Keeping finalization separate from delivery lets a
457    /// caller record the completed lifecycle before any later suspension
458    /// point.
459    ///
460    /// # Panics
461    ///
462    /// Panics when `encoder` writes more units than its declared finish bound.
463    pub async fn finish_async<E, M, Value>(&mut self, encoder: &mut E, map_error: &mut M) -> Result<()>
464    where
465        E: Transcoder<Input = Value, Output = O::Item>,
466        M: FnMut(E::Error) -> Error,
467    {
468        let required = encoder
469            .max_finish_output_len()
470            .map_err(capacity_error_to_invalid_data)?;
471        self.ensure_spare_capacity_async(required).await?;
472        let (units, output_index, available) = self.output.spare_raw_parts_mut();
473        debug_assert!(available >= required);
474        let written = encoder.finish(units, output_index).map_err(&mut *map_error)?;
475        assert!(written <= required, "finish wrote beyond its bound");
476        // SAFETY: `written` is bounded by the reserved spare range above.
477        unsafe {
478            self.output.advance(written);
479        }
480        Ok(())
481    }
482
483    /// Reserves enough total buffer capacity and makes `count` spare slots
484    /// available for one transcoder operation.
485    ///
486    /// # Parameters
487    ///
488    /// * `count` - Minimum spare units needed after any pending delivery.
489    ///
490    /// # Returns
491    ///
492    /// `Ok(())` when the requested spare capacity is available.
493    ///
494    /// # Errors
495    ///
496    /// Returns allocation or wrapped output errors; undelivered units remain
497    /// buffered.
498    async fn ensure_spare_capacity_async(&mut self, count: usize) -> Result<()> {
499        let required_capacity = self.output.pending_len().saturating_add(count);
500        self.output
501            .try_reserve_capacity(required_capacity)
502            .map_err(allocation_error)?;
503        self.output.ensure_spare_capacity_async(count).await
504    }
505}
506
507/// Converts capacity planning failures into invalid asynchronous stream data.
508///
509/// # Parameters
510///
511/// * `error` - Capacity failure retained as the source of the I/O error.
512///
513/// # Returns
514///
515/// An `InvalidData` error owning the capacity failure.
516#[must_use]
517fn capacity_error_to_invalid_data(error: CapacityError) -> Error {
518    Error::new(ErrorKind::InvalidData, error)
519}
520
521impl<O> fmt::Debug for AsyncTranscodeEncodeOutput<O>
522where
523    O: AsyncOutput,
524    O::Item: Clone + Default,
525    AsyncBufferedOutput<O>: fmt::Debug,
526{
527    /// Formats this asynchronous output adapter for debugging.
528    ///
529    /// # Parameters
530    ///
531    /// * `formatter` - Destination for the adapter and wrapped buffer
532    ///   description.
533    ///
534    /// # Returns
535    ///
536    /// `Ok(())` after the description is written.
537    ///
538    /// # Errors
539    ///
540    /// Propagates errors reported by the formatter.
541    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
542        formatter
543            .debug_struct("AsyncTranscodeEncodeOutput")
544            .field("output", &self.output)
545            .finish()
546    }
547}