Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add variable length encoding to array_traits #5

Merged
merged 3 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 92 additions & 26 deletions include/bitstream/traits/array_traits.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#pragma once
#include "../utility/assert.h"
#include "../utility/bits.h"

#include "../stream/serialize_traits.h"
#include "../stream/bit_reader.h"
#include "../stream/bit_writer.h"

#include "../traits/bool_trait.h"
#include "../traits/integral_traits.h"

#include <cstdint>

namespace bitstream
Expand All @@ -16,49 +18,113 @@ namespace bitstream
template<typename T, typename Trait>
struct serialize_traits<array_subset<T, Trait>>
{
private:
template<uint32_t Min, uint32_t Max, typename Stream>
static bool serialize_difference(Stream& stream, int& previous, int& current, uint32_t& difference)
{
bool use_bits;
if (Stream::writing)
use_bits = difference <= Max;
BS_ASSERT(stream.template serialize<bool>(use_bits));
if (use_bits)
{
using bounded_trait = bounded_int<uint32_t, Min, Max>;

BS_ASSERT(stream.template serialize<bounded_trait>(difference));
if (Stream::reading)
current = previous + difference;
previous = current;
return true;
}

return false;
}

template<typename Stream>
static bool serialize_index(Stream& stream, int& previous, int& current, int max_size)
{
uint32_t difference;
if (Stream::writing)
{
BS_ASSERT(previous < current);
difference = current - previous;
BS_ASSERT(difference > 0);
}

// +1 (1 bit)
bool plus_one;
if (Stream::writing)
plus_one = difference == 1;
BS_ASSERT(stream.template serialize<bool>(plus_one));
if (plus_one)
{
if (Stream::reading)
current = previous + 1;
previous = current;
return true;
}

// [+2,5] -> [0,3] (2 bits)
if (serialize_difference<2, 5>(stream, previous, current, difference))
return true;

// [6,13] -> [0,7] (3 bits)
if (serialize_difference<6, 13>(stream, previous, current, difference))
return true;

// [14,29] -> [0,15] (4 bits)
if (serialize_difference<14, 29>(stream, previous, current, difference))
return true;

// [30,61] -> [0,31] (5 bits)
if (serialize_difference<30, 61>(stream, previous, current, difference))
return true;

// [62,125] -> [0,63] (6 bits)
if (serialize_difference<62, 125>(stream, previous, current, difference))
return true;

// [126,MaxObjects+1]
BS_ASSERT(stream.template serialize<uint32_t>(difference, 126, max_size));
if (Stream::reading)
current = previous + difference;
previous = current;
return true;
}

public:
template<typename Compare, typename... Args>
static bool serialize(bit_writer& writer, T* values, uint32_t max_size, Compare compare, Args&&... args) noexcept
static bool serialize(bit_writer& writer, T* values, int max_size, Compare compare, Args&&... args) noexcept
{
uint32_t prev_index = 0;
for (uint32_t i = 0; i < max_size; i++)
int prev_index = -1;
for (int index = 0; index < max_size; index++)
{
if (!compare(values[i]))
if (!compare(values[index]))
continue;

uint32_t num_bits = utility::bits_in_range(prev_index, max_size);

uint32_t index = i - prev_index;
BS_ASSERT(writer.serialize_bits(index, num_bits));

BS_ASSERT(writer.serialize<Trait>(values[i], std::forward<Args>(args)...));
BS_ASSERT(serialize_index(writer, prev_index, index, max_size));

prev_index = i;
BS_ASSERT(writer.serialize<Trait>(values[index], std::forward<Args>(args)...));
}

uint32_t num_bits = utility::bits_in_range(prev_index, max_size);

BS_ASSERT(writer.serialize_bits(max_size - prev_index, num_bits));
BS_ASSERT(serialize_index(writer, prev_index, max_size, max_size));

return true;
}

template<typename Compare, typename... Args>
static bool serialize(bit_reader& reader, T* values, uint32_t max_size, Compare compare, Args&&... args) noexcept
static bool serialize(bit_reader& reader, T* values, int max_size, Compare compare, Args&&... args) noexcept
{
uint32_t prev_index = 0;
for (uint32_t i = 0; i <= max_size; i++)
int prev_index = -1;
int index = 0;
while (true)
{
uint32_t num_bits = utility::bits_in_range(prev_index, max_size);

uint32_t index;
BS_ASSERT(reader.serialize_bits(index, num_bits));
BS_ASSERT(serialize_index(reader, prev_index, index, max_size));

if (index + prev_index == max_size)
if (index == max_size)
break;

BS_ASSERT(reader.serialize<Trait>(values[index + prev_index], std::forward<Args>(args)...));

prev_index += index;
BS_ASSERT(reader.serialize<Trait>(values[index], std::forward<Args>(args)...));
}

return true;
Expand Down
34 changes: 34 additions & 0 deletions include/bitstream/traits/bool_trait.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once
#include "../utility/assert.h"

#include "../stream/serialize_traits.h"
#include "../stream/bit_reader.h"
#include "../stream/bit_writer.h"

namespace bitstream
{
/**
* @brief A trait used to serialize a boolean as a single bit
*/
template<>
struct serialize_traits<bool>
{
static bool serialize(bit_writer& writer, bool value) noexcept
{
uint32_t unsigned_value = value;

return writer.serialize_bits(unsigned_value, 1U);
}

static bool serialize(bit_reader& reader, bool& value) noexcept
{
uint32_t unsigned_value;

BS_ASSERT(reader.serialize_bits(unsigned_value, 1U));

value = unsigned_value;

return true;
}
};
}
80 changes: 44 additions & 36 deletions include/bitstream/traits/integral_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,67 +27,75 @@ namespace bitstream
template<typename T>
struct serialize_traits<T, typename std::enable_if_t<std::is_integral_v<T>>>
{
static bool serialize(bit_writer& writer, const T& value, T min = (std::numeric_limits<T>::min)(), T max = (std::numeric_limits<T>::max)()) noexcept
static bool serialize(bit_writer& writer, T value, T min = (std::numeric_limits<T>::min)(), T max = (std::numeric_limits<T>::max)()) noexcept
{
BS_ASSERT(min < max);

BS_ASSERT(value >= min && value < max);
BS_ASSERT(value >= min && value <= max);

int num_bits = static_cast<int>(utility::bits_in_range(min, max));
uint32_t num_bits = utility::bits_in_range(min, max);

BS_ASSERT(num_bits <= sizeof(T) * 8);

if constexpr (sizeof(T) > 4)
{
// If the given range is bigger than a word (32 bits)
uint32_t unsigned_value = static_cast<uint32_t>(value - min);
BS_ASSERT(writer.serialize_bits(unsigned_value, 32));
if (num_bits > 32)
{
// If the given range is bigger than a word (32 bits)
uint32_t unsigned_value = static_cast<uint32_t>(value - min);
BS_ASSERT(writer.serialize_bits(unsigned_value, 32));

unsigned_value = static_cast<uint32_t>((value - min) >> 32);
BS_ASSERT(writer.serialize_bits(unsigned_value, num_bits - 32));
}
else
{
// If the given range is smaller than or equal to a word (32 bits)
uint32_t unsigned_value = static_cast<uint32_t>(value - min);
BS_ASSERT(writer.serialize_bits(unsigned_value, num_bits));
unsigned_value = static_cast<uint32_t>((value - min) >> 32);
BS_ASSERT(writer.serialize_bits(unsigned_value, num_bits - 32));

return true;
}
}

// If the given range is smaller than or equal to a word (32 bits)
uint32_t unsigned_value = static_cast<uint32_t>(value - min);
BS_ASSERT(writer.serialize_bits(unsigned_value, num_bits));

return true;
}

static bool serialize(bit_reader& reader, T& value, T min = (std::numeric_limits<T>::min)(), T max = (std::numeric_limits<T>::max)()) noexcept
{
BS_ASSERT(min < max);

int num_bits = static_cast<int>(utility::bits_in_range(min, max));
uint32_t num_bits = utility::bits_in_range(min, max);

BS_ASSERT(num_bits <= sizeof(T) * 8);

if constexpr (sizeof(T) > 4)
{
// If the given range is bigger than a word (32 bits)
value = 0;
uint32_t unsigned_value;
if (num_bits > 32)
{
// If the given range is bigger than a word (32 bits)
value = 0;
uint32_t unsigned_value;

BS_ASSERT(reader.serialize_bits(unsigned_value, 32));
value |= static_cast<T>(unsigned_value);
BS_ASSERT(reader.serialize_bits(unsigned_value, 32));
value |= static_cast<T>(unsigned_value);

BS_ASSERT(reader.serialize_bits(unsigned_value, num_bits - 32));
value |= static_cast<T>(unsigned_value) << 32;
BS_ASSERT(reader.serialize_bits(unsigned_value, num_bits - 32));
value |= static_cast<T>(unsigned_value) << 32;

value += min;
}
else
{
// If the given range is smaller than or equal to a word (32 bits)
uint32_t unsigned_value;
BS_ASSERT(reader.serialize_bits(unsigned_value, num_bits));
value += min;

value = static_cast<T>(unsigned_value) + min;
BS_ASSERT(value >= min && value <= max);

return true;
}
}

// If the given range is smaller than or equal to a word (32 bits)
uint32_t unsigned_value;
BS_ASSERT(reader.serialize_bits(unsigned_value, num_bits));

value = static_cast<T>(unsigned_value) + min;

BS_ASSERT(value >= min && value < max);
BS_ASSERT(value >= min && value <= max);

return true;
}
Expand All @@ -104,13 +112,13 @@ namespace bitstream
template<typename T, T Min, T Max>
struct serialize_traits<bounded_int<T, Min, Max>, typename std::enable_if_t<std::is_integral_v<T>>>
{
static bool serialize(bit_writer& writer, const T& value) noexcept
static bool serialize(bit_writer& writer, T value) noexcept
{
static_assert(Min < Max);

BS_ASSERT(value >= Min && value < Max);
BS_ASSERT(value >= Min && value <= Max);

constexpr int num_bits = static_cast<int>(utility::bits_in_range(Min, Max));
constexpr uint32_t num_bits = utility::bits_in_range(Min, Max);

static_assert(num_bits <= sizeof(T) * 8);

Expand All @@ -137,7 +145,7 @@ namespace bitstream
{
static_assert(Min < Max);

constexpr int num_bits = static_cast<int>(utility::bits_in_range(Min, Max));
constexpr uint32_t num_bits = utility::bits_in_range(Min, Max);

static_assert(num_bits <= sizeof(T) * 8);

Expand All @@ -164,7 +172,7 @@ namespace bitstream
value = static_cast<T>(unsigned_value) + Min;
}

BS_ASSERT(value >= Min && value < Max);
BS_ASSERT(value >= Min && value <= Max);

return true;
}
Expand Down
18 changes: 10 additions & 8 deletions test/src/test/serialize_array_traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <bitstream/stream/bit_writer.h>

#include <bitstream/traits/array_traits.h>
#include <bitstream/traits/bool_trait.h>
#include <bitstream/traits/integral_traits.h>

namespace bitstream::test::traits
Expand All @@ -13,33 +14,34 @@ namespace bitstream::test::traits
{
using trait = array_subset<uint32_t, bounded_int<uint32_t, 0U, 2048U>>;

// Test half precision float
uint32_t values_in[5]
// Test array subset
uint32_t values_in[6]
{
10,
21,
42,
99,
420,
1337
};

auto compare = [](uint32_t value) { return value != 21 && value != 42 && value != 99; };

uint8_t buffer[16]{ 0 };
bit_writer writer(buffer, 16);

auto compare = [](uint32_t value) { return value != 21 && value != 99; };

BS_TEST_ASSERT(writer.serialize<trait>(values_in, 5U, compare)); // Use bounded_int for writing
BS_TEST_ASSERT(writer.serialize<trait>(values_in, 6, compare)); // Use bounded_int for writing
uint32_t num_bytes = writer.flush();

BS_TEST_ASSERT_OPERATION(num_bytes, == , 6);


uint32_t values_out[5];
uint32_t values_out[6];
bit_reader reader(std::move(writer));

BS_TEST_ASSERT(reader.serialize<array_subset<uint32_t>>(values_out, 5U, compare, 0U, 2048U)); // Use min, max arguments for reading
BS_TEST_ASSERT(reader.serialize<array_subset<uint32_t>>(values_out, 6, compare, 0U, 2048U)); // Use min, max arguments for reading

for (int i = 0; i < 5; i++)
for (int i = 0; i < 6; i++)
{
if (!compare(values_in[i]))
continue;
Expand Down
Loading