Skip to main content

qubit_codec/buffered/
buffered_encode_engine.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Reusable buffered encoder engine.
11
12use super::{
13    buffered_encode_hooks::BufferedEncodeHooks,
14    encode_context::EncodeContext,
15    encode_plan::EncodePlan,
16    encode_state::EncodeState,
17    encode_step::EncodeStep,
18    finish_error::FinishError,
19    transcode_progress::TranscodeProgress,
20};
21use crate::{
22    CapacityError,
23    Codec,
24    codec::assert_unit_bounds,
25};
26
27/// Reusable buffered encoding engine for codec-backed encoders.
28///
29/// The engine owns the low-level codec and hook object. It keeps the common
30/// buffered encoding loop private: input-index validation, output-capacity
31/// checks, input consumption, output progress, and [`crate::TranscodeStatus`]
32/// reporting.
33///
34/// Use this type to build a streaming encoder over a one-value [`Codec`]. The
35/// engine does not allocate output. It repeatedly asks hooks to plan one input
36/// value, verifies that the caller-provided output slice can hold that plan, and
37/// then lets the hooks write the value. If the next value would not fit, the
38/// engine returns [`crate::TranscodeStatus::NeedOutput`] without consuming that
39/// value; the caller can provide a larger or fresh output buffer and resume
40/// with the returned input index.
41///
42/// For the common strict policy that simply wraps codec errors, use
43/// [`crate::CodecBufferedEncoder`]. Use `BufferedEncodeEngine` directly when
44/// the encode policy needs custom planning, replacement, skipped values, or
45/// finish-time output.
46///
47/// # Example
48///
49/// ```rust
50/// use core::{
51///     convert::Infallible,
52///     num::NonZeroUsize,
53/// };
54/// use qubit_codec::{
55///     BufferedEncodeEngine,
56///     BufferedEncodeHooks,
57///     Codec,
58///     CodecEncodeError,
59///     EncodeContext,
60///     EncodePlan,
61///     TranscodeStatus,
62/// };
63///
64/// #[derive(Clone, Copy)]
65/// struct ByteCodec;
66///
67/// unsafe impl Codec for ByteCodec {
68///     type Value = u8;
69///     type Unit = u8;
70///     type DecodeError = Infallible;
71///     type EncodeError = Infallible;
72///
73///     fn min_units_per_value(&self) -> NonZeroUsize {
74///         NonZeroUsize::MIN
75///     }
76///
77///     fn max_units_per_value(&self) -> NonZeroUsize {
78///         NonZeroUsize::MIN
79///     }
80///
81///     unsafe fn decode_unchecked(
82///         &self,
83///         input: &[u8],
84///         index: usize,
85///     ) -> Result<(u8, NonZeroUsize), Self::DecodeError> {
86///         Ok((input[index], NonZeroUsize::MIN))
87///     }
88///
89///     unsafe fn encode_unchecked(
90///         &self,
91///         value: &u8,
92///         output: &mut [u8],
93///         index: usize,
94///     ) -> Result<usize, Self::EncodeError> {
95///         output[index] = *value;
96///         Ok(1)
97///     }
98/// }
99///
100/// struct StrictHooks;
101///
102/// impl BufferedEncodeHooks<ByteCodec> for StrictHooks {
103///     type Error = CodecEncodeError<Infallible>;
104///     type PlanAction = ();
105///
106///     fn prepare_encode(
107///         &mut self,
108///         codec: &ByteCodec,
109///         _value: &u8,
110///         _input_index: usize,
111///     ) -> Result<EncodePlan<()>, Self::Error> {
112///         Ok(EncodePlan::new(codec.max_units_per_value().get(), ()))
113///     }
114///
115///     unsafe fn write_encode(
116///         &mut self,
117///         codec: &ByteCodec,
118///         context: EncodeContext<'_, u8, u8>,
119///         _plan: EncodePlan<()>,
120///     ) -> Result<usize, Self::Error> {
121///         unsafe {
122///             codec.encode_unchecked(context.input_value, context.output, context.output_index)
123///         }
124///         .map_err(|error| CodecEncodeError::encode(error, context.input_index))
125///     }
126///
127///     fn invalid_input_index(
128///         &mut self,
129///         _codec: &ByteCodec,
130///         index: usize,
131///         input_len: usize,
132///     ) -> Self::Error {
133///         CodecEncodeError::invalid_input_index(index, input_len)
134///     }
135///
136///     fn invalid_output_index(
137///         &mut self,
138///         _codec: &ByteCodec,
139///         index: usize,
140///         output_len: usize,
141///     ) -> Self::Error {
142///         CodecEncodeError::invalid_output_index(index, output_len)
143///     }
144/// }
145///
146/// let mut engine = BufferedEncodeEngine::new(ByteCodec, StrictHooks);
147/// let input = [1_u8, 2, 3];
148/// let mut output = [0_u8; 2];
149///
150/// let progress = engine.transcode(&input, 0, &mut output, 0)?;
151/// match progress.status() {
152///     TranscodeStatus::Complete => unreachable!("output is intentionally short"),
153///     TranscodeStatus::NeedOutput { output_index, .. } => {
154///         assert_eq!(2, output_index);
155///         assert_eq!([1, 2], output);
156///         // Write out `output[..output_index]`, then resume at
157///         // `progress.read()` with fresh output capacity.
158///     }
159///     TranscodeStatus::NeedInput { .. } => unreachable!("encoders do not read encoded input"),
160/// }
161/// # Ok::<(), CodecEncodeError<Infallible>>(())
162/// ```
163///
164/// # Type Parameters
165///
166/// - `C`: Low-level codec used by the engine.
167/// - `H`: Policy hook object used by the engine.
168#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
169pub struct BufferedEncodeEngine<C, H> {
170    /// Low-level codec used for one-value encoding.
171    pub(super) codec: C,
172    /// Policy hooks used for planning and writing values.
173    pub(super) hooks: H,
174}
175
176impl<C, H> BufferedEncodeEngine<C, H>
177where
178    C: Codec,
179    H: BufferedEncodeHooks<C>,
180{
181    /// Creates a buffered encoder engine.
182    ///
183    /// # Parameters
184    ///
185    /// - `codec`: Low-level codec used for one-value encoding.
186    /// - `hooks`: Policy hooks used for planning and writing values.
187    ///
188    /// # Returns
189    ///
190    /// Returns a buffered encoder engine.
191    #[must_use]
192    #[inline(always)]
193    pub const fn new(codec: C, hooks: H) -> Self {
194        Self { codec, hooks }
195    }
196
197    /// Prepares one value for writing through the configured encode hooks.
198    ///
199    /// # Parameters
200    ///
201    /// - `input_value`: Input value to be planned.
202    /// - `input_index`: Absolute input value index at which this value is located.
203    ///
204    /// # Returns
205    ///
206    /// Returns an encode plan containing the maximum planned output width and action.
207    #[inline(always)]
208    pub(crate) fn prepare_value(
209        &mut self,
210        input_value: &C::Value,
211        input_index: usize,
212    ) -> Result<EncodePlan<H::PlanAction>, H::Error> {
213        self.hooks.prepare_encode(&self.codec, input_value, input_index)
214    }
215
216    /// Writes one value using a previously prepared encode context.
217    ///
218    /// # Safety
219    ///
220    /// The caller must guarantee that the context was built from an encode plan
221    /// whose output-capacity bound is writable from `context.output_index`.
222    ///
223    /// # Parameters
224    ///
225    /// - `context`: Context containing prepared plan, input index, and output.
226    /// - `plan`: Prepared encode plan that selected the write action.
227    ///
228    /// # Returns
229    ///
230    /// Returns the number of output units written, which must not exceed the
231    /// prepared capacity.
232    ///
233    /// # Errors
234    ///
235    /// Returns `H::Error` when hook-specific writing fails.
236    #[inline(always)]
237    pub(crate) unsafe fn write_prepared_value(
238        &mut self,
239        context: EncodeContext<'_, C::Value, C::Unit>,
240        plan: EncodePlan<H::PlanAction>,
241    ) -> Result<usize, H::Error> {
242        // SAFETY: Forwarded from this method's safety contract.
243        unsafe { self.hooks.write_encode(&self.codec, context, plan) }
244    }
245
246    /// Returns a conservative upper bound for output units needed for `input_len` values.
247    ///
248    /// # Parameters
249    ///
250    /// - `input_len`: Number of input values the caller plans to encode.
251    ///
252    /// # Returns
253    ///
254    /// Returns a conservative upper bound, or a capacity error on arithmetic
255    /// overflow.
256    #[must_use = "capacity planning can fail on overflow"]
257    #[inline(always)]
258    pub fn max_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
259        assert_unit_bounds::<C>(&self.codec);
260        self.hooks.max_output_len(&self.codec, input_len)
261    }
262
263    /// Returns the maximum output units emitted by finishing hook-owned state.
264    ///
265    /// # Returns
266    ///
267    /// Returns the hook-provided final output bound.
268    #[must_use]
269    #[inline(always)]
270    pub fn max_finish_output_len(&self) -> usize {
271        self.hooks.max_finish_output_len(&self.codec)
272    }
273
274    /// Resets hook-owned state.
275    ///
276    /// # Parameters
277    ///
278    /// - `self`: Encoder instance whose hook state is reset.
279    ///
280    /// # Returns
281    ///
282    /// Returns unit `()`.
283    #[inline(always)]
284    pub fn reset(&mut self) {
285        self.hooks.reset(&self.codec);
286    }
287
288    /// Encodes values into a caller-provided output buffer.
289    ///
290    /// The engine stops before consuming the next input value when the current
291    /// output buffer does not satisfy that value's planned capacity bound.
292    ///
293    /// # Parameters
294    ///
295    /// - `input`: Complete input value slice visible to the encoder.
296    /// - `input_index`: Absolute input value index where encoding starts.
297    /// - `output`: Complete output unit slice visible to the encoder.
298    /// - `output_index`: Absolute output unit index where writing starts.
299    ///
300    /// # Returns
301    ///
302    /// Returns progress describing input values consumed, output units written,
303    /// and why encoding stopped.
304    ///
305    /// # Errors
306    ///
307    /// Returns hook errors when `input_index` is outside `input`, when
308    /// `output_index` is outside `output`, or when hook planning or writing
309    /// rejects a value.
310    pub fn transcode(
311        &mut self,
312        input: &[C::Value],
313        input_index: usize,
314        output: &mut [C::Unit],
315        output_index: usize,
316    ) -> Result<TranscodeProgress, H::Error> {
317        if input_index > input.len() {
318            return Err(self.hooks.invalid_input_index(&self.codec, input_index, input.len()));
319        }
320        if output_index > output.len() {
321            return Err(self.hooks.invalid_output_index(&self.codec, output_index, output.len()));
322        }
323        assert_unit_bounds::<C>(&self.codec);
324        let mut state = EncodeState::new(input, input_index, output, output_index);
325
326        while state.has_input() {
327            // SAFETY: The loop condition proves that the current input cursor
328            // points at an available value.
329            let context = unsafe { state.context_unchecked() };
330            let step = self.encode_step(context)?;
331            if let Some(progress) = step.apply_to_state(&mut state) {
332                return Ok(progress);
333            }
334        }
335
336        Ok(state.complete_progress())
337    }
338
339    /// Builds an invalid-output-index error through the configured hooks.
340    ///
341    /// # Parameters
342    ///
343    /// - `index`: Invalid output index.
344    /// - `output_len`: Output slice length.
345    ///
346    /// # Returns
347    ///
348    /// Returns the hook-specific error.
349    #[inline(always)]
350    pub(crate) fn invalid_output_index(&mut self, index: usize, output_len: usize) -> H::Error {
351        self.hooks.invalid_output_index(&self.codec, index, output_len)
352    }
353
354    /// Finishes hook-owned output after EOF.
355    ///
356    /// The engine owns no final output state itself. Hook implementations may
357    /// finish their own retained state and emit final output after the caller has
358    /// supplied all input values. The caller must provide enough output capacity
359    /// for [`BufferedEncodeEngine::max_finish_output_len`].
360    ///
361    /// # Parameters
362    ///
363    /// - `output`: Complete output unit slice visible to the encoder.
364    /// - `output_index`: Absolute output unit index where writing starts.
365    ///
366    /// # Returns
367    ///
368    /// Returns the number of units written by finalization.
369    ///
370    /// # Errors
371    ///
372    /// Returns [`FinishError`] when the caller provides invalid or insufficient
373    /// output capacity, or when hook finalization fails.
374    ///
375    /// # Panics
376    ///
377    /// Panics when the hook writes or reports more final output units than
378    /// [`BufferedEncodeEngine::max_finish_output_len`] declared.
379    pub fn finish(&mut self, output: &mut [C::Unit], output_index: usize) -> Result<usize, FinishError<H::Error>> {
380        let required = self.max_finish_output_len();
381        FinishError::ensure_output_capacity(output.len(), output_index, required)?;
382        let output_end = output_index + required;
383        let output = &mut output[..output_end];
384        let written = self
385            .hooks
386            .finish(&self.codec, output, output_index)
387            .map_err(FinishError::source)?;
388        assert!(
389            written <= required,
390            "BufferedEncodeEngine hook wrote beyond its finish bound",
391        );
392        Ok(written)
393    }
394
395    /// Encodes one value attempt into a normalized encode step.
396    ///
397    /// # Parameters
398    ///
399    /// - `context`: Current value, absolute input index, and target output cursor.
400    ///
401    /// # Returns
402    ///
403    /// Returns an encode step describing either written output or missing output
404    /// capacity.
405    ///
406    /// # Errors
407    ///
408    /// Returns hook errors when planning or writing rejects the value.
409    #[inline]
410    pub(super) fn encode_step(
411        &mut self,
412        context: EncodeContext<'_, C::Value, C::Unit>,
413    ) -> Result<EncodeStep, H::Error> {
414        let plan = self.prepare_value(context.input_value, context.input_index)?;
415        let max_output_units = plan.max_output_units;
416        let available = context.available_output();
417        if available < max_output_units {
418            return Ok(EncodeStep::need_output(max_output_units, available));
419        }
420
421        // SAFETY: The capacity check above guarantees the bound requested by
422        // the prepared plan.
423        let written = unsafe { self.write_prepared_value(context, plan) }?;
424        assert!(
425            written <= max_output_units,
426            "BufferedEncodeEngine hook wrote beyond its prepared capacity bound",
427        );
428        Ok(EncodeStep::written(written))
429    }
430}