Module dispatch

Source
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.
CodesRead
Convenience extension trait for reading all the codes supported by the library.
CodesWrite
Convenience extension trait for writing all the codes supported by the library.
DynamicCodeRead
A trait providing a method to read a code from a generic CodesRead.
DynamicCodeWrite
A trait providing a method to write a code to a generic CodesWrite.
StaticCodeRead
A trait providing a method to read a code from a CodesRead specified as trait type parameter.
StaticCodeWrite
A trait providing a method to write a code to a CodesWrite specified as a trait type parameter.