qubit_codec/transcode/engine/transcode_encode_engine.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//! Reusable buffered encoder engine.
9
10use core::num::NonZeroUsize;
11
12use super::super::internal::{
13 encode_attempt::EncodeAttempt,
14 encode_state::EncodeState,
15 lifecycle_guard::LifecycleGuard,
16};
17use super::{
18 EncodeOutcome,
19 EncodeUnencodableAction,
20 TranscodeEncodeHooks,
21};
22use crate::codec::assert_unit_bounds;
23use crate::{
24 CapacityError,
25 Codec,
26 TranscodeEncodeError,
27 TranscodeEncodeErrorOf,
28 TranscodeEncoder,
29 TranscodeFailure,
30 TranscodeProgress,
31 Transcoder,
32};
33
34/// Reusable buffered encoding engine for codec-backed encoders.
35///
36/// The engine owns the low-level codec and hook object. It keeps the common
37/// buffered encoding loop private: input-index validation, output-capacity
38/// checks, input consumption, output progress, and [`crate::TranscodeStatus`]
39/// reporting.
40///
41/// Use this type to build a streaming encoder over a one-value [`Codec`]. The
42/// engine does not allocate output. It encodes values accepted by
43/// [`Codec::can_encode_value`] directly through the codec. When a value is
44/// outside the codec's encodable domain, the engine asks hooks whether to
45/// reject, skip, or replace it. If the current output buffer is too small for
46/// the selected value, the engine returns
47/// [`crate::TranscodeStatus::NeedOutput`] without consuming that input; the
48/// caller can provide a larger or fresh output buffer and resume with the
49/// returned input index.
50///
51/// For the common strict policy that simply wraps codec errors, use
52/// [`crate::CodecTranscodeEncoder`]. Use `TranscodeEncodeEngine` directly when
53/// the encode policy needs custom planning, replacement, skipped values, or
54/// finish-time output.
55///
56/// # Example
57///
58/// ```rust
59/// use core::{
60/// convert::Infallible,
61/// num::NonZeroUsize,
62/// };
63/// use qubit_codec::{
64/// Codec,
65/// TranscodeEncodeErrorOf,
66/// DecodeFailure,
67/// TranscodeStatus,
68/// };
69/// use qubit_codec::engine::{
70/// EncodeContext,
71/// EncodeUnencodableAction,
72/// TranscodeEncodeEngine,
73/// TranscodeEncodeHooks,
74/// };
75///
76/// #[derive(Clone, Copy)]
77/// struct ByteCodec;
78///
79/// impl Codec for ByteCodec {
80/// type Value = u8;
81/// type Unit = u8;
82/// type DecodeError = Infallible;
83/// type EncodeError = Infallible;
84///
85/// const MIN_UNITS_PER_VALUE: usize = 1;
86/// const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
87/// const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
88///
89/// unsafe fn decode(
90/// &mut self,
91/// input: &[u8],
92/// index: usize,
93/// ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
94/// Ok((input[index], NonZeroUsize::MIN))
95/// }
96///
97/// unsafe fn encode(
98/// &mut self,
99/// value: &u8,
100/// output: &mut [u8],
101/// index: usize,
102/// ) -> Result<usize, Self::EncodeError> {
103/// output[index] = *value;
104/// Ok(1)
105/// }
106/// }
107///
108/// struct StrictHooks;
109///
110/// impl TranscodeEncodeHooks<ByteCodec> for StrictHooks {
111/// fn handle_unencodable_encode(
112/// &mut self,
113/// _codec: &mut ByteCodec,
114/// _context: &EncodeContext<'_, u8>,
115/// ) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<ByteCodec>> {
116/// unreachable!("ByteCodec accepts every u8")
117/// }
118/// }
119///
120/// let mut engine = TranscodeEncodeEngine::new(ByteCodec, StrictHooks);
121/// let input = [1_u8, 2, 3];
122/// let mut output = [0_u8; 2];
123/// let mut reset_output = [];
124/// engine.reset(&mut reset_output, 0)?;
125///
126/// let progress = engine.transcode(&input, 0, &mut output, 0)?;
127/// match progress.status() {
128/// TranscodeStatus::Complete => unreachable!("output is intentionally short"),
129/// TranscodeStatus::NeedOutput { .. } => {
130/// assert_eq!(2, progress.written());
131/// assert_eq!([1, 2], output);
132/// // Write out `output[..progress.written()]`, then resume at
133/// // `progress.read()` with fresh output capacity.
134/// }
135/// TranscodeStatus::NeedInput { .. } => unreachable!("encoders do not read encoded input"),
136/// }
137/// # Ok::<(), TranscodeEncodeErrorOf<ByteCodec>>(())
138/// ```
139///
140/// # Type Parameters
141///
142/// - `C`: Low-level codec used by the engine.
143/// - `H`: Policy hook object used by the engine.
144#[derive(Debug)]
145pub struct TranscodeEncodeEngine<C, H> {
146 codec: C,
147 hooks: H,
148 /// Guard for the `reset → transcode* → finish` lifecycle in every build
149 /// profile.
150 lifecycle: LifecycleGuard,
151}
152
153impl<C, H> TranscodeEncodeEngine<C, H>
154where
155 C: Codec,
156 H: TranscodeEncodeHooks<C>,
157{
158 /// Creates a buffered encoder engine.
159 ///
160 /// # Parameters
161 ///
162 /// - `codec`: Low-level codec used for one-value encoding.
163 /// - `hooks`: Policy hooks used for planning and writing values.
164 ///
165 /// # Returns
166 ///
167 /// Returns a buffered encoder engine.
168 ///
169 /// # Compile-Time Checks
170 ///
171 /// Fails to compile when the supplied codec declares a zero decode unit
172 /// bound or when [`Codec::MIN_UNITS_PER_VALUE`] exceeds
173 /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`]. The encode bound may be zero for
174 /// a fully buffered codec.
175 #[inline]
176 #[must_use]
177 pub fn new(codec: C, hooks: H) -> Self {
178 assert_unit_bounds::<C>();
179 Self {
180 codec,
181 hooks,
182 lifecycle: LifecycleGuard::new(),
183 }
184 }
185
186 /// Returns the wrapped low-level codec.
187 ///
188 /// # Returns
189 ///
190 /// Returns a shared reference to the codec owned by this engine.
191 #[inline(always)]
192 #[must_use]
193 pub const fn codec(&self) -> &C {
194 &self.codec
195 }
196
197 /// Returns the wrapped low-level codec mutably.
198 ///
199 /// # Returns
200 ///
201 /// Returns a mutable reference to the codec owned by this engine.
202 #[inline(always)]
203 #[must_use]
204 pub fn codec_mut(&mut self) -> &mut C {
205 &mut self.codec
206 }
207
208 /// Returns the encode hooks used by this engine.
209 ///
210 /// # Returns
211 ///
212 /// Returns a shared reference to the hook object owned by this engine.
213 #[inline(always)]
214 #[must_use]
215 pub const fn hooks(&self) -> &H {
216 &self.hooks
217 }
218
219 /// Returns the encode hooks mutably.
220 ///
221 /// # Returns
222 ///
223 /// Returns a mutable reference to the hook object owned by this engine.
224 #[inline(always)]
225 #[must_use]
226 pub fn hooks_mut(&mut self) -> &mut H {
227 &mut self.hooks
228 }
229
230 /// Consumes the engine and returns its codec and hooks.
231 ///
232 /// Any lifecycle state owned by the engine is discarded.
233 ///
234 /// # Returns
235 ///
236 /// Returns the wrapped codec followed by the encode hooks.
237 #[inline]
238 #[must_use]
239 pub fn into_parts(self) -> (C, H) {
240 let Self { codec, hooks, .. } = self;
241 (codec, hooks)
242 }
243
244 /// Gets a conservative upper bound for output units needed for
245 /// `input_len` values.
246 ///
247 /// This bound covers only the streaming encode phase. It is delegated to
248 /// [`TranscodeEncodeHooks::max_transcode_output_len`], so it includes hook
249 /// policy and is valid for every reachable transient codec and hook state.
250 /// Downstream encoders must use this engine-level API for capacity planning
251 /// instead of recomputing the bound from [`Codec`] constants.
252 ///
253 /// # Parameters
254 ///
255 /// - `input_len`: Number of input values the caller plans to encode.
256 ///
257 /// # Returns
258 ///
259 /// a conservative upper bound for output units, or a capacity error on
260 /// arithmetic overflow.
261 #[inline(always)]
262 #[must_use = "capacity planning can fail on overflow"]
263 pub fn max_transcode_output_len(
264 &self,
265 input_len: usize,
266 ) -> Result<usize, CapacityError> {
267 self.hooks.max_transcode_output_len(&self.codec, input_len)
268 }
269
270 /// Gets the global maximum output units emitted by stream reset.
271 ///
272 /// # Returns
273 ///
274 /// the codec's reset-output upper bound.
275 #[inline(always)]
276 #[must_use = "capacity planning can fail on overflow"]
277 pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
278 Ok(C::MAX_ENCODE_RESET_UNITS)
279 }
280
281 /// Gets the global maximum output units emitted by finishing codec and hook
282 /// state.
283 ///
284 /// Returns the sum of [`Codec::MAX_ENCODE_FINISH_UNITS`] and the
285 /// hook-provided final-output bound. The codec finish portion covers units
286 /// written by [`Codec::encode_finish`]; hook implementations must not
287 /// include that portion in
288 /// [`TranscodeEncodeHooks::max_finish_output_len`]. Both component bounds
289 /// cover every reachable transient state.
290 ///
291 /// # Returns
292 ///
293 /// the combined codec-finish and hook-finish output bound.
294 #[inline(always)]
295 #[must_use = "capacity planning can fail on overflow"]
296 pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
297 C::MAX_ENCODE_FINISH_UNITS
298 .checked_add(self.hooks.max_finish_output_len(&self.codec))
299 .ok_or(CapacityError::OutputLengthOverflow)
300 }
301
302 /// Gets the maximum output units needed by a complete one-shot encode
303 /// stream.
304 ///
305 /// The returned bound covers reset output, the streaming encode phase for
306 /// `input_len` values, and finish output. Its components are global across
307 /// transient state. Higher-level complete encode helpers should use this
308 /// engine-level bound instead of recomputing capacity from [`Codec`]
309 /// constants, because hook policy may change streaming or finish output.
310 ///
311 /// # Parameters
312 ///
313 /// - `input_len`: Number of input values in the complete stream.
314 ///
315 /// # Returns
316 ///
317 /// Returns the complete-stream output bound, or a capacity error on
318 /// arithmetic overflow.
319 #[inline(never)]
320 #[must_use = "capacity planning can fail on overflow"]
321 pub fn max_total_output_len(
322 &self,
323 input_len: usize,
324 ) -> Result<usize, CapacityError> {
325 let reset = self.max_reset_output_len()?;
326 let transcode = self.max_transcode_output_len(input_len)?;
327 let finish = self.max_finish_output_len()?;
328 reset
329 .checked_add(transcode)
330 .and_then(|len| len.checked_add(finish))
331 .ok_or(CapacityError::OutputLengthOverflow)
332 }
333
334 /// Resets codec encode state, hook-owned state, and stream-start output.
335 ///
336 /// # Parameters
337 ///
338 /// - `output`: Complete output unit slice visible to the encoder.
339 /// - `output_index`: Absolute output unit index where writing starts.
340 ///
341 /// # Returns
342 ///
343 /// Returns the number of reset units written.
344 ///
345 /// # Errors
346 ///
347 /// Returns framework errors when the caller provides invalid or
348 /// insufficient output capacity. Returns domain errors when codec reset or
349 /// hook reset handling fails. Capacity and index failures occur before any
350 /// reset state is changed. Once reset execution starts, an error poisons
351 /// the engine until a later reset succeeds.
352 pub fn reset(
353 &mut self,
354 output: &mut [C::Unit],
355 output_index: usize,
356 ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
357 let required = self.max_reset_output_len()?;
358 TranscodeFailure::ensure_output_capacity(
359 output.len(),
360 output_index,
361 required,
362 )?;
363 self.lifecycle.on_reset_start();
364 self.hooks.reset_hooks(&mut self.codec);
365 let written = unsafe {
366 // SAFETY: The capacity check above reserves the codec's declared
367 // reset-output bound at `output_index`.
368 self.codec.encode_reset(output, output_index)
369 }
370 .map_err(TranscodeEncodeError::domain_reset)?;
371 assert!(
372 written <= required,
373 "Codec::encode_reset wrote beyond its reset bound",
374 );
375 self.lifecycle.on_reset_success();
376 Ok(written)
377 }
378
379 /// Encodes values into a caller-provided output buffer.
380 ///
381 /// The engine stops before consuming the next input value when the current
382 /// output buffer does not satisfy that value's planned capacity bound.
383 ///
384 /// # Parameters
385 ///
386 /// - `input`: Complete input value slice visible to the encoder.
387 /// - `input_index`: Absolute input value index where encoding starts.
388 /// - `output`: Complete output unit slice visible to the encoder.
389 /// - `output_index`: Absolute output unit index where writing starts.
390 ///
391 /// # Returns
392 ///
393 /// Returns progress describing input values consumed, output units written,
394 /// and why encoding stopped.
395 ///
396 /// # Errors
397 ///
398 /// Returns hook errors when `input_index` is outside `input`, when
399 /// `output_index` is outside `output`, or when hook planning or writing
400 /// rejects a value. Returns
401 /// [`TranscodeFailure::TranscodeBeforeReset`] when the engine has not
402 /// completed its first reset. Returns
403 /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
404 /// already finished and has not been reset, or
405 /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
406 /// failed after execution started.
407 pub fn transcode(
408 &mut self,
409 input: &[C::Value],
410 input_index: usize,
411 output: &mut [C::Unit],
412 output_index: usize,
413 ) -> Result<TranscodeProgress, TranscodeEncodeErrorOf<C>> {
414 self.lifecycle.on_transcode()?;
415 TranscodeFailure::ensure_transcode_indices(
416 input.len(),
417 input_index,
418 output.len(),
419 output_index,
420 )?;
421 let mut state =
422 EncodeState::new(input, input_index, output, output_index);
423
424 while state.has_input() {
425 // SAFETY: The loop condition proves that the current input cursor
426 // points at an available value.
427 let attempt = unsafe { state.context_unchecked() };
428 let outcome = self.encode_one(attempt)?;
429 if let Some(progress) = state.apply_encode_outcome(outcome) {
430 return Ok(progress);
431 }
432 }
433
434 Ok(state.complete_progress())
435 }
436
437 /// Finishes codec and hook-owned output after EOF.
438 ///
439 /// Finalization first finishes encode-side codec state through
440 /// [`Codec::encode_finish`], then lets hook implementations finish their
441 /// own retained state. The caller must provide enough output capacity for
442 /// [`TranscodeEncodeEngine::max_finish_output_len`], which includes both
443 /// the codec finish bound and the hook-owned finish bound.
444 ///
445 /// # Parameters
446 ///
447 /// - `output`: Complete output unit slice visible to the encoder.
448 /// - `output_index`: Absolute output unit index where writing starts.
449 ///
450 /// # Returns
451 ///
452 /// Returns the number of units written by finalization.
453 ///
454 /// # Errors
455 ///
456 /// Returns framework errors when the caller provides invalid or
457 /// insufficient output capacity. Returns domain errors when codec finish or
458 /// hook finalization fails. Returns
459 /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
460 /// completed its first reset. Returns
461 /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
462 /// already finished and has not been reset, or
463 /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
464 /// failed after execution started. Capacity and index failures occur before
465 /// finish execution and remain retryable; later failures poison the engine
466 /// until reset succeeds.
467 ///
468 /// # Panics
469 ///
470 /// Panics when the codec finish writes beyond
471 /// [`Codec::MAX_ENCODE_FINISH_UNITS`] or when the combined codec and hook
472 /// finalization writes beyond
473 /// [`TranscodeEncodeEngine::max_finish_output_len`].
474 pub fn finish(
475 &mut self,
476 output: &mut [C::Unit],
477 output_index: usize,
478 ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
479 self.lifecycle.on_finish_attempt()?;
480 let required = self.max_finish_output_len()?;
481 TranscodeFailure::ensure_output_capacity(
482 output.len(),
483 output_index,
484 required,
485 )?;
486 self.lifecycle.on_finish_start();
487 let finished = unsafe {
488 // SAFETY: The capacity check above reserves the codec's declared
489 // finish-output bound at `output_index`.
490 self.codec.encode_finish(output, output_index)
491 }
492 .map_err(TranscodeEncodeError::domain_finish)?;
493 assert!(
494 finished <= C::MAX_ENCODE_FINISH_UNITS,
495 "Codec::encode_finish wrote beyond its finish bound",
496 );
497 let written = self.hooks.finish_hooks(
498 &mut self.codec,
499 output,
500 output_index + finished,
501 )?;
502 assert!(
503 finished + written <= required,
504 "TranscodeEncodeEngine hook wrote beyond its finish bound",
505 );
506 self.lifecycle.on_finish_success();
507 Ok(finished + written)
508 }
509
510 /// Runs a complete one-shot `reset -> transcode -> finish` encode stream.
511 ///
512 /// The complete input is supplied as `input`, and output starts at index
513 /// `0` in `output`. Callers that need subranges should slice their
514 /// buffers before calling this method. Downstream one-shot encoder
515 /// helpers should call this engine method instead of reproducing the
516 /// reset, transcode, and finish sequence themselves.
517 ///
518 /// # Parameters
519 ///
520 /// - `input`: Complete input values.
521 /// - `output`: Output unit slice for the whole encoded stream.
522 ///
523 /// # Returns
524 ///
525 /// Returns the number of output units written.
526 ///
527 /// # Errors
528 ///
529 /// Returns framework errors for insufficient output or capacity overflow,
530 /// and domain errors from reset, encode, or finish.
531 #[inline]
532 pub fn transcode_complete_into(
533 &mut self,
534 input: &[C::Value],
535 output: &mut [C::Unit],
536 ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
537 <Self as Transcoder>::transcode_complete_into(self, input, output)
538 }
539
540 /// Encodes one value through the codec and unencodable-value hooks.
541 ///
542 /// This is the single entry point for `TranscodeConvertEngine` to drive
543 /// the encode side without accessing `codec` and `hooks` directly.
544 ///
545 /// # Parameters
546 ///
547 /// - `context`: Encode context for the current value.
548 ///
549 /// # Returns
550 ///
551 /// Returns whether the value was consumed or needs more output capacity.
552 ///
553 /// # Errors
554 ///
555 /// Returns an engine-domain error when the codec fails or when the hook
556 /// rejects an unencodable value.
557 pub(in crate::transcode) fn encode_one(
558 &mut self,
559 attempt: EncodeAttempt<'_, C::Value, C::Unit>,
560 ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
561 let encodable = {
562 let context = attempt.context();
563 self.codec.can_encode_value(context.input_value())
564 };
565 if encodable {
566 return self.encode_encodable_value(attempt);
567 }
568 let action = {
569 let context = attempt.context();
570 self.hooks
571 .handle_unencodable_encode(&mut self.codec, &context)?
572 };
573 self.apply_unencodable_action(action, attempt)
574 }
575
576 /// Encodes an encodable input value.
577 ///
578 /// # Parameters
579 ///
580 /// - `context`: Encode context for the current value.
581 ///
582 /// # Returns
583 ///
584 /// Returns the encode outcome for the current value.
585 ///
586 /// # Errors
587 ///
588 /// Returns a codec error when encoding fails.
589 ///
590 /// # Panics
591 ///
592 /// Panics when [`Codec::encode_len`] exceeds
593 /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`] or [`Codec::encode`] writes a
594 /// different number of units than planned.
595 fn encode_encodable_value(
596 &mut self,
597 attempt: EncodeAttempt<'_, C::Value, C::Unit>,
598 ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
599 let required = self.codec.encode_len(attempt.value());
600 assert!(
601 required <= C::MAX_ENCODE_UNITS_PER_VALUE,
602 "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE",
603 );
604 if attempt.available_output() < required {
605 return Ok(EncodeOutcome::need_output(
606 NonZeroUsize::new(required).expect(
607 "required output is non-zero when capacity is insufficient",
608 ),
609 ));
610 }
611 let (value, input_index, output, output_index) = attempt.into_parts();
612 let written = unsafe {
613 // SAFETY: The capacity check above reserves the exact value width.
614 self.codec.encode(value, output, output_index)
615 }
616 .map_err(|error| {
617 TranscodeEncodeError::domain_main(error, input_index)
618 })?;
619 assert!(
620 written == required,
621 "Codec::encode wrote a different length than Codec::encode_len",
622 );
623 Ok(EncodeOutcome::consumed(written))
624 }
625
626 /// Applies the hook-selected unencodable-value action.
627 ///
628 /// # Parameters
629 ///
630 /// - `action`: Policy action selected by the encode hooks.
631 /// - `context`: Encode context for the rejected input value.
632 ///
633 /// # Returns
634 ///
635 /// Returns the encode outcome for the current input value.
636 ///
637 /// # Errors
638 ///
639 /// Returns a codec error when replacement encoding fails.
640 #[inline(always)]
641 fn apply_unencodable_action(
642 &mut self,
643 action: EncodeUnencodableAction<C::Value>,
644 attempt: EncodeAttempt<'_, C::Value, C::Unit>,
645 ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
646 match action {
647 EncodeUnencodableAction::Reject => {
648 // Keep the streaming encoder usable for non-Clone values.
649 // The failure still carries the absolute input index; callers
650 // that need value context can add it in a higher-level facade.
651 Err(TranscodeEncodeError::unencodable_without_context(
652 attempt.input_index(),
653 ))
654 }
655 EncodeUnencodableAction::Skip => Ok(EncodeOutcome::consumed(0)),
656 EncodeUnencodableAction::Replace { value } => {
657 self.encode_replacement_value(value, attempt)
658 }
659 }
660 }
661
662 /// Encodes a hook-provided replacement value.
663 ///
664 /// # Parameters
665 ///
666 /// - `value`: Replacement value selected by hooks.
667 /// - `context`: Encode context for the original unencodable input value.
668 ///
669 /// # Returns
670 ///
671 /// Returns the encode outcome for the replacement.
672 ///
673 /// # Errors
674 ///
675 /// Returns a codec error when replacement encoding fails.
676 ///
677 /// # Panics
678 ///
679 /// Panics when hooks return a replacement value that the codec cannot
680 /// encode, [`Codec::encode_len`] exceeds
681 /// [`Codec::MAX_ENCODE_UNITS_PER_VALUE`], or [`Codec::encode`] writes a
682 /// different number of units than planned.
683 fn encode_replacement_value(
684 &mut self,
685 value: C::Value,
686 attempt: EncodeAttempt<'_, C::Value, C::Unit>,
687 ) -> Result<EncodeOutcome, TranscodeEncodeErrorOf<C>> {
688 assert!(
689 self.codec.can_encode_value(&value),
690 "EncodeUnencodableAction::Replace returned an unencodable replacement value",
691 );
692 let required = self.codec.encode_len(&value);
693 assert!(
694 required <= C::MAX_ENCODE_UNITS_PER_VALUE,
695 "Codec::encode_len exceeded Codec::MAX_ENCODE_UNITS_PER_VALUE",
696 );
697 if attempt.available_output() < required {
698 return Ok(EncodeOutcome::need_output(
699 NonZeroUsize::new(required).expect(
700 "required output is non-zero when capacity is insufficient",
701 ),
702 ));
703 }
704 let (_, input_index, output, output_index) = attempt.into_parts();
705 let written = unsafe {
706 // SAFETY: The capacity check above reserves the exact replacement
707 // value width, and the hook contract requires encodability.
708 self.codec.encode(&value, output, output_index)
709 }
710 .map_err(|error| {
711 TranscodeEncodeError::domain_main(error, input_index)
712 })?;
713 assert!(
714 written == required,
715 "Codec::encode wrote a different length than Codec::encode_len",
716 );
717 Ok(EncodeOutcome::consumed(written))
718 }
719}
720
721impl<C, H> Default for TranscodeEncodeEngine<C, H>
722where
723 C: Codec + Default,
724 H: TranscodeEncodeHooks<C> + Default,
725{
726 /// Creates a default buffered encoder engine.
727 ///
728 /// # Returns
729 ///
730 /// Returns an engine with default codec and hooks.
731 #[inline(always)]
732 fn default() -> Self {
733 Self::new(C::default(), H::default())
734 }
735}
736
737impl<C, H> Transcoder for TranscodeEncodeEngine<C, H>
738where
739 C: Codec,
740 H: TranscodeEncodeHooks<C>,
741{
742 type Input = C::Value;
743 type Output = C::Unit;
744 type Error = TranscodeEncodeErrorOf<C>;
745
746 /// Returns an upper bound for units produced from `input_len` values.
747 #[inline(always)]
748 fn max_transcode_output_len(
749 &self,
750 input_len: usize,
751 ) -> Result<usize, CapacityError> {
752 TranscodeEncodeEngine::max_transcode_output_len(self, input_len)
753 }
754
755 /// Returns the maximum units emitted when resetting stream state.
756 #[inline(always)]
757 fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
758 TranscodeEncodeEngine::max_reset_output_len(self)
759 }
760
761 /// Returns the maximum units emitted by finishing internal state.
762 #[inline(always)]
763 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
764 TranscodeEncodeEngine::max_finish_output_len(self)
765 }
766
767 /// Resets codec encode state, hook-owned state, and stream-start output.
768 #[inline(always)]
769 fn reset(
770 &mut self,
771 output: &mut [C::Unit],
772 output_index: usize,
773 ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
774 TranscodeEncodeEngine::reset(self, output, output_index)
775 }
776
777 /// Encodes input values into caller-provided output units.
778 #[inline(always)]
779 fn transcode(
780 &mut self,
781 input: &[C::Value],
782 input_index: usize,
783 output: &mut [C::Unit],
784 output_index: usize,
785 ) -> Result<TranscodeProgress, TranscodeEncodeErrorOf<C>> {
786 TranscodeEncodeEngine::transcode(
787 self,
788 input,
789 input_index,
790 output,
791 output_index,
792 )
793 }
794
795 /// Finishes hook-owned encoder state.
796 #[inline(always)]
797 fn finish(
798 &mut self,
799 output: &mut [C::Unit],
800 output_index: usize,
801 ) -> Result<usize, TranscodeEncodeErrorOf<C>> {
802 TranscodeEncodeEngine::finish(self, output, output_index)
803 }
804}
805
806impl<C, H> TranscodeEncoder for TranscodeEncodeEngine<C, H>
807where
808 C: Codec,
809 H: TranscodeEncodeHooks<C>,
810{
811 type EncodeError = C::EncodeError;
812}