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}