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 code traits in [codes](super::codes), such as
12//! [`Omega`](crate::codes::omega), extend [`BitRead`] and [`BitWrite`] to
13//! provide a way to read and write codes from a bitstream. The user can thus
14//! select at compile time the desired trait and use the 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 is an enum whose variants represent all the available
79//! codes. It implements all the dispatch traits, so it can be used to read or
80//! write any code both in a generic and in a specific way. It also implements
81//! the [`CodeLen`] trait, which provides a method to compute the length of a
82//! codeword.
83//!
84//! If Rust would support const enums in traits, one could create structures
85//! with const enum type parameters of type [`Codes`], and then the compiler
86//! would be able to optimize away the code selection at compile time. However,
87//! this is not currently possible, so we provide a workaround using a
88//! zero-sized struct with a `const usize` parameter, [`ConstCode`], that
89//! implements all the dispatch traits and [`CodeLen`], and can be used to
90//! select the code at compile time. The parameter must be taken from the
91//! [`code_consts`] module, which contains constants for all parameterless
92//! codes, and for the codes with parameters up to 10. For example, here at
93//! execution time there will be no test to select a code, even if
94//! `read_two_codes_and_sum` is generic:
95//!```rust
96//! use dsi_bitstream::prelude::*;
97//! use dsi_bitstream::dispatch::{code_consts, CodesRead, DynamicCodeRead};
98//! use std::fmt::Debug;
99//!
100//! fn read_two_codes_and_sum<
101//! E: Endianness,
102//! R: CodesRead<E> + ?Sized,
103//! GR: DynamicCodeRead
104//! >(
105//! reader: &mut R,
106//! code: GR,
107//! ) -> Result<u64, R::Error> {
108//! Ok(code.read(reader)? + code.read(reader)?)
109//! }
110//!
111//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
112//! reader: &mut R,
113//! ) -> Result<u64, R::Error> {
114//! read_two_codes_and_sum(reader, ConstCode::<{code_consts::GAMMA}>)
115//! }
116//!```
117//!
118//! Working with [`ConstCode`] is very efficient, but it forces the choice of a
119//! code at compile time. If you need to read or write a code multiple times on
120//! the same type of bitstream, you can use the structs [`FuncCodeReader`] and
121//! [`FuncCodeWriter`], which implement [`StaticCodeRead`] and
122//! [`StaticCodeWrite`] by storing a function pointer.
123//!
124//! A value of type [`FuncCodeReader`] or [`FuncCodeWriter`] can be created by
125//! calling their `new` method with a variant of the [`Codes`] enum. As in the
126//! case of [`ConstCode`], there are pointers for all parameterless codes, and
127//! for the codes with parameters up to 10, and the method will return an error
128//! if the code is not supported.
129//!
130//! For example:
131//!```rust
132//! use dsi_bitstream::prelude::*;
133//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
134//! use std::fmt::Debug;
135//!
136//! fn read_two_codes_and_sum<
137//! E: Endianness,
138//! R: CodesRead<E> + ?Sized,
139//! SR: StaticCodeRead<E, R>
140//! >(
141//! reader: &mut R,
142//! code: SR,
143//! ) -> Result<u64, R::Error> {
144//! Ok(code.read(reader)? + code.read(reader)?)
145//! }
146//!
147//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
148//! reader: &mut R,
149//! ) -> Result<u64, R::Error> {
150//! read_two_codes_and_sum(reader, FuncCodeReader::new(Codes::Gamma).unwrap())
151//! }
152//!```
153//! Note that we [`unwrap`](core::result::Result::unwrap) the result of the
154//! [`new`](FuncCodeReader::new) method, as we know that a function pointer
155//! exists for the γ code.
156//!
157//! # Workaround to Limitations
158//!
159//! Both [`ConstCode`] and [`FuncCodeReader`] / [`FuncCodeWriter`] are limited
160//! to a fixed set of codes. If you need to work with a code that is not
161//! supported by them, you can implement your own version. For example, here we
162//! define a zero-sized struct that represent a Rice code with a fixed parameter
163//! `LOG2_B`:
164//! ```rust
165//! use dsi_bitstream::prelude::*;
166//! use dsi_bitstream::dispatch::{CodesRead, CodesWrite};
167//! use dsi_bitstream::dispatch::{DynamicCodeRead, DynamicCodeWrite};
168//! use std::fmt::Debug;
169//!
170//! #[derive(Clone, Copy, Debug, Default)]
171//! pub struct Rice<const LOG2_B: usize>;
172//!
173//! impl<const LOG2_B: usize> DynamicCodeRead for Rice<LOG2_B> {
174//! fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
175//! &self,
176//! reader: &mut CR,
177//! ) -> Result<u64, CR::Error> {
178//! reader.read_rice(LOG2_B)
179//! }
180//! }
181//!
182//! impl<const LOG2_B: usize> DynamicCodeWrite for Rice<LOG2_B> {
183//! fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
184//! &self,
185//! writer: &mut CW,
186//! value: u64,
187//! ) -> Result<usize, CW::Error> {
188//! writer.write_rice(value, LOG2_B)
189//! }
190//! }
191//!
192//! impl<const LOG2_B: usize> CodeLen for Rice<LOG2_B> {
193//! #[inline]
194//! fn len(&self, value: u64) -> usize {
195//! len_rice(value, LOG2_B)
196//! }
197//! }
198//! ```
199//!
200//! Suppose instead you need to pass a [`StaticCodeRead`] to a method using a
201//! code that is not supported directly by [`FuncCodeReader`]. You can create a
202//! new [`FuncCodeReader`] using a provided function:
203//!```rust
204//! use dsi_bitstream::prelude::*;
205//! use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
206//! use std::fmt::Debug;
207//!
208//! fn read_two_codes_and_sum<
209//! E: Endianness,
210//! R: CodesRead<E> + ?Sized,
211//! SR: StaticCodeRead<E, R>
212//! >(
213//! reader: &mut R,
214//! code: SR,
215//! ) -> Result<u64, R::Error> {
216//! Ok(code.read(reader)? + code.read(reader)?)
217//! }
218//!
219//! fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
220//! reader: &mut R,
221//! ) -> Result<u64, R::Error> {
222//! read_two_codes_and_sum(reader, FuncCodeReader::new_with_func(|r: &mut R| r.read_rice(20)))
223//! }
224//!```
225
226use crate::prelude::Endianness;
227use crate::prelude::{BitRead, BitWrite};
228
229use crate::codes::*;
230use anyhow::Result;
231
232pub mod codes;
233pub use codes::*;
234
235pub mod r#static;
236pub use r#static::{code_consts, ConstCode};
237
238pub mod dynamic;
239pub use dynamic::{FuncCodeLen, FuncCodeReader, FuncCodeWriter};
240
241pub mod factory;
242pub use factory::{CodesReaderFactory, FactoryFuncCodeReader};
243
244/// Convenience extension trait for reading all the codes supported by the
245/// library.
246///
247/// A blanket implementation is provided for all types that implement the
248/// necessary traits.
249///
250/// This trait is mainly useful internally to implement the dispatch
251/// traits [`DynamicCodeRead`], [`StaticCodeRead`], [`DynamicCodeWrite`], and
252/// [`StaticCodeWrite`]. The user might find more useful to define its own
253/// convenience trait that includes only the codes they need.
254pub trait CodesRead<E: Endianness>:
255 BitRead<E>
256 + GammaRead<E>
257 + GammaReadParam<E>
258 + DeltaRead<E>
259 + DeltaReadParam<E>
260 + ZetaRead<E>
261 + ZetaReadParam<E>
262 + OmegaRead<E>
263 + MinimalBinaryRead<E>
264 + PiRead<E>
265 + GolombRead<E>
266 + RiceRead<E>
267 + ExpGolombRead<E>
268 + VByteBeRead<E>
269 + VByteLeRead<E>
270{
271}
272
273impl<E: Endianness, B> CodesRead<E> for B where
274 B: BitRead<E>
275 + GammaRead<E>
276 + GammaReadParam<E>
277 + DeltaRead<E>
278 + DeltaReadParam<E>
279 + ZetaRead<E>
280 + ZetaReadParam<E>
281 + OmegaRead<E>
282 + MinimalBinaryRead<E>
283 + PiRead<E>
284 + GolombRead<E>
285 + RiceRead<E>
286 + ExpGolombRead<E>
287 + VByteBeRead<E>
288 + VByteLeRead<E>
289{
290}
291
292/// Convenience extension trait for writing all the codes supported by the
293/// library.
294///
295/// A blanket implementation is provided for all types that implement the
296/// necessary traits.
297///
298/// This trait is mainly useful internally to implement the dispatch
299/// traits [`DynamicCodeRead`], [`StaticCodeRead`], [`DynamicCodeWrite`], and
300/// [`StaticCodeWrite`]. The user might find more useful to define its own
301/// convenience trait that includes only the codes they need.
302pub trait CodesWrite<E: Endianness>:
303 BitWrite<E>
304 + GammaWrite<E>
305 + DeltaWrite<E>
306 + ZetaWrite<E>
307 + OmegaWrite<E>
308 + MinimalBinaryWrite<E>
309 + PiWrite<E>
310 + GolombWrite<E>
311 + RiceWrite<E>
312 + ExpGolombWrite<E>
313 + VByteBeWrite<E>
314 + VByteLeWrite<E>
315{
316}
317
318impl<E: Endianness, B> CodesWrite<E> for B where
319 B: BitWrite<E>
320 + GammaWrite<E>
321 + DeltaWrite<E>
322 + ZetaWrite<E>
323 + OmegaWrite<E>
324 + MinimalBinaryWrite<E>
325 + PiWrite<E>
326 + GolombWrite<E>
327 + RiceWrite<E>
328 + ExpGolombWrite<E>
329 + VByteBeWrite<E>
330 + VByteLeWrite<E>
331{
332}
333
334/// A trait providing a method to read a code from a generic [`CodesRead`].
335///
336/// The difference with [`StaticCodeRead`] is that this trait is more generic,
337/// as the [`CodesRead`] is a parameter of the method, and not of the trait.
338pub trait DynamicCodeRead {
339 fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
340 &self,
341 reader: &mut CR,
342 ) -> Result<u64, CR::Error>;
343}
344
345/// A trait providing a method to write a code to a generic [`CodesWrite`].
346///
347/// The difference with [`StaticCodeWrite`] is that this trait is more generic,
348/// as the [`CodesWrite`] is a parameter of the method, and not of the trait.
349pub trait DynamicCodeWrite {
350 fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
351 &self,
352 writer: &mut CW,
353 value: u64,
354 ) -> Result<usize, CW::Error>;
355}
356
357/// A trait providing a method to read a code from a [`CodesRead`] specified as
358/// trait type parameter.
359///
360/// The difference with [`DynamicCodeRead`] is that this trait is more specialized,
361/// as the [`CodesRead`] is a parameter of the trait.
362///
363/// For a fixed code this trait may be implemented by storing
364/// a function pointer.
365pub trait StaticCodeRead<E: Endianness, CR: CodesRead<E> + ?Sized> {
366 fn read(&self, reader: &mut CR) -> Result<u64, CR::Error>;
367}
368
369/// A trait providing a method to write a code to a [`CodesWrite`] specified as
370/// a trait type parameter.
371///
372/// The difference with [`DynamicCodeWrite`] is that this trait is more specialized,
373/// as the [`CodesWrite`] is a parameter of the trait.
374///
375/// For a fixed code this trait may be implemented by storing a function
376/// pointer.
377pub trait StaticCodeWrite<E: Endianness, CW: CodesWrite<E> + ?Sized> {
378 fn write(&self, writer: &mut CW, value: u64) -> Result<usize, CW::Error>;
379}
380
381/// A trait providing a generic method to compute the length of a codeword.
382pub trait CodeLen {
383 /// Return the length of the codeword for `value`.
384 fn len(&self, value: u64) -> usize;
385}