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