Expand description
Programmable static and dynamic dispatch for codes.
The code traits in codes, such as
Omega
, extend BitRead
and BitWrite
to
provide a way to read and write codes from a bitstream. The user can thus
select at compile time the desired trait and use the associated codes.
In many contexts, however, one does not want to commit to a specific set of codes, but rather would like to write generic methods that accept some code as an input and then use it to read or write values. For example, a stream encoder might let the user choose between different codes, depending on the user’s knowledge of the distribution of the values to be encoded.
Having dynamic selection of a code, however, entails a performance cost, as, for example, a match statement must be used to select the correct code. To mitigate this cost, we provide two types of dispatch traits and three types of implementations based on them.
§Dispatch Traits
The traits DynamicCodeRead
and DynamicCodeWrite
are the most generic
ones, and provide a method to read and write a code from a bitstream. By
implementing them, you can write a method accepting one or more unspecified
codes, and operate with them. For example, in this function we read twice a
code and return the sum of the two values, but make no commitment on which
code we will be using:
use dsi_bitstream::prelude::*;
use dsi_bitstream::dispatch::{CodesRead, DynamicCodeRead};
use std::fmt::Debug;
fn read_two_codes_and_sum<
E: Endianness,
R: CodesRead<E> + ?Sized,
GR: DynamicCodeRead
>(
reader: &mut R,
code: GR,
) -> Result<u64, R::Error> {
Ok(code.read(reader)? + code.read(reader)?)
}
On the other hand, the traits StaticCodeRead
and StaticCodeWrite
are
specialized for a reader or writer of given endianness. This means that they
can in principle be implemented for a specific code by storing a function
pointer, with much less runtime overhead.
use dsi_bitstream::prelude::*;
use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead};
use std::fmt::Debug;
fn read_two_codes_and_sum<
E: Endianness,
R: CodesRead<E> + ?Sized,
SR: StaticCodeRead<E, R>
>(
reader: &mut R,
code: SR,
) -> Result<u64, R::Error> {
Ok(code.read(reader)? + code.read(reader)?)
}
Note that the syntax for invoking the methods in the two groups of traits is identical, but the type variables are on the method in the first case, and on the trait in the second case.
§Implementations
The Codes
enum is an enum whose variants represent all the available
codes. It implements all the dispatch traits, so it can be used to read or
write any code both in a generic and in a specific way. It also implements
the CodeLen
trait, which provides a method to compute the length of a
codeword.
If Rust would support const enums in traits, one could create structures
with const enum type parameters of type Codes
, and then the compiler
would be able to optimize away the code selection at compile time. However,
this is not currently possible, so we provide a workaround using a
zero-sized struct with a const usize
parameter, ConstCode
, that
implements all the dispatch traits and CodeLen
, and can be used to
select the code at compile time. The parameter must be taken from the
code_consts
module, which contains constants for all parameterless
codes, and for the codes with parameters up to 10. For example, here at
execution time there will be no test to select a code, even if
read_two_codes_and_sum
is generic:
use dsi_bitstream::prelude::*;
use dsi_bitstream::dispatch::{code_consts, CodesRead, DynamicCodeRead};
use std::fmt::Debug;
fn read_two_codes_and_sum<
E: Endianness,
R: CodesRead<E> + ?Sized,
GR: DynamicCodeRead
>(
reader: &mut R,
code: GR,
) -> Result<u64, R::Error> {
Ok(code.read(reader)? + code.read(reader)?)
}
fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
reader: &mut R,
) -> Result<u64, R::Error> {
read_two_codes_and_sum(reader, ConstCode::<{code_consts::GAMMA}>)
}
Working with ConstCode
is very efficient, but it forces the choice of a
code at compile time. If you need to read or write a code multiple times on
the same type of bitstream, you can use the structs FuncCodeReader
and
FuncCodeWriter
, which implement StaticCodeRead
and
StaticCodeWrite
by storing a function pointer.
A value of type FuncCodeReader
or FuncCodeWriter
can be created by
calling their new
method with a variant of the Codes
enum. As in the
case of ConstCode
, there are pointers for all parameterless codes, and
for the codes with parameters up to 10, and the method will return an error
if the code is not supported.
For example:
use dsi_bitstream::prelude::*;
use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
use std::fmt::Debug;
fn read_two_codes_and_sum<
E: Endianness,
R: CodesRead<E> + ?Sized,
SR: StaticCodeRead<E, R>
>(
reader: &mut R,
code: SR,
) -> Result<u64, R::Error> {
Ok(code.read(reader)? + code.read(reader)?)
}
fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
reader: &mut R,
) -> Result<u64, R::Error> {
read_two_codes_and_sum(reader, FuncCodeReader::new(Codes::Gamma).unwrap())
}
Note that we unwrap
the result of the
new
method, as we know that a function pointer
exists for the γ code.
§Workaround to Limitations
Both ConstCode
and FuncCodeReader
/ FuncCodeWriter
are limited
to a fixed set of codes. If you need to work with a code that is not
supported by them, you can implement your own version. For example, here we
define a zero-sized struct that represent a Rice code with a fixed parameter
LOG2_B
:
use dsi_bitstream::prelude::*;
use dsi_bitstream::dispatch::{CodesRead, CodesWrite};
use dsi_bitstream::dispatch::{DynamicCodeRead, DynamicCodeWrite};
use std::fmt::Debug;
#[derive(Clone, Copy, Debug, Default)]
pub struct Rice<const LOG2_B: usize>;
impl<const LOG2_B: usize> DynamicCodeRead for Rice<LOG2_B> {
fn read<E: Endianness, CR: CodesRead<E> + ?Sized>(
&self,
reader: &mut CR,
) -> Result<u64, CR::Error> {
reader.read_rice(LOG2_B)
}
}
impl<const LOG2_B: usize> DynamicCodeWrite for Rice<LOG2_B> {
fn write<E: Endianness, CW: CodesWrite<E> + ?Sized>(
&self,
writer: &mut CW,
value: u64,
) -> Result<usize, CW::Error> {
writer.write_rice(value, LOG2_B)
}
}
impl<const LOG2_B: usize> CodeLen for Rice<LOG2_B> {
#[inline]
fn len(&self, value: u64) -> usize {
len_rice(value, LOG2_B)
}
}
Suppose instead you need to pass a StaticCodeRead
to a method using a
code that is not supported directly by FuncCodeReader
. You can create a
new FuncCodeReader
using a provided function:
use dsi_bitstream::prelude::*;
use dsi_bitstream::dispatch::{CodesRead, StaticCodeRead, FuncCodeReader};
use std::fmt::Debug;
fn read_two_codes_and_sum<
E: Endianness,
R: CodesRead<E> + ?Sized,
SR: StaticCodeRead<E, R>
>(
reader: &mut R,
code: SR,
) -> Result<u64, R::Error> {
Ok(code.read(reader)? + code.read(reader)?)
}
fn call_read_two_codes_and_sum<E: Endianness, R: CodesRead<E> + ?Sized>(
reader: &mut R,
) -> Result<u64, R::Error> {
read_two_codes_and_sum(reader, FuncCodeReader::new_with_func(|r: &mut R| r.read_rice(20)))
}
Re-exports§
pub use static::code_consts;
pub use static::ConstCode;
pub use dynamic::FuncCodeLen;
pub use dynamic::FuncCodeReader;
pub use dynamic::FuncCodeWriter;
pub use factory::CodesReaderFactory;
pub use factory::FactoryFuncCodeReader;
pub use codes::*;
Modules§
- codes
- Enumeration of all available codes, with associated read and write methods.
- dynamic
- Dynamic dispatching for codes based on function pointers.
- factory
- Dynamic-dispatching factories for readers with a lifetime.
- static
- Static dispatch for codes.
Traits§
- CodeLen
- A trait providing a generic method to compute the length of a codeword.
- Codes
Read - Convenience extension trait for reading all the codes supported by the library.
- Codes
Write - Convenience extension trait for writing all the codes supported by the library.
- Dynamic
Code Read - A trait providing a method to read a code from a generic
CodesRead
. - Dynamic
Code Write - A trait providing a method to write a code to a generic
CodesWrite
. - Static
Code Read - A trait providing a method to read a code from a
CodesRead
specified as trait type parameter. - Static
Code Write - A trait providing a method to write a code to a
CodesWrite
specified as a trait type parameter.