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