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