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