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