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}