dsi_bitstream/dispatch/mod.rs
1/*
2 * SPDX-FileCopyrightText: 2025 Tommaso Fontana
3 * SPDX-FileCopyrightText: 2025 Inria
4 * SPDX-FileCopyrightText: 2025 Sebastiano Vigna
5 *
6 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
7 */
8
9//! Programmable static and dynamic dispatch for codes.
10//!
11//! The modules in [codes](super::codes), such as [`omega`], extend [`BitRead`]
12//! and [`BitWrite`] to provide a way to read and write codes from a bitstream.
13//! The user can thus select at compile time the desired trait and use the
14//! associated codes.
15//!
16//! In many contexts, however, one does not want to commit to a specific set of
17//! codes, but rather would like to write generic methods that accept some code
18//! as an input and then use it to read or write values. For example, a stream
19//! encoder might let the user choose between different codes, depending on the
20//! user's knowledge of the distribution of the values to be encoded.
21//!
22//! Having dynamic selection of a code, however, entails a performance cost, as,
23//! for example, a match statement must be used to select the correct code. To
24//! mitigate this cost, we provide two types of dispatch traits and three types
25//! of implementations based on them.
26//!
27//! # Dispatch Traits
28//!
29//! The traits [`DynamicCodeRead`] and [`DynamicCodeWrite`] are the most generic
30//! ones, and provide a method to read and write a code from a bitstream. By
31//! implementing them, you can write a method accepting one or more unspecified
32//! codes, and operate with them. For example, in this function we read twice a
33//! code and return the sum of the two values, but make no commitment on which
34//! code we will be using:
35//!```rust
36//! use dsi_bitstream::prelude::*;
37//! use dsi_bitstream::dispatch::{CodesRead, DynamicCodeRead};
38//! use std::fmt::Debug;
39//!
40//! fn read_two_codes_and_sum<
41//! E: Endianness,
42//! R: CodesRead<E> + ?Sized,
43//! GR: DynamicCodeRead
44//! >(
45//! reader: &mut R,
46//! code: GR,
47//! ) -> Result<u64, R::Error> {
48//! Ok(code.read(reader)? + code.read(reader)?)
49//! }
50//!```
51//! On the other hand, the traits [`StaticCodeRead`] and [`StaticCodeWrite`] are
52//! specialized for a reader or writer of given endianness. This means that they
53//! can in principle be implemented for a specific code by storing a function
54//! pointer, with much less runtime overhead.
55//!```rust
56//! use dsi_bitstream::prelude::*;
57//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead};
58//! use std::fmt::Debug;
59//!
60//! fn read_two_codes_and_sum<
61//! E: Endianness,
62//! R: CodesRead<E> + ?Sized,
63//! SR: StaticCodeRead<E, R>
64//! >(
65//! reader: &mut R,
66//! code: SR,
67//! ) -> Result<u64, R::Error> {
68//! Ok(code.read(reader)? + code.read(reader)?)
69//! }
70//!```
71//!
72//! Note that the syntax for invoking the methods in the two groups of traits is
73//! identical, but the type variables are on the method in the first case, and
74//! on the trait in the second case.
75//!
76//! # Implementations
77//!
78//! The [`Codes`] enum variants represent all the available codes. [`Codes`]
79//! implements all the dispatch traits, so it can be used to read or write any
80//! code both in a generic and in a specific way. It also implements the
81//! [`CodeLen`] trait, which provides a method to compute the length of a
82//! codeword. The only exception is for [minimal binary
83//! codes](crate::codes::minimal_binary), which have a separate
84//! [`MinimalBinary`] structure with the same functionality, as they cannot
85//! represent all integers.
86//!
87//! If Rust supported const enums in traits, one could create structures
88//! with const enum type parameters of type [`Codes`], and then the compiler
89//! would be able to optimize away the code selection at compile time. However,
90//! this is not currently possible, so we provide a workaround using a
91//! zero-sized struct with a `const usize` parameter, [`ConstCode`], that
92//! implements all the dispatch traits and [`CodeLen`], and can be used to
93//! select the code at compile time. The parameter must be taken from the
94//! [`code_consts`] module, which contains constants for all parameterless
95//! codes, and for the codes with parameters up to 10. For example, here at
96//! execution time there will be no test to select a code, even if
97//! `read_two_codes_and_sum` is generic:
98//!```rust
99//! use dsi_bitstream::prelude::*;
100//! use dsi_bitstream::dispatch::{code_consts, CodesRead, DynamicCodeRead};
101//! use std::fmt::Debug;
102//!
103//! fn read_two_codes_and_sum<
104//! E: Endianness,
105//! R: CodesRead<E> + ?Sized,
106//! GR: DynamicCodeRead
107//! >(
108//! reader: &mut R,
109//! code: GR,
110//! ) -> Result<u64, R::Error> {
111//! Ok(code.read(reader)? + code.read(reader)?)
112//! }
113//!
114//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
115//! reader: &mut R,
116//! ) -> Result<u64, R::Error> {
117//! read_two_codes_and_sum(reader, ConstCode::<{code_consts::GAMMA}>)
118//! }
119//!```
120//!
121//! Working with [`ConstCode`] is very efficient, but it forces the choice of a
122//! code at compile time. If you need to read or write a code multiple times on
123//! the same type of bitstream, you can use the structs [`FuncCodeReader`] and
124//! [`FuncCodeWriter`], which implement [`StaticCodeRead`] and
125//! [`StaticCodeWrite`] by storing a function pointer.
126//!
127//! A value of type [`FuncCodeReader`] or [`FuncCodeWriter`] can be created by
128//! calling their `new` method with a variant of the [`Codes`] enum. As in the
129//! case of [`ConstCode`], there are pointers for all parameterless codes, and
130//! for the codes with parameters up to 10, and the method will return an error
131//! if the code is not supported.
132//!
133//! For example:
134//!```rust
135//! use dsi_bitstream::prelude::*;
136//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
137//! use std::fmt::Debug;
138//!
139//! fn read_two_codes_and_sum<
140//! E: Endianness,
141//! R: CodesRead<E> + ?Sized,
142//! SR: StaticCodeRead<E, R>
143//! >(
144//! reader: &mut R,
145//! code: SR,
146//! ) -> Result<u64, R::Error> {
147//! Ok(code.read(reader)? + code.read(reader)?)
148//! }
149//!
150//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
151//! reader: &mut R,
152//! ) -> Result<u64, R::Error> {
153//! read_two_codes_and_sum(reader, FuncCodeReader::new(Codes::Gamma).unwrap())
154//! }
155//!```
156//! Note that we [`unwrap`](core::result::Result::unwrap) the result of the
157//! [`new`](FuncCodeReader::new) method, as we know that a function pointer
158//! exists for the γ code.
159//!
160//! # Workaround to Limitations
161//!
162//! Both [`ConstCode`] and [`FuncCodeReader`] / [`FuncCodeWriter`] are limited
163//! to a fixed set of codes. If you need to work with a code that is not
164//! supported by them, you can implement your own version. For example, here we
165//! define a zero-sized struct that represents a Rice code with a fixed parameter
166//! `LOG2_B`:
167//! ```rust
168//! use dsi_bitstream::prelude::*;
169//! use dsi_bitstream::dispatch::{CodesRead, CodesWrite};
170//! use dsi_bitstream::dispatch::{DynamicCodeRead, DynamicCodeWrite};
171//! use std::fmt::Debug;
172//!
173//! #[derive(Clone, Copy, Debug, Default)]
174//! pub struct Rice<const LOG2_B: usize>;
175//!
176//! impl<const LOG2_B: usize> DynamicCodeRead for Rice<LOG2_B> {
177//! fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
178//! &self,
179//! reader: &mut CR,
180//! ) -> Result<u64, CR::Error> {
181//! reader.read_rice(LOG2_B)
182//! }
183//! }
184//!
185//! impl<const LOG2_B: usize> DynamicCodeWrite for Rice<LOG2_B> {
186//! fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
187//! &self,
188//! writer: &mut CW,
189//! n: u64,
190//! ) -> Result<usize, CW::Error> {
191//! writer.write_rice(n, LOG2_B)
192//! }
193//! }
194//!
195//! impl<const LOG2_B: usize> CodeLen for Rice<LOG2_B> {
196//! #[inline]
197//! fn len(&self, n: u64) -> usize {
198//! len_rice(n, LOG2_B)
199//! }
200//! }
201//! ```
202//!
203//! Suppose instead you need to pass a [`StaticCodeRead`] to a method using a
204//! code that is not supported directly by [`FuncCodeReader`]. You can create a
205//! new [`FuncCodeReader`] using a provided function:
206//!```rust
207//! use dsi_bitstream::prelude::*;
208//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
209//! use std::fmt::Debug;
210//!
211//! fn read_two_codes_and_sum<
212//! E: Endianness,
213//! R: CodesRead<E> + ?Sized,
214//! SR: StaticCodeRead<E, R>
215//! >(
216//! reader: &mut R,
217//! code: SR,
218//! ) -> Result<u64, R::Error> {
219//! Ok(code.read(reader)? + code.read(reader)?)
220//! }
221//!
222//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
223//! reader: &mut R,
224//! ) -> Result<u64, R::Error> {
225//! read_two_codes_and_sum(reader, FuncCodeReader::new_with_func(|r: &mut R| r.read_rice(20)))
226//! }
227//!```
228
229use crate::prelude::Endianness;
230use crate::prelude::{BitRead, BitWrite};
231
232use crate::codes::*;
233
234pub mod codes;
235pub use codes::*;
236
237/// Error returned when a code is not supported for dispatch.
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
239pub enum DispatchError {
240 /// The code is not supported for dynamic dispatch.
241 UnsupportedCode(Codes),
242 /// The code constant is not supported.
243 UnsupportedCodeConst(usize),
244}
245
246impl core::fmt::Display for DispatchError {
247 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
248 match self {
249 Self::UnsupportedCode(code) => {
250 write!(f, "unsupported dispatch for code {:?}", code)
251 }
252 Self::UnsupportedCodeConst(c) => {
253 write!(f, "unsupported code constant {}", c)
254 }
255 }
256 }
257}
258
259impl core::error::Error for DispatchError {}
260
261pub mod r#static;
262pub use r#static::{ConstCode, code_consts};
263
264pub mod dynamic;
265pub use dynamic::{FuncCodeLen, FuncCodeReader, FuncCodeWriter};
266
267pub mod factory;
268pub use factory::{CodesReaderFactory, CodesReaderFactoryHelper, FactoryFuncCodeReader};
269
270/// Convenience extension trait for reading all the codes supported by the
271/// library.
272///
273/// A blanket implementation is provided for all types that implement the
274/// necessary traits.
275///
276/// This trait is mainly useful internally to implement the dispatch
277/// traits [`DynamicCodeRead`], [`StaticCodeRead`], [`DynamicCodeWrite`], and
278/// [`StaticCodeWrite`]. The user might find it more useful to define their own
279/// convenience trait that includes only the codes they need.
280pub trait CodesRead<E: Endianness>:
281 BitRead<E>
282 + GammaRead<E>
283 + DeltaRead<E>
284 + ZetaRead<E>
285 + OmegaRead<E>
286 + PiRead<E>
287 + MinimalBinaryRead<E>
288 + GolombRead<E>
289 + RiceRead<E>
290 + ExpGolombRead<E>
291 + VByteBeRead<E>
292 + VByteLeRead<E>
293{
294}
295
296impl<E: Endianness, B> CodesRead<E> for B where
297 B: BitRead<E>
298 + GammaRead<E>
299 + DeltaRead<E>
300 + ZetaRead<E>
301 + OmegaRead<E>
302 + PiRead<E>
303 + MinimalBinaryRead<E>
304 + GolombRead<E>
305 + RiceRead<E>
306 + ExpGolombRead<E>
307 + VByteBeRead<E>
308 + VByteLeRead<E>
309{
310}
311
312/// Convenience extension trait for writing all the codes supported by the
313/// library.
314///
315/// A blanket implementation is provided for all types that implement the
316/// necessary traits.
317///
318/// This trait is mainly useful internally to implement the dispatch
319/// traits [`DynamicCodeWrite`] and [`StaticCodeWrite`]. The user might find it
320/// more useful to define their own convenience trait that includes only the
321/// codes they need.
322pub trait CodesWrite<E: Endianness>:
323 BitWrite<E>
324 + GammaWrite<E>
325 + DeltaWrite<E>
326 + ZetaWrite<E>
327 + OmegaWrite<E>
328 + MinimalBinaryWrite<E>
329 + PiWrite<E>
330 + GolombWrite<E>
331 + RiceWrite<E>
332 + ExpGolombWrite<E>
333 + VByteBeWrite<E>
334 + VByteLeWrite<E>
335{
336}
337
338impl<E: Endianness, B> CodesWrite<E> for B where
339 B: BitWrite<E>
340 + GammaWrite<E>
341 + DeltaWrite<E>
342 + ZetaWrite<E>
343 + OmegaWrite<E>
344 + MinimalBinaryWrite<E>
345 + PiWrite<E>
346 + GolombWrite<E>
347 + RiceWrite<E>
348 + ExpGolombWrite<E>
349 + VByteBeWrite<E>
350 + VByteLeWrite<E>
351{
352}
353
354/// A trait providing a method to read a code from a generic [`CodesRead`].
355///
356/// The difference with [`StaticCodeRead`] is that this trait is more generic,
357/// as the [`CodesRead`] is a parameter of the method, and not of the trait.
358pub trait DynamicCodeRead {
359 fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
360 &self,
361 reader: &mut CR,
362 ) -> Result<u64, CR::Error>;
363}
364
365/// A trait providing a method to write a code to a generic [`CodesWrite`].
366///
367/// The difference with [`StaticCodeWrite`] is that this trait is more generic,
368/// as the [`CodesWrite`] is a parameter of the method, and not of the trait.
369pub trait DynamicCodeWrite {
370 fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
371 &self,
372 writer: &mut CW,
373 n: u64,
374 ) -> Result<usize, CW::Error>;
375}
376
377/// A trait providing a method to read a code from a [`CodesRead`] specified as
378/// trait type parameter.
379///
380/// The difference with [`DynamicCodeRead`] is that this trait is
381/// more specialized, as the [`CodesRead`] is a parameter of the
382/// trait.
383///
384/// For a fixed code this trait may be implemented by storing
385/// a function pointer.
386pub trait StaticCodeRead<E: Endianness, CR: CodesRead<E> + ?Sized> {
387 fn read(&self, reader: &mut CR) -> Result<u64, CR::Error>;
388}
389
390/// A trait providing a method to write a code to a [`CodesWrite`]
391/// specified as a trait type parameter.
392///
393/// The difference with [`DynamicCodeWrite`] is that this trait is
394/// more specialized, as the [`CodesWrite`] is a parameter of the
395/// trait.
396///
397/// For a fixed code this trait may be implemented by storing a function
398/// pointer.
399pub trait StaticCodeWrite<E: Endianness, CW: CodesWrite<E> + ?Sized> {
400 fn write(&self, writer: &mut CW, n: u64) -> Result<usize, CW::Error>;
401}
402
403/// A trait providing a generic method to compute the length of a codeword.
404pub trait CodeLen {
405 /// Returns the length of the codeword for `n`.
406 #[must_use]
407 fn len(&self, n: u64) -> usize;
408}