Additional helpers methods for BlobReader/BlobWriter and related infrastructure fixes (for the upcoming code that will be using them). (#137)
This commit is contained in:
Родитель
78e67e2ab3
Коммит
6bcbdaede3
|
@ -82,7 +82,7 @@ class RefPtr {
|
|||
|
||||
// Returns a pointer to the managed object and releases the ownership
|
||||
// (ref count won't decrease after this operation).
|
||||
constexpr T* release() {
|
||||
[[nodiscard]] T* release() noexcept {
|
||||
T* result = ptr_;
|
||||
ptr_ = nullptr;
|
||||
return result;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
#include <Microsoft/MixedReality/Sharing/Common/Serialization/Serialization.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace Microsoft::MixedReality::Sharing::Serialization {
|
||||
|
@ -15,11 +16,20 @@ class BlobReader {
|
|||
explicit BlobReader(std::string_view input) noexcept;
|
||||
|
||||
// Reads next bytes_count bytes (as encoded by BlobWriter).
|
||||
// The returned string_view references the bytes of the input.
|
||||
// Throws std::out_of_range if there is not enough input left.
|
||||
// The behavior is undefined if the reader is reused after it
|
||||
// had thrown an exception.
|
||||
std::string_view ReadBytes(size_t bytes_count);
|
||||
|
||||
// Reads a blob of bytes with the number of bytes encoded
|
||||
// as exponential-Golomb code; see BlobWriter::WriteBytesWithSize().
|
||||
// The returned string_view references the bytes of the input.
|
||||
// Throws std::out_of_range if there is not enough input left.
|
||||
// The behavior is undefined if the reader is reused after it
|
||||
// had thrown an exception.
|
||||
std::string_view ReadBytesWithSize();
|
||||
|
||||
// Reads up to 32 bits from the bit stream.
|
||||
// Throws std::out_of_range if there is not enough input left.
|
||||
// The behavior is undefined if the reader is reused after it
|
||||
|
@ -32,11 +42,20 @@ class BlobReader {
|
|||
// had thrown an exception, or if bits_count is not in [1, 64].
|
||||
uint64_t ReadBits64(bit_shift_t bits_count);
|
||||
|
||||
// Reads a single bit and returns it as a bool.
|
||||
bool ReadBool();
|
||||
|
||||
// Reads an exponential-Golomb code (as encoded by BlobWriter).
|
||||
// Throws std::out_of_range if there is not enough input left.
|
||||
// The behavior is undefined if the reader is reused after it
|
||||
// had thrown an exception.
|
||||
uint64_t ReadExponentialGolombCode();
|
||||
uint64_t ReadGolomb();
|
||||
|
||||
// Reads an optional exponential-Golomb code (as encoded by BlobWriter).
|
||||
// Throws std::out_of_range if there is not enough input left.
|
||||
// The behavior is undefined if the reader is reused after it
|
||||
// had thrown an exception.
|
||||
std::optional<uint64_t> ReadOptionalGolomb();
|
||||
|
||||
// Returns true if there are no more than 7 unread bits,
|
||||
// and all of them are 0.
|
||||
|
@ -74,4 +93,9 @@ bool BlobReader::ProbablyNoMoreData() const noexcept {
|
|||
return unread_bytes_count_ == 0 && bit_buf_bits_count_ < 8 && bit_buf_ == 0;
|
||||
}
|
||||
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
bool BlobReader::ReadBool() {
|
||||
return ReadBits32(1) == 1;
|
||||
}
|
||||
|
||||
} // namespace Microsoft::MixedReality::Sharing::Serialization
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <Microsoft/MixedReality/Sharing/Common/Serialization/Serialization.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
namespace Microsoft::MixedReality::Sharing::Serialization {
|
||||
|
||||
|
@ -38,15 +39,43 @@ class BlobWriter {
|
|||
void WriteBytes(const char* data, size_t size) noexcept;
|
||||
void WriteBytes(std::string_view sv) noexcept;
|
||||
|
||||
// Writes the provided bytes to the byte stream and their count
|
||||
// as exponential-Golomb code.
|
||||
// The reader will be able to read this string without knowing
|
||||
// its size in advance, see BlobReader::ReadBytesWithSize().
|
||||
void WriteBytesWithSize(const std::byte* data, size_t size) noexcept;
|
||||
void WriteBytesWithSize(const char* data, size_t size) noexcept;
|
||||
void WriteBytesWithSize(std::string_view sv) noexcept;
|
||||
|
||||
// Appends the provided bits to the bit stream.
|
||||
// Expects that the provided value fits into bits_count bits, otherwise the
|
||||
// behavior is undefined.
|
||||
void WriteBits(uint64_t value, bit_shift_t bits_count) noexcept;
|
||||
|
||||
// Writes a bool as a single bit.
|
||||
void WriteBool(bool value) noexcept;
|
||||
|
||||
// Encodes the provided value as an order-0 exponential-Golomb code.
|
||||
// Has a special shortened encoding for ~0ull since we are not interested
|
||||
// in arbitrarily large codes.
|
||||
void WriteExponentialGolombCode(uint64_t value) noexcept;
|
||||
void WriteGolomb(uint64_t value) noexcept;
|
||||
|
||||
// Encodes the provided optional value as an order-0 exponential-Golomb code,
|
||||
// offsetted so that the empty optional has the shortest possible encoding
|
||||
// (1 bit) and all other values are encoded as a value that is 1 greater
|
||||
// (so, for example, 5 will be encoded as 6).
|
||||
// Has a special shortened encoding for ~0ull and ~1ull, so the offsetting
|
||||
// procedure above doesn't overflow.
|
||||
void WriteOptionalGolomb(
|
||||
const std::optional<uint64_t> optional_value) noexcept;
|
||||
|
||||
// Equivalent to WriteOptionalGolomb with optional_value
|
||||
// argument equal to present_value.
|
||||
void WritePresentOptionalGolomb(uint64_t present_value) noexcept;
|
||||
|
||||
// Equivalent to WriteOptionalGolomb with optional_value
|
||||
// argument being empty.
|
||||
void WriteMissingOptionalGolomb() noexcept;
|
||||
|
||||
// The number of bytes the blob will occupy if the writer would be finalized
|
||||
// with the current state.
|
||||
|
@ -140,4 +169,34 @@ void BlobWriter::WriteBits(uint64_t value, bit_shift_t bits_count) noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
void BlobWriter::WriteBytesWithSize(const char* data, size_t size) noexcept {
|
||||
WriteBytesWithSize(reinterpret_cast<const std::byte*>(data), size);
|
||||
}
|
||||
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
void BlobWriter::WriteBytesWithSize(std::string_view sv) noexcept {
|
||||
WriteBytesWithSize(reinterpret_cast<const std::byte*>(sv.data()), sv.size());
|
||||
}
|
||||
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
void BlobWriter::WriteMissingOptionalGolomb() noexcept {
|
||||
WriteBits(1, 1);
|
||||
}
|
||||
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
void BlobWriter::WriteOptionalGolomb(
|
||||
const std::optional<uint64_t> optional_value) noexcept {
|
||||
if (optional_value) {
|
||||
WritePresentOptionalGolomb(*optional_value);
|
||||
} else {
|
||||
WriteMissingOptionalGolomb();
|
||||
}
|
||||
}
|
||||
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
void BlobWriter::WriteBool(bool value) noexcept {
|
||||
WriteBits(value, 1);
|
||||
}
|
||||
|
||||
} // namespace Microsoft::MixedReality::Sharing::Serialization
|
||||
|
|
|
@ -28,7 +28,7 @@ MS_MR_SHARING_FORCEINLINE
|
|||
// obtained by calling pointer_to_enum64().
|
||||
template <class To, class From>
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
typename std::enable_if_t<(sizeof(From) == 64) &&
|
||||
typename std::enable_if_t<(sizeof(From) == 8) &&
|
||||
(sizeof(To) <= sizeof(From)) &&
|
||||
std::is_enum_v<From> && std::is_pointer_v<To>,
|
||||
To>
|
||||
|
@ -43,7 +43,7 @@ MS_MR_SHARING_FORCEINLINE
|
|||
// is smaller. Stored pointers can be retrieved with enum64_to_pointer().
|
||||
template <class To, class From>
|
||||
MS_MR_SHARING_FORCEINLINE
|
||||
typename std::enable_if_t<(sizeof(To) == 64) &&
|
||||
typename std::enable_if_t<(sizeof(To) == 8) &&
|
||||
(sizeof(To) >= sizeof(From)) &&
|
||||
std::is_enum_v<To> && std::is_pointer_v<From>,
|
||||
To>
|
||||
|
|
|
@ -184,7 +184,7 @@ uint64_t BlobReader::ReadBits64(bit_shift_t bits_count) {
|
|||
return ReadBits<uint64_t>(bits_count);
|
||||
}
|
||||
|
||||
uint64_t BlobReader::ReadExponentialGolombCode() {
|
||||
uint64_t BlobReader::ReadGolomb() {
|
||||
// Counting the number of zero bits to determine the length of the code.
|
||||
// 64 zeros is a special case for ~0ull (see BlobWriter for details).
|
||||
bit_shift_t zeroes_count = 0;
|
||||
|
@ -222,4 +222,23 @@ uint64_t BlobReader::ReadExponentialGolombCode() {
|
|||
return ReadBits<uint64_t>(zeroes_count + 1) - 1;
|
||||
}
|
||||
|
||||
std::optional<uint64_t> BlobReader::ReadOptionalGolomb() {
|
||||
uint64_t code = ReadGolomb();
|
||||
if (code == 0)
|
||||
return {};
|
||||
if (code == ~0ull)
|
||||
return ~ReadBits64(1); // Special encoding for ~0ull and ~1ull.
|
||||
return code - 1;
|
||||
}
|
||||
|
||||
std::string_view BlobReader::ReadBytesWithSize() {
|
||||
uint64_t size = ReadGolomb();
|
||||
if constexpr (sizeof(size_t) < sizeof(uint64_t)) {
|
||||
if (size > std::numeric_limits<size_t>::max()) {
|
||||
throw std::out_of_range("Not enough bytes in the blob");
|
||||
}
|
||||
}
|
||||
return ReadBytes(static_cast<size_t>(size));
|
||||
}
|
||||
|
||||
} // namespace Microsoft::MixedReality::Sharing::Serialization
|
||||
|
|
|
@ -51,7 +51,7 @@ void BlobWriter::Grow(size_t min_free_bytes_after_grow) noexcept {
|
|||
buffer_end_ = buffer_ + new_elements_count;
|
||||
}
|
||||
|
||||
void BlobWriter::WriteExponentialGolombCode(uint64_t value) noexcept {
|
||||
void BlobWriter::WriteGolomb(uint64_t value) noexcept {
|
||||
// The encoding increments the value by 1 and counts the number of bits in the
|
||||
// result. Then it writes the number of zeros equal to the number of bits
|
||||
// minus one, and then all the significant bits of the incremented number.
|
||||
|
@ -92,6 +92,30 @@ void BlobWriter::WriteExponentialGolombCode(uint64_t value) noexcept {
|
|||
WriteBits(value, index + 1);
|
||||
}
|
||||
|
||||
void BlobWriter::WriteBytesWithSize(const std::byte* data,
|
||||
size_t size) noexcept {
|
||||
// Not reusing WriteBytes() to avoid the possibility of double reallocation
|
||||
// (growing with 16 extra bytes ensures that we'll always be able to write
|
||||
// the size as exponential-Golomb code).
|
||||
if (free_bytes_count_ < size)
|
||||
Grow(size + 16);
|
||||
memcpy(bytes_scection_end_, data, size);
|
||||
bytes_scection_end_ += size;
|
||||
free_bytes_count_ -= size;
|
||||
WriteGolomb(size);
|
||||
}
|
||||
|
||||
void BlobWriter::WritePresentOptionalGolomb(uint64_t present_value) noexcept {
|
||||
if (present_value >= ~1ull) {
|
||||
// Special-casing these two values, so that they are always encoded with
|
||||
// 65 bits.
|
||||
WriteBits(0, 64);
|
||||
WriteBits(~present_value, 1);
|
||||
} else {
|
||||
WriteGolomb(present_value + 1);
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view BlobWriter::Finalize() noexcept {
|
||||
const size_t bits_size = bits_section_size();
|
||||
const size_t bytes_size = bytes_section_size();
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
|
|
@ -120,12 +120,12 @@ TEST(Serialization, blob_write_read_golomb_codes_short_sequences) {
|
|||
BlobWriter writer;
|
||||
writer.WriteBits(0, offset);
|
||||
for (const auto& x : values) {
|
||||
writer.WriteExponentialGolombCode(x.value);
|
||||
writer.WriteGolomb(x.value);
|
||||
}
|
||||
BlobReader reader{writer.Finalize()};
|
||||
ASSERT_EQ(reader.ReadBits64(offset), 0);
|
||||
for (const auto& expected : values) {
|
||||
uint64_t read_value = reader.ReadExponentialGolombCode();
|
||||
uint64_t read_value = reader.ReadGolomb();
|
||||
ASSERT_EQ(expected.value, read_value);
|
||||
}
|
||||
ASSERT_TRUE(reader.ProbablyNoMoreData());
|
||||
|
@ -139,11 +139,11 @@ TEST(Serialization, blob_write_read_golomb_codes_long_sequence) {
|
|||
auto values = GenerateRandomValues(20000);
|
||||
BlobWriter writer;
|
||||
for (const auto& x : values) {
|
||||
writer.WriteExponentialGolombCode(x.value);
|
||||
writer.WriteGolomb(x.value);
|
||||
}
|
||||
BlobReader reader{writer.Finalize()};
|
||||
for (const auto& expected : values) {
|
||||
uint64_t read_value = reader.ReadExponentialGolombCode();
|
||||
uint64_t read_value = reader.ReadGolomb();
|
||||
ASSERT_EQ(expected.value, read_value);
|
||||
}
|
||||
ASSERT_TRUE(reader.ProbablyNoMoreData());
|
||||
|
@ -161,7 +161,7 @@ TEST(Serialization, blob_read_from_empty) {
|
|||
}
|
||||
|
||||
BlobReader reader{{}};
|
||||
ASSERT_THROW(reader.ReadExponentialGolombCode(), std::out_of_range);
|
||||
ASSERT_THROW(reader.ReadGolomb(), std::out_of_range);
|
||||
}
|
||||
|
||||
TEST(Serialization, blob_read_out_of_range) {
|
||||
|
@ -179,8 +179,8 @@ TEST(Serialization, blob_read_out_of_range) {
|
|||
|
||||
TEST(Serialization, blob_read_golomb_out_of_range) {
|
||||
BlobWriter writer;
|
||||
writer.WriteExponentialGolombCode(6);
|
||||
writer.WriteExponentialGolombCode(2);
|
||||
writer.WriteGolomb(6);
|
||||
writer.WriteGolomb(2);
|
||||
auto sv = writer.Finalize();
|
||||
|
||||
// The order is: low bits => high bits.
|
||||
|
@ -193,11 +193,11 @@ TEST(Serialization, blob_read_golomb_out_of_range) {
|
|||
|
||||
ASSERT_EQ(sv, "\x3B"sv);
|
||||
BlobReader reader{sv};
|
||||
ASSERT_EQ(reader.ReadExponentialGolombCode(), 6);
|
||||
ASSERT_EQ(reader.ReadExponentialGolombCode(), 2);
|
||||
ASSERT_EQ(reader.ReadGolomb(), 6);
|
||||
ASSERT_EQ(reader.ReadGolomb(), 2);
|
||||
|
||||
// Can't read anything else.
|
||||
ASSERT_THROW(reader.ReadExponentialGolombCode(), std::out_of_range);
|
||||
ASSERT_THROW(reader.ReadGolomb(), std::out_of_range);
|
||||
}
|
||||
|
||||
} // namespace Microsoft::MixedReality::Sharing::Serialization
|
||||
|
|
Загрузка…
Ссылка в новой задаче