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