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. An encoder error does not provide general
323    /// transactional rollback or complete progress: the encoder may already
324    /// have advanced, but units written by its failing call are not committed
325    /// to this adapter's pending output. Do not replay the original input
326    /// range unless the concrete encoder provides an explicit recovery
327    /// protocol. Previously committed pending units follow the wrapped output's
328    /// recovery contract; reset starts a new logical stream and does not undo
329    /// output already delivered.
330    pub fn poll_transcode<E, M, Value>(
331        mut self: Pin<&mut Self>,
332        cx: &mut Context<'_>,
333        encoder: &mut E,
334        map_error: &mut M,
335        input: &[Value],
336        input_index: usize,
337        count: usize,
338    ) -> Poll<Result<TranscodeProgress>>
339    where
340        E: TranscodeEncoder<Input = Value, Output = O::Item>,
341        M: FnMut(E::Error) -> Error,
342    {
343        let input_end = SliceRange::checked_range_end(
344            input.len(),
345            input_index,
346            count,
347            "encode input range exceeds source buffer",
348        )?;
349        if count == 0 {
350            return Poll::Ready(Ok(TranscodeProgress::complete(0, 0)));
351        }
352        let input = &input[..input_end];
353        let this = self.as_mut().get_mut();
354        let per_value = match encoder.max_transcode_output_len(1) {
355            Ok(required) => required.max(1),
356            Err(error) => {
357                return Poll::Ready(Err(capacity_error_to_invalid_data(error)));
358            }
359        };
360        let required_spare = this.required_spare.max(per_value);
361        if let Err(error) = this.output.try_reserve_capacity(required_spare) {
362            return Poll::Ready(Err(allocation_error(error)));
363        }
364        match Pin::new(&mut this.output).poll_ensure_spare_capacity(cx, required_spare) {
365            Poll::Ready(Ok(())) => {}
366            Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
367            Poll::Pending => return Poll::Pending,
368        }
369        let (units, output_index, available_output) = this.output.spare_raw_parts_mut();
370        let progress = encoder
371            .transcode(input, input_index, units, output_index)
372            .map_err(&mut *map_error)
373            .and_then(|progress| {
374                validate_encode_progress(progress, input_index, count, output_index, available_output)
375            });
376        let progress = match progress {
377            Ok(progress) => progress,
378            Err(error) => return Poll::Ready(Err(error)),
379        };
380        // SAFETY: Progress validation proves the initialized output count fits
381        // the spare window provided to the encoder.
382        unsafe {
383            this.output.advance(progress.written());
384        }
385        this.required_spare = if let TranscodeStatus::NeedOutput { required, .. } = progress.status() {
386            required.get()
387        } else {
388            // `validate_encode_progress` rejects `NeedInput` before this
389            // point.
390            debug_assert!(progress.is_complete());
391            1
392        };
393        Poll::Ready(Ok(progress))
394    }
395
396    /// Transcodes one cancellation-safe progress step into the retained buffer.
397    ///
398    /// This is the async wrapper for [`Self::poll_transcode`]. It returns after
399    /// one encoder invocation; callers that need to consume a whole source
400    /// range must advance the source index by [`TranscodeProgress::read`] and
401    /// call it again.
402    ///
403    /// # Type Parameters
404    ///
405    /// * `E` - Encoder producing the wrapped output item type.
406    /// * `M` - Error mapper invoked for encoder failures.
407    /// * `Value` - Logical input value type accepted by the encoder.
408    ///
409    /// # Parameters
410    ///
411    /// * `encoder` - Encoder invoked once after output space is available.
412    /// * `map_error` - Maps encoder errors to I/O errors.
413    /// * `input` - Source values borrowed for this operation.
414    /// * `input_index` - Starting source offset.
415    /// * `count` - Number of source values available at that offset.
416    ///
417    /// # Returns
418    ///
419    /// One validated progress report describing committed source consumption.
420    ///
421    /// # Errors
422    ///
423    /// Returns range, capacity, allocation, delivery, contract or mapped
424    /// encoder errors, with pending units retained as described by
425    /// [`Self::poll_transcode`].
426    pub async fn transcode_async<E, M, Value>(
427        &mut self,
428        encoder: &mut E,
429        map_error: &mut M,
430        input: &[Value],
431        input_index: usize,
432        count: usize,
433    ) -> Result<TranscodeProgress>
434    where
435        E: TranscodeEncoder<Input = Value, Output = O::Item>,
436        M: FnMut(E::Error) -> Error,
437    {
438        poll_fn(|cx| Pin::new(&mut *self).poll_transcode(cx, encoder, map_error, input, input_index, count)).await
439    }
440
441    /// Finishes the encoder and retains its final output for delivery.
442    ///
443    /// # Type Parameters
444    ///
445    /// * `E` - Encoder producing the wrapped output item type.
446    /// * `M` - Error mapper invoked for encoder failures.
447    /// * `Value` - Logical input value type accepted by the encoder.
448    ///
449    /// # Parameters
450    ///
451    /// * `encoder` - Streaming encoder whose lifecycle is being finished.
452    /// * `map_error` - Maps transcoder errors into I/O errors.
453    ///
454    /// # Returns
455    ///
456    /// Returns `Ok(())` after final units are committed to the buffer.
457    ///
458    /// # Errors
459    ///
460    /// Returns capacity planning, output delivery, allocation, or mapped finish
461    /// errors. Call [`Self::flush_async`] after this method to deliver
462    /// final units. Keeping finalization separate from delivery lets a
463    /// caller record the completed lifecycle before any later suspension
464    /// point.
465    ///
466    /// # Panics
467    ///
468    /// Panics when `encoder` writes more units than its declared finish bound.
469    pub async fn finish_async<E, M, Value>(&mut self, encoder: &mut E, map_error: &mut M) -> Result<()>
470    where
471        E: Transcoder<Input = Value, Output = O::Item>,
472        M: FnMut(E::Error) -> Error,
473    {
474        let required = encoder
475            .max_finish_output_len()
476            .map_err(capacity_error_to_invalid_data)?;
477        self.ensure_spare_capacity_async(required).await?;
478        let (units, output_index, available) = self.output.spare_raw_parts_mut();
479        debug_assert!(available >= required);
480        let written = encoder.finish(units, output_index).map_err(&mut *map_error)?;
481        assert!(written <= required, "finish wrote beyond its bound");
482        // SAFETY: `written` is bounded by the reserved spare range above.
483        unsafe {
484            self.output.advance(written);
485        }
486        Ok(())
487    }
488
489    /// Reserves enough total buffer capacity and makes `count` spare slots
490    /// available for one transcoder operation. Pending units may be delivered
491    /// before the codec operation, and an async poll can return `Pending` until
492    /// the wrapped output accepts them.
493    ///
494    /// # Parameters
495    ///
496    /// * `count` - Minimum spare units needed after any pending delivery.
497    ///
498    /// # Returns
499    ///
500    /// `Ok(())` when the requested spare capacity is available.
501    ///
502    /// # Errors
503    ///
504    /// Returns allocation or wrapped output errors; undelivered units remain
505    /// buffered.
506    async fn ensure_spare_capacity_async(&mut self, count: usize) -> Result<()> {
507        self.output.try_reserve_capacity(count).map_err(allocation_error)?;
508        self.output.ensure_spare_capacity_async(count).await
509    }
510}
511
512/// Converts capacity planning failures into invalid asynchronous stream data.
513///
514/// # Parameters
515///
516/// * `error` - Capacity failure retained as the source of the I/O error.
517///
518/// # Returns
519///
520/// An `InvalidData` error owning the capacity failure.
521#[must_use]
522fn capacity_error_to_invalid_data(error: CapacityError) -> Error {
523    Error::new(ErrorKind::InvalidData, error)
524}
525
526impl<O> fmt::Debug for AsyncTranscodeEncodeOutput<O>
527where
528    O: AsyncOutput,
529    O::Item: Clone + Default,
530    AsyncBufferedOutput<O>: fmt::Debug,
531{
532    /// Formats this asynchronous output adapter for debugging.
533    ///
534    /// # Parameters
535    ///
536    /// * `formatter` - Destination for the adapter and wrapped buffer
537    ///   description.
538    ///
539    /// # Returns
540    ///
541    /// `Ok(())` after the description is written.
542    ///
543    /// # Errors
544    ///
545    /// Propagates errors reported by the formatter.
546    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
547        formatter
548            .debug_struct("AsyncTranscodeEncodeOutput")
549            .field("output", &self.output)
550            .finish()
551    }
552}