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::Result;
15use std::pin::Pin;
16use std::task::Context;
17use std::task::Poll;
18
19use qubit_io::AsyncBufferedOutput;
20use qubit_io::AsyncOutput;
21use qubit_io::Buffer;
22use qubit_utils::SliceRange;
23use qubit_utils::allocation_error;
24
25use super::internal::capacity_error_to_invalid_data;
26use super::internal::validate_encode_progress;
27use crate::TranscodeEncoder;
28use crate::TranscodeProgress;
29use crate::TranscodeStatus;
30use crate::Transcoder;
31
32/// Buffers asynchronous transcoder output while preserving pending units.
33///
34/// This adapter is the asynchronous counterpart to
35/// [`super::TranscodeEncodeOutput`]. It owns the unit-level buffer, so a
36/// future cancelled while the wrapped output is pending retains every unit
37/// that the transcoder has already produced. Each transcode operation returns
38/// after exactly one encoder invocation, so its progress is the explicit
39/// source-consumption commit boundary.
40///
41/// Cancellation and pending-output recovery assume that wrapped I/O calls
42/// obey their normal return contracts. This adapter does not track poisoning
43/// or indeterminate delivery after an inner I/O panic. If such a panic is
44/// caught, reconcile external side effects before retrying or replaying
45/// pending units.
46///
47/// # Type Parameters
48///
49/// * `O` - Wrapped asynchronous unit output.
50///
51/// # Examples
52///
53/// ```
54/// use core::convert::Infallible;
55/// use core::num::NonZeroUsize;
56/// use std::future::Future;
57/// use std::io::Result;
58/// use std::pin::{pin, Pin};
59/// use std::task::{Context, Poll, Waker};
60///
61/// use qubit_codec::{AsyncTranscodeEncodeOutput, Codec, CodecTranscodeEncoder};
62/// use qubit_codec::{DecodeFailure, TranscodeEncoder};
63/// use qubit_io::AsyncOutput;
64///
65/// struct ByteCodec;
66/// impl Codec for ByteCodec {
67///     type Value = u8;
68///     type Unit = u8;
69///     type DecodeError = Infallible;
70///     type EncodeError = Infallible;
71///     const MIN_UNITS_PER_VALUE: usize = 1;
72///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
73///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
74///     unsafe fn decode(&mut self, input: &[u8], index: usize)
75///         -> std::result::Result<(u8, NonZeroUsize), DecodeFailure<Infallible>> {
76///         Ok((input[index], NonZeroUsize::MIN))
77///     }
78///     unsafe fn encode(&mut self, value: &u8, output: &mut [u8], index: usize)
79///         -> std::result::Result<usize, Infallible> {
80///         output[index] = *value;
81///         Ok(1)
82///     }
83/// }
84///
85/// #[derive(Default)]
86/// struct Sink { bytes: Vec<u8>, flushes: usize }
87/// impl AsyncOutput for Sink {
88///     type Item = u8;
89///     unsafe fn poll_write_unchecked(
90///         self: Pin<&mut Self>, _: &mut Context<'_>, units: &[u8], index: usize, count: usize,
91///     ) -> Poll<Result<usize>> {
92///         self.get_mut().bytes.extend_from_slice(&units[index..index + count]);
93///         Poll::Ready(Ok(count))
94///     }
95///     fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<()>> {
96///         self.get_mut().flushes += 1;
97///         Poll::Ready(Ok(()))
98///     }
99/// }
100///
101/// async fn run_example() -> Result<()> {
102///     let mut encoder = CodecTranscodeEncoder::new(ByteCodec);
103///     let mut output = AsyncTranscodeEncodeOutput::with_capacity(Sink::default(), 8);
104///     let mut map_error = |_| std::io::Error::other("encode error");
105///     output.reset_async(&mut encoder, &mut map_error).await?;
106///     let values = [1_u8, 2, 3];
107///     let mut offset = 0;
108///     while offset < values.len() {
109///         let progress = output.transcode_async(
110///             &mut encoder, &mut map_error, &values, offset, values.len() - offset,
111///         ).await?;
112///         assert!(progress.read() > 0);
113///         offset += progress.read();
114///     }
115///     output.finish_async(&mut encoder, &mut map_error).await?;
116///     assert_eq!(output.pending_len(), values.len());
117///     output.flush_async().await?;
118///     let (sink, pending) = output.into_parts();
119///     assert!(pending.is_empty());
120///     assert_eq!(sink.bytes, vec![1, 2, 3]);
121///     assert_eq!(sink.flushes, 1);
122///     Ok(())
123/// }
124///
125/// let mut future = pin!(run_example());
126/// let mut context = Context::from_waker(Waker::noop());
127/// match future.as_mut().poll(&mut context) {
128///     Poll::Ready(result) => result.unwrap(),
129///     Poll::Pending => panic!("the example sink always completes immediately"),
130/// }
131/// ```
132///
133/// `finish_async` commits final units to the adapter buffer. `flush_async`
134/// delivers those units and flushes the wrapped sink.
135#[must_use]
136pub struct AsyncTranscodeEncodeOutput<O>
137where
138    O: AsyncOutput,
139    O::Item: Clone + Default,
140{
141    /// Owned output buffer retaining units until the wrapped sink accepts them.
142    output: AsyncBufferedOutput<O>,
143    /// Minimum spare units requested by the previous encoder progress report.
144    required_spare: usize,
145}
146
147impl<O> AsyncTranscodeEncodeOutput<O>
148where
149    O: AsyncOutput,
150    O::Item: Clone + Default,
151{
152    /// Creates an asynchronous encoder output with the default unit capacity.
153    ///
154    /// # Parameters
155    ///
156    /// * `inner` - Unit output receiving transcoder data.
157    ///
158    /// # Returns
159    ///
160    /// Returns a buffered asynchronous output adapter.
161    ///
162    /// # Panics
163    ///
164    /// Panics if initializing the default buffer requires `O::Item::default()`
165    /// or `O::Item::clone()` and either operation panics, or if the default
166    /// backing length exceeds `Vec`'s supported capacity.
167    pub fn new(inner: O) -> Self {
168        Self {
169            output: AsyncBufferedOutput::new(inner),
170            required_spare: 1,
171        }
172    }
173
174    /// Creates an asynchronous encoder output with at least `capacity` units.
175    ///
176    /// # Parameters
177    ///
178    /// * `inner` - Unit output receiving transcoder data.
179    /// * `capacity` - Requested internal unit capacity.
180    ///
181    /// # Returns
182    ///
183    /// Returns a buffered asynchronous output adapter.
184    ///
185    /// # Panics
186    ///
187    /// Panics if initializing the buffer requires `O::Item::default()` or
188    /// `O::Item::clone()` and either operation panics, or if the requested
189    /// backing length exceeds `Vec`'s supported capacity.
190    pub fn with_capacity(inner: O, capacity: usize) -> Self {
191        Self {
192            output: AsyncBufferedOutput::with_capacity(inner, capacity),
193            required_spare: 1,
194        }
195    }
196
197    /// Tries to create an adapter with at least `capacity` buffered units.
198    ///
199    /// # Parameters
200    ///
201    /// * `inner` - Output moved into the adapter, including on allocation
202    ///   failure.
203    /// * `capacity` - Minimum number of units to reserve.
204    ///
205    /// # Returns
206    ///
207    /// Returns an empty buffered adapter on successful allocation.
208    ///
209    /// # Errors
210    ///
211    /// Returns an allocation error when the internal unit buffer cannot be
212    /// allocated.
213    ///
214    /// # Panics
215    ///
216    /// Panics if initializing the buffer requires `O::Item::default()` or
217    /// `O::Item::clone()` and either operation panics.
218    pub fn try_with_capacity(inner: O, capacity: usize) -> std::result::Result<Self, TryReserveError> {
219        Ok(Self {
220            output: AsyncBufferedOutput::try_with_capacity(inner, capacity)?,
221            required_spare: 1,
222        })
223    }
224
225    /// Returns the total internal unit-buffer capacity.
226    ///
227    /// # Returns
228    ///
229    /// The allocated unit capacity, including occupied and spare slots.
230    #[must_use]
231    #[inline]
232    pub fn capacity(&self) -> usize {
233        self.output.capacity()
234    }
235
236    /// Returns a shared reference to the wrapped asynchronous output.
237    ///
238    /// # Returns
239    ///
240    /// Returns the wrapped output. Units can still be pending in this adapter.
241    #[must_use]
242    #[inline]
243    pub const fn inner(&self) -> &O {
244        self.output.inner()
245    }
246
247    /// Returns a mutable reference to the wrapped asynchronous output.
248    ///
249    /// Pending adapter units are not automatically delivered before direct
250    /// output operations. Call [`Self::flush_async`] first when ordering with
251    /// previously transcoded data matters.
252    ///
253    /// # Returns
254    ///
255    /// Returns the wrapped output.
256    #[must_use]
257    #[inline]
258    pub fn inner_mut(&mut self) -> &mut O {
259        self.output.inner_mut()
260    }
261
262    /// Returns the number of units retained pending delivery.
263    ///
264    /// # Returns
265    ///
266    /// Returns the length of the pending unit window.
267    #[must_use]
268    #[inline]
269    pub const fn pending_len(&self) -> usize {
270        self.output.pending_len()
271    }
272
273    /// Consumes this adapter without flushing the wrapped output.
274    ///
275    /// This method performs no asynchronous I/O. The returned buffer holds
276    /// units that must be delivered before the logical stream can continue.
277    ///
278    /// Recovery assumes that wrapped I/O calls obey their normal return
279    /// contracts. If an inner I/O panic is caught, the pending units may
280    /// overlap a prefix already delivered. The buffer does not track this
281    /// indeterminate delivery; reconcile external side effects before
282    /// retrying or replaying those units.
283    ///
284    /// # Returns
285    ///
286    /// Returns the wrapped output and its pending unit buffer, assuming
287    /// wrapped I/O calls obey their normal return contracts.
288    #[must_use = "the returned output and pending buffer must be handled"]
289    #[inline]
290    pub fn into_parts(self) -> (O, Buffer<O::Item>) {
291        self.output.into_parts()
292    }
293}
294
295impl<O> AsyncTranscodeEncodeOutput<O>
296where
297    O: AsyncOutput + Unpin,
298    O::Item: Clone + Default + Unpin,
299{
300    /// Flushes pending transcoded units and the wrapped asynchronous output.
301    ///
302    /// # Returns
303    ///
304    /// Returns `Ok(())` after pending units are delivered and the sink is
305    /// flushed.
306    ///
307    /// # Errors
308    ///
309    /// Returns output-delivery or flush errors from the wrapped output. A
310    /// failed delivery retains every unit that was not accepted.
311    pub async fn flush_async(&mut self) -> Result<()> {
312        self.output.flush_async().await
313    }
314
315    /// Delivers pending transcoded units without flushing the wrapped output.
316    ///
317    /// # Returns
318    ///
319    /// Returns `Ok(())` when the pending buffer is empty.
320    ///
321    /// # Errors
322    ///
323    /// Returns output-delivery errors from the wrapped output. A failed
324    /// delivery retains every unit that was not accepted.
325    pub async fn drain_async(&mut self) -> Result<()> {
326        let capacity = self.output.capacity();
327        self.output.ensure_spare_capacity_async(capacity).await
328    }
329
330    /// Runs encoder reset and buffers its stream-prefix units.
331    ///
332    /// # Type Parameters
333    ///
334    /// * `E` - Encoder producing the wrapped output item type.
335    /// * `M` - Error mapper invoked for encoder failures.
336    /// * `Value` - Logical input value type accepted by the encoder.
337    ///
338    /// # Parameters
339    ///
340    /// * `encoder` - Streaming encoder whose lifecycle is being started.
341    /// * `map_error` - Maps transcoder errors into I/O errors.
342    ///
343    /// # Returns
344    ///
345    /// Returns `Ok(())` with the reset prefix retained in the buffer.
346    /// A successful reset discards the previous stream's spare-capacity hint.
347    /// Previously committed pending units remain buffered in delivery order.
348    ///
349    /// # Errors
350    ///
351    /// Returns capacity planning, output delivery, allocation, or mapped reset
352    /// errors. Pending units remain retained if asynchronous delivery fails.
353    ///
354    /// # Panics
355    ///
356    /// Panics when `encoder` writes more units than its declared reset bound.
357    pub async fn reset_async<E, M, Value>(&mut self, encoder: &mut E, map_error: &mut M) -> Result<()>
358    where
359        E: Transcoder<Input = Value, Output = O::Item>,
360        M: FnMut(E::Error) -> Error,
361    {
362        let required = encoder.max_reset_output_len().map_err(capacity_error_to_invalid_data)?;
363        self.ensure_spare_capacity_async(required).await?;
364        let (units, output_index, available) = self.output.spare_raw_parts_mut();
365        debug_assert!(available >= required);
366        let written = encoder.reset(units, output_index).map_err(&mut *map_error)?;
367        assert!(written <= required, "reset wrote beyond its bound");
368        // SAFETY: `written` is bounded by the reserved spare range above.
369        unsafe {
370            self.output.advance(written);
371        }
372        self.required_spare = 1;
373        Ok(())
374    }
375
376    /// Polls one cancellation-safe encoder operation.
377    ///
378    /// The adapter may first deliver previously pending output to make a
379    /// conservative spare range available. Once it invokes `encoder`, it
380    /// advances the produced units and returns immediately without another
381    /// poll. The returned [`TranscodeProgress`] is therefore the exact source
382    /// range committed by this call.
383    ///
384    /// # Type Parameters
385    ///
386    /// * `E` - Encoder producing the wrapped output item type.
387    /// * `M` - Error mapper invoked for encoder failures.
388    /// * `Value` - Logical input value type accepted by the encoder.
389    ///
390    /// # Parameters
391    ///
392    /// * `cx` - Task context used by the sink to register readiness.
393    /// * `encoder` - Streaming encoder used for the conversion.
394    /// * `map_error` - Maps transcoder errors into I/O errors.
395    /// * `input` - Source values.
396    /// * `input_index` - First source value to transcode.
397    /// * `count` - Number of source values available from `input_index`.
398    ///
399    /// # Returns
400    ///
401    /// Returns `Pending` while making output space, without consuming input,
402    /// or `Ready` with one validated encoder progress report or an error.
403    ///
404    /// # Errors
405    ///
406    /// Returns invalid input ranges, output delivery, allocation, contract, or
407    /// mapped transcoder errors. Pending units remain retained after a failed
408    /// asynchronous delivery. An encoder error does not provide general
409    /// transactional rollback or complete progress: the encoder may already
410    /// have advanced, but units written by its failing call are not committed
411    /// to this adapter's pending output. Do not replay the original input
412    /// range unless the concrete encoder provides an explicit recovery
413    /// protocol. Previously committed pending units follow the wrapped output's
414    /// recovery contract; reset starts a new logical stream and does not undo
415    /// output already delivered.
416    pub fn poll_transcode<E, M, Value>(
417        mut self: Pin<&mut Self>,
418        cx: &mut Context<'_>,
419        encoder: &mut E,
420        map_error: &mut M,
421        input: &[Value],
422        input_index: usize,
423        count: usize,
424    ) -> Poll<Result<TranscodeProgress>>
425    where
426        E: TranscodeEncoder<Input = Value, Output = O::Item>,
427        M: FnMut(E::Error) -> Error,
428    {
429        let input_end = SliceRange::checked_range_end(
430            input.len(),
431            input_index,
432            count,
433            "encode input range exceeds source buffer",
434        )?;
435        if count == 0 {
436            return Poll::Ready(Ok(TranscodeProgress::complete(0, 0)));
437        }
438        let input = &input[..input_end];
439        let this = self.as_mut().get_mut();
440        let per_value = match encoder.max_transcode_output_len(1) {
441            Ok(required) => required.max(1),
442            Err(error) => {
443                return Poll::Ready(Err(capacity_error_to_invalid_data(error)));
444            }
445        };
446        let required_spare = this.required_spare.max(per_value);
447        if let Err(error) = this.output.try_reserve_capacity(required_spare) {
448            return Poll::Ready(Err(allocation_error(error)));
449        }
450        match Pin::new(&mut this.output).poll_ensure_spare_capacity(cx, required_spare) {
451            Poll::Ready(Ok(())) => {}
452            Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
453            Poll::Pending => return Poll::Pending,
454        }
455        let (units, output_index, available_output) = this.output.spare_raw_parts_mut();
456        let progress = encoder
457            .transcode(input, input_index, units, output_index)
458            .map_err(&mut *map_error)
459            .and_then(|progress| {
460                validate_encode_progress(progress, input_index, count, output_index, available_output)
461            });
462        let progress = match progress {
463            Ok(progress) => progress,
464            Err(error) => return Poll::Ready(Err(error)),
465        };
466        // SAFETY: Progress validation proves the initialized output count fits
467        // the spare window provided to the encoder.
468        unsafe {
469            this.output.advance(progress.written());
470        }
471        this.required_spare = if let TranscodeStatus::NeedOutput { required, .. } = progress.status() {
472            required.get()
473        } else {
474            // `validate_encode_progress` rejects `NeedInput` before this
475            // point.
476            debug_assert!(progress.is_complete());
477            1
478        };
479        Poll::Ready(Ok(progress))
480    }
481
482    /// Transcodes one cancellation-safe progress step into the retained buffer.
483    ///
484    /// This is the async wrapper for [`Self::poll_transcode`]. It returns after
485    /// one encoder invocation; callers that need to consume a whole source
486    /// range must advance the source index by [`TranscodeProgress::read`] and
487    /// call it again.
488    ///
489    /// # Type Parameters
490    ///
491    /// * `E` - Encoder producing the wrapped output item type.
492    /// * `M` - Error mapper invoked for encoder failures.
493    /// * `Value` - Logical input value type accepted by the encoder.
494    ///
495    /// # Parameters
496    ///
497    /// * `encoder` - Encoder invoked once after output space is available.
498    /// * `map_error` - Maps encoder errors to I/O errors.
499    /// * `input` - Source values borrowed for this operation.
500    /// * `input_index` - Starting source offset.
501    /// * `count` - Number of source values available at that offset.
502    ///
503    /// # Returns
504    ///
505    /// One validated progress report describing committed source consumption.
506    ///
507    /// # Errors
508    ///
509    /// Returns range, capacity, allocation, delivery, contract or mapped
510    /// encoder errors, with pending units retained as described by
511    /// [`Self::poll_transcode`].
512    pub async fn transcode_async<E, M, Value>(
513        &mut self,
514        encoder: &mut E,
515        map_error: &mut M,
516        input: &[Value],
517        input_index: usize,
518        count: usize,
519    ) -> Result<TranscodeProgress>
520    where
521        E: TranscodeEncoder<Input = Value, Output = O::Item>,
522        M: FnMut(E::Error) -> Error,
523    {
524        poll_fn(|cx| Pin::new(&mut *self).poll_transcode(cx, encoder, map_error, input, input_index, count)).await
525    }
526
527    /// Finishes the encoder and retains its final output for delivery.
528    ///
529    /// # Type Parameters
530    ///
531    /// * `E` - Encoder producing the wrapped output item type.
532    /// * `M` - Error mapper invoked for encoder failures.
533    /// * `Value` - Logical input value type accepted by the encoder.
534    ///
535    /// # Parameters
536    ///
537    /// * `encoder` - Streaming encoder whose lifecycle is being finished.
538    /// * `map_error` - Maps transcoder errors into I/O errors.
539    ///
540    /// # Returns
541    ///
542    /// Returns `Ok(())` after final units are committed to the buffer.
543    ///
544    /// # Errors
545    ///
546    /// Returns capacity planning, output delivery, allocation, or mapped finish
547    /// errors. Call [`Self::flush_async`] after this method to deliver
548    /// final units. Keeping finalization separate from delivery lets a
549    /// caller record the completed lifecycle before any later suspension
550    /// point.
551    ///
552    /// # Panics
553    ///
554    /// Panics when `encoder` writes more units than its declared finish bound.
555    pub async fn finish_async<E, M, Value>(&mut self, encoder: &mut E, map_error: &mut M) -> Result<()>
556    where
557        E: Transcoder<Input = Value, Output = O::Item>,
558        M: FnMut(E::Error) -> Error,
559    {
560        let required = encoder
561            .max_finish_output_len()
562            .map_err(capacity_error_to_invalid_data)?;
563        self.ensure_spare_capacity_async(required).await?;
564        let (units, output_index, available) = self.output.spare_raw_parts_mut();
565        debug_assert!(available >= required);
566        let written = encoder.finish(units, output_index).map_err(&mut *map_error)?;
567        assert!(written <= required, "finish wrote beyond its bound");
568        // SAFETY: `written` is bounded by the reserved spare range above.
569        unsafe {
570            self.output.advance(written);
571        }
572        Ok(())
573    }
574
575    /// Reserves enough total buffer capacity and makes `count` spare slots
576    /// available for one transcoder operation. Pending units may be delivered
577    /// before the codec operation, and an async poll can return `Pending` until
578    /// the wrapped output accepts them.
579    ///
580    /// # Parameters
581    ///
582    /// * `count` - Minimum spare units needed after any pending delivery.
583    ///
584    /// # Returns
585    ///
586    /// `Ok(())` when the requested spare capacity is available.
587    ///
588    /// # Errors
589    ///
590    /// Returns allocation or wrapped output errors; undelivered units remain
591    /// buffered.
592    async fn ensure_spare_capacity_async(&mut self, count: usize) -> Result<()> {
593        self.output.try_reserve_capacity(count).map_err(allocation_error)?;
594        self.output.ensure_spare_capacity_async(count).await
595    }
596}
597
598impl<O> fmt::Debug for AsyncTranscodeEncodeOutput<O>
599where
600    O: AsyncOutput,
601    O::Item: Clone + Default,
602    AsyncBufferedOutput<O>: fmt::Debug,
603{
604    /// Formats this asynchronous output adapter for debugging.
605    ///
606    /// # Parameters
607    ///
608    /// * `formatter` - Destination for the adapter and wrapped buffer
609    ///   description.
610    ///
611    /// # Returns
612    ///
613    /// `Ok(())` after the description is written.
614    ///
615    /// # Errors
616    ///
617    /// Propagates errors reported by the formatter.
618    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
619        formatter
620            .debug_struct("AsyncTranscodeEncodeOutput")
621            .field("output", &self.output)
622            .finish()
623    }
624}