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}