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