Skip to content

Creating Custom Codecs

Learn how to extend PFC with your own codec implementations.

Codec Requirements

A codec must implement:

struct MyCodec {
    // Required: Is size fixed?
    static constexpr bool is_fixed_size();

    // Required: Maximum encoded bits
    static constexpr size_t max_encoded_bits();

    // Required: Encode value
    template<BitSink S>
    static void encode(ValueType value, S& sink);

    // Required: Decode value
    template<BitSource S>
    static ValueType decode(S& source);
};

Example: Run-Length Encoding

struct RunLengthCodec {
    static constexpr bool is_fixed_size() { return false; }
    static constexpr size_t max_encoded_bits() { return 128; }

    template<BitSink S>
    static void encode(uint32_t value, S& sink) {
        // Encode run length and value
        EliasGamma::encode(value & 0xFF, sink);  // Value
        EliasGamma::encode(value >> 8, sink);     // Count
    }

    template<BitSource S>
    static uint32_t decode(S& source) {
        uint32_t value = EliasGamma::decode(source);
        uint32_t count = EliasGamma::decode(source);
        return value | (count << 8);
    }
};

Using Your Codec

Once defined, your codec works with all PFC infrastructure:

// With packed values
using Packed RLE = Packed<uint32_t, RunLengthCodec>;

// With containers
PackedVector<PackedRLE> vec;

// With compression API
auto compressed = compress<RunLengthCodec>(data);

Testing Your Codec

Always test round-trip correctness:

TEST_CASE("RunLengthCodec round-trip") {
    uint8_t buffer[64];
    BitWriter writer(buffer);

    uint32_t original = 0x0102;
    RunLengthCodec::encode(original, writer);
    writer.align();

    BitReader reader(buffer);
    uint32_t decoded = RunLengthCodec::decode(reader);

    REQUIRE(decoded == original);
}

See Design Principles for codec design philosophy.