Skip to main content

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}