Attempting to use an external library to handle bitstreams. Isn't going the grreatest.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2023, Krede
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Stanislav Denisov (nxrighthere@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1 @@
|
||||
0.1.4
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
// Quantization
|
||||
#include "quantization/bounded_range.h"
|
||||
#include "quantization/half_precision.h"
|
||||
#include "quantization/smallest_three.h"
|
||||
|
||||
// Stream
|
||||
#include "stream/bit_measure.h"
|
||||
#include "stream/bit_reader.h"
|
||||
#include "stream/bit_writer.h"
|
||||
#include "stream/byte_buffer.h"
|
||||
#include "stream/serialize_traits.h"
|
||||
|
||||
// Traits
|
||||
#include "traits/array_traits.h"
|
||||
#include "traits/bool_trait.h"
|
||||
#include "traits/enum_trait.h"
|
||||
#include "traits/float_trait.h"
|
||||
#include "traits/integral_traits.h"
|
||||
#include "traits/quantization_traits.h"
|
||||
#include "traits/string_traits.h"
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Stanislav Denisov
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief Class for quantizing single-precision floats into a range and precision
|
||||
*/
|
||||
class bounded_range
|
||||
{
|
||||
public:
|
||||
constexpr bounded_range() noexcept :
|
||||
m_Min(0),
|
||||
m_Max(0),
|
||||
m_Precision(0),
|
||||
m_BitsRequired(0),
|
||||
m_Mask(0) {}
|
||||
|
||||
constexpr bounded_range(float min, float max, float precision) noexcept :
|
||||
m_Min(min),
|
||||
m_Max(max),
|
||||
m_Precision(precision),
|
||||
m_BitsRequired(log2(static_cast<uint32_t>((m_Max - m_Min) * (1.0f / precision) + 0.5f)) + 1),
|
||||
m_Mask((1U << m_BitsRequired) - 1U) {}
|
||||
|
||||
constexpr inline float get_min() const noexcept { return m_Min; }
|
||||
constexpr inline float get_max() const noexcept { return m_Max; }
|
||||
constexpr inline float get_precision() const noexcept { return m_Precision; }
|
||||
constexpr inline uint32_t get_bits_required() const noexcept { return m_BitsRequired; }
|
||||
|
||||
constexpr inline uint32_t quantize(float value) const noexcept
|
||||
{
|
||||
if (value < m_Min)
|
||||
value = m_Min;
|
||||
else if (value > m_Max)
|
||||
value = m_Max;
|
||||
|
||||
return static_cast<uint32_t>(static_cast<float>((value - m_Min) * (1.0f / m_Precision)) + 0.5f) & m_Mask;
|
||||
}
|
||||
|
||||
constexpr inline float dequantize(uint32_t data) const noexcept
|
||||
{
|
||||
float adjusted = (static_cast<float>(data) * m_Precision) + m_Min;
|
||||
|
||||
if (adjusted < m_Min)
|
||||
adjusted = m_Min;
|
||||
else if (adjusted > m_Max)
|
||||
adjusted = m_Max;
|
||||
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
private:
|
||||
constexpr inline static uint32_t log2(uint32_t value) noexcept
|
||||
{
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
|
||||
return DE_BRUIJN[(value * 0x07C4ACDDU) >> 27];
|
||||
}
|
||||
|
||||
private:
|
||||
float m_Min;
|
||||
float m_Max;
|
||||
float m_Precision;
|
||||
|
||||
uint32_t m_BitsRequired;
|
||||
uint32_t m_Mask;
|
||||
|
||||
constexpr inline static uint32_t DE_BRUIJN[32]
|
||||
{
|
||||
0, 9, 1, 10, 13, 21, 2, 29,
|
||||
11, 14, 16, 18, 22, 25, 3, 30,
|
||||
8, 12, 20, 28, 15, 17, 24, 7,
|
||||
19, 27, 23, 6, 26, 5, 4, 31
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018 Stanislav Denisov
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief Class for quantizing single-precision floats into half-precision
|
||||
*/
|
||||
class half_precision
|
||||
{
|
||||
public:
|
||||
inline static uint16_t quantize(float value) noexcept
|
||||
{
|
||||
int32_t tmp;
|
||||
std::memcpy(&tmp, &value, sizeof(float));
|
||||
|
||||
int32_t s = (tmp >> 16) & 0x00008000;
|
||||
int32_t e = ((tmp >> 23) & 0X000000FF) - (127 - 15);
|
||||
int32_t m = tmp & 0X007FFFFF;
|
||||
|
||||
if (e <= 0) {
|
||||
if (e < -10)
|
||||
return static_cast<uint16_t>(s);
|
||||
|
||||
m |= 0x00800000;
|
||||
|
||||
int32_t t = 14 - e;
|
||||
int32_t a = (1 << (t - 1)) - 1;
|
||||
int32_t b = (m >> t) & 1;
|
||||
|
||||
m = (m + a + b) >> t;
|
||||
|
||||
return static_cast<uint16_t>(s | m);
|
||||
}
|
||||
|
||||
if (e == 0XFF - (127 - 15)) {
|
||||
if (m == 0)
|
||||
return static_cast<uint16_t>(s | 0X7C00);
|
||||
|
||||
m >>= 13;
|
||||
|
||||
return static_cast<uint16_t>(s | 0X7C00 | m | ((m == 0) ? 1 : 0));
|
||||
}
|
||||
|
||||
m = m + 0X00000FFF + ((m >> 13) & 1);
|
||||
|
||||
if ((m & 0x00800000) != 0) {
|
||||
m = 0;
|
||||
e++;
|
||||
}
|
||||
|
||||
if (e > 30)
|
||||
return static_cast<uint16_t>(s | 0X7C00);
|
||||
|
||||
return static_cast<uint16_t>(s | (e << 10) | (m >> 13));
|
||||
}
|
||||
|
||||
inline static float dequantize(uint16_t value) noexcept
|
||||
{
|
||||
uint32_t tmp;
|
||||
uint32_t mantissa = static_cast<uint32_t>(value & 1023);
|
||||
uint32_t exponent = 0XFFFFFFF2;
|
||||
|
||||
if ((value & -33792) == 0) {
|
||||
if (mantissa != 0) {
|
||||
while ((mantissa & 1024) == 0) {
|
||||
exponent--;
|
||||
mantissa <<= 1;
|
||||
}
|
||||
|
||||
mantissa &= 0XFFFFFBFF;
|
||||
tmp = ((static_cast<uint32_t>(value) & 0x8000) << 16) | ((exponent + 127) << 23) | (mantissa << 13);
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = static_cast<uint32_t>((value & 0x8000) << 16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = ((static_cast<uint32_t>(value) & 0x8000) << 16) | (((((static_cast<uint32_t>(value) >> 10) & 0X1F) - 15) + 127) << 23) | (mantissa << 13);
|
||||
}
|
||||
|
||||
float result;
|
||||
std::memcpy(&result, &tmp, sizeof(float));
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright (c) 2020 Stanislav Denisov, Maxim Munning, Davin Carten
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A quantized representation of a quaternion
|
||||
*/
|
||||
struct quantized_quaternion
|
||||
{
|
||||
uint32_t m;
|
||||
uint32_t a;
|
||||
uint32_t b;
|
||||
uint32_t c;
|
||||
|
||||
constexpr quantized_quaternion() noexcept :
|
||||
m(0),
|
||||
a(0),
|
||||
b(0),
|
||||
c(0) {}
|
||||
|
||||
constexpr quantized_quaternion(uint32_t w, uint32_t x, uint32_t y, uint32_t z) noexcept :
|
||||
m(w), a(x), b(y), c(z) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class for quantizing a user-specified quaternion into fewer bits using the smallest-three algorithm
|
||||
* @tparam T The quaternion-type to quantize
|
||||
*/
|
||||
template<typename T, size_t BitsPerElement = 12>
|
||||
class smallest_three
|
||||
{
|
||||
private:
|
||||
static constexpr float SMALLEST_THREE_UNPACK = 0.70710678118654752440084436210485f + 0.0000001f;
|
||||
static constexpr float SMALLEST_THREE_PACK = 1.0f / SMALLEST_THREE_UNPACK;
|
||||
|
||||
public:
|
||||
inline static quantized_quaternion quantize(const T& quaternion) noexcept
|
||||
{
|
||||
constexpr float half_range = static_cast<float>(1 << (BitsPerElement - 1));
|
||||
constexpr float packer = SMALLEST_THREE_PACK * half_range;
|
||||
|
||||
float max_value = -1.0f;
|
||||
bool sign_minus = false;
|
||||
uint32_t m = 0;
|
||||
uint32_t a = 0;
|
||||
uint32_t b = 0;
|
||||
uint32_t c = 0;
|
||||
|
||||
for (uint32_t i = 0; i < 4; i++)
|
||||
{
|
||||
float element = quaternion[i];
|
||||
|
||||
float abs = element > 0.0f ? element : -element;
|
||||
|
||||
if (abs > max_value)
|
||||
{
|
||||
sign_minus = element < 0.0f;
|
||||
m = i;
|
||||
max_value = abs;
|
||||
}
|
||||
}
|
||||
|
||||
float af = 0.0f;
|
||||
float bf = 0.0f;
|
||||
float cf = 0.0f;
|
||||
|
||||
switch (m)
|
||||
{
|
||||
case 0:
|
||||
af = quaternion[1];
|
||||
bf = quaternion[2];
|
||||
cf = quaternion[3];
|
||||
break;
|
||||
case 1:
|
||||
af = quaternion[0];
|
||||
bf = quaternion[2];
|
||||
cf = quaternion[3];
|
||||
break;
|
||||
case 2:
|
||||
af = quaternion[0];
|
||||
bf = quaternion[1];
|
||||
cf = quaternion[3];
|
||||
break;
|
||||
default: // case 3
|
||||
af = quaternion[0];
|
||||
bf = quaternion[1];
|
||||
cf = quaternion[2];
|
||||
break;
|
||||
}
|
||||
|
||||
if (sign_minus)
|
||||
{
|
||||
a = static_cast<uint32_t>((-af * packer) + half_range);
|
||||
b = static_cast<uint32_t>((-bf * packer) + half_range);
|
||||
c = static_cast<uint32_t>((-cf * packer) + half_range);
|
||||
}
|
||||
else
|
||||
{
|
||||
a = static_cast<uint32_t>((af * packer) + half_range);
|
||||
b = static_cast<uint32_t>((bf * packer) + half_range);
|
||||
c = static_cast<uint32_t>((cf * packer) + half_range);
|
||||
}
|
||||
|
||||
return { m, a, b, c };
|
||||
}
|
||||
|
||||
inline static T dequantize(const quantized_quaternion& data) noexcept
|
||||
{
|
||||
constexpr uint32_t half_range = (1 << (BitsPerElement - 1));
|
||||
constexpr float unpacker = SMALLEST_THREE_UNPACK * (1.0f / half_range);
|
||||
|
||||
float a = static_cast<float>(data.a * unpacker - half_range * unpacker);
|
||||
float b = static_cast<float>(data.b * unpacker - half_range * unpacker);
|
||||
float c = static_cast<float>(data.c * unpacker - half_range * unpacker);
|
||||
|
||||
float d = std::sqrt(1.0f - ((a * a) + (b * b) + (c * c)));
|
||||
|
||||
switch (data.m)
|
||||
{
|
||||
case 0:
|
||||
return T{ d, a, b, c };
|
||||
case 1:
|
||||
return T{ a, d, b, c };
|
||||
case 2:
|
||||
return T{ a, b, d, c };
|
||||
default: // case 3
|
||||
return T{ a, b, c, d };
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/crc.h"
|
||||
#include "../utility/endian.h"
|
||||
#include "../utility/meta.h"
|
||||
|
||||
#include "byte_buffer.h"
|
||||
#include "serialize_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A stream for writing objects tightly into a buffer
|
||||
* @note Does not take ownership of the buffer
|
||||
*/
|
||||
class bit_measure
|
||||
{
|
||||
public:
|
||||
static constexpr bool writing = true;
|
||||
static constexpr bool reading = false;
|
||||
|
||||
/**
|
||||
* @brief Default construct a writer pointing to a null buffer
|
||||
*/
|
||||
bit_measure() noexcept :
|
||||
m_NumBitsWritten(0),
|
||||
m_TotalBits((std::numeric_limits<uint32_t>::max)()) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a writer pointing to the given byte array with @p num_bytes size
|
||||
* @param num_bytes The number of bytes in the array
|
||||
*/
|
||||
bit_measure(uint32_t num_bytes) noexcept :
|
||||
m_NumBitsWritten(0),
|
||||
m_TotalBits(num_bytes * 8) {}
|
||||
|
||||
bit_measure(const bit_measure&) = delete;
|
||||
|
||||
bit_measure(bit_measure&& other) noexcept :
|
||||
m_NumBitsWritten(other.m_NumBitsWritten),
|
||||
m_TotalBits(other.m_TotalBits)
|
||||
{
|
||||
other.m_NumBitsWritten = 0;
|
||||
other.m_TotalBits = 0;
|
||||
}
|
||||
|
||||
bit_measure& operator=(const bit_measure&) = delete;
|
||||
|
||||
bit_measure& operator=(bit_measure&& rhs) noexcept
|
||||
{
|
||||
m_NumBitsWritten = rhs.m_NumBitsWritten;
|
||||
m_TotalBits = rhs.m_TotalBits;
|
||||
|
||||
rhs.m_NumBitsWritten = 0;
|
||||
rhs.m_TotalBits = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the buffer that this writer is currently serializing into
|
||||
* @return The buffer
|
||||
*/
|
||||
[[nodiscard]] uint8_t* get_buffer() const noexcept { return nullptr; }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bits which have been written to the buffer
|
||||
* @return The number of bits which have been written
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_num_bits_serialized() const noexcept { return m_NumBitsWritten; }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bytes which have been written to the buffer
|
||||
* @return The number of bytes which have been written
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_num_bytes_serialized() const noexcept { return m_NumBitsWritten > 0U ? ((m_NumBitsWritten - 1U) / 8U + 1U) : 0U; }
|
||||
|
||||
/**
|
||||
* @brief Returns whether the @p num_bits can fit in the buffer
|
||||
* @param num_bits The number of bits to test
|
||||
* @return Whether the number of bits can fit in the buffer
|
||||
*/
|
||||
[[nodiscard]] bool can_serialize_bits(uint32_t num_bits) const noexcept { return m_NumBitsWritten + num_bits <= m_TotalBits; }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bits which have not been written yet
|
||||
* @note The same as get_total_bits() - get_num_bits_serialized()
|
||||
* @return The remaining space in the buffer
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_remaining_bits() const noexcept { return m_TotalBits - m_NumBitsWritten; }
|
||||
|
||||
/**
|
||||
* @brief Returns the size of the buffer, in bits
|
||||
* @return The size of the buffer, in bits
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_total_bits() const noexcept { return m_TotalBits; }
|
||||
|
||||
/**
|
||||
* @brief Instructs the writer that you intend to use `serialize_checksum()` later on, and to reserve the first 32 bits.
|
||||
* @return Returns false if anything has already been written to the buffer or if there's no space to write the checksum
|
||||
*/
|
||||
[[nodiscard]] bool prepend_checksum() noexcept
|
||||
{
|
||||
BS_ASSERT(m_NumBitsWritten == 0);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(32U));
|
||||
|
||||
m_NumBitsWritten += 32U;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a checksum of the @p protocol_version and the rest of the buffer as the first 32 bits
|
||||
* @param protocol_version A unique version number
|
||||
* @return The number of bytes written to the buffer
|
||||
*/
|
||||
uint32_t serialize_checksum(uint32_t protocol_version) noexcept
|
||||
{
|
||||
return m_NumBitsWritten;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer up to the given number of bytes with zeros
|
||||
* @param num_bytes The byte number to pad to
|
||||
* @return Returns false if the current size of the buffer is bigger than @p num_bytes
|
||||
*/
|
||||
[[nodiscard]] bool pad_to_size(uint32_t num_bytes) noexcept
|
||||
{
|
||||
BS_ASSERT(num_bytes * 8U <= m_TotalBits);
|
||||
|
||||
BS_ASSERT(num_bytes * 8U >= m_NumBitsWritten);
|
||||
|
||||
m_NumBitsWritten = num_bytes * 8U;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer up with the given number of bytes
|
||||
* @param num_bytes The amount of bytes to pad
|
||||
* @return Returns false if the current size of the buffer is bigger than @p num_bytes or if the padded bits are not zeros.
|
||||
*/
|
||||
[[nodiscard]] bool pad(uint32_t num_bytes) noexcept
|
||||
{
|
||||
return pad_to_size(get_num_bytes_serialized() + num_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer with up to 8 zeros, so that the next write is byte-aligned
|
||||
* @return Success
|
||||
*/
|
||||
[[nodiscard]] bool align() noexcept
|
||||
{
|
||||
uint32_t remainder = m_NumBitsWritten % 8U;
|
||||
if (remainder != 0U)
|
||||
m_NumBitsWritten += 8U - remainder;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the first @p num_bits bits of @p value into the buffer
|
||||
* @param value The value to serialize
|
||||
* @param num_bits The number of bits of the @p value to serialize
|
||||
* @return Returns false if @p num_bits is less than 1 or greater than 32 or if writing the given number of bits would overflow the buffer
|
||||
*/
|
||||
[[nodiscard]] bool serialize_bits(uint32_t value, uint32_t num_bits) noexcept
|
||||
{
|
||||
BS_ASSERT(num_bits > 0U && num_bits <= 32U);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(num_bits));
|
||||
|
||||
m_NumBitsWritten += num_bits;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the first @p num_bits bits of the given byte array, 32 bits at a time
|
||||
* @param bytes The bytes to serialize
|
||||
* @param num_bits The number of bits of the @p bytes to serialize
|
||||
* @return Returns false if @p num_bits is less than 1 or if writing the given number of bits would overflow the buffer
|
||||
*/
|
||||
[[nodiscard]] bool serialize_bytes(const uint8_t* bytes, uint32_t num_bits) noexcept
|
||||
{
|
||||
BS_ASSERT(num_bits > 0U);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(num_bits));
|
||||
|
||||
m_NumBitsWritten += num_bits;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes to the buffer, using the given @p Trait.
|
||||
* @note The Trait type in this function must always be explicitly declared
|
||||
* @tparam Trait A template specialization of serialize_trait<>
|
||||
* @tparam ...Args The types of the arguments to pass to the serialize function
|
||||
* @param ...args The arguments to pass to the serialize function
|
||||
* @return Whether successful or not
|
||||
*/
|
||||
template<typename Trait, typename... Args, typename = utility::has_serialize_t<Trait, bit_measure, Args...>>
|
||||
[[nodiscard]] bool serialize(Args&&... args) noexcept(utility::is_serialize_noexcept_v<Trait, bit_measure, Args...>)
|
||||
{
|
||||
return serialize_traits<Trait>::serialize(*this, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes to the buffer, by trying to deduce the trait.
|
||||
* @note The Trait type in this function is always implicit and will be deduced from the first argument if possible.
|
||||
* If the trait cannot be deduced it will not compile.
|
||||
* @tparam Trait The type of the first argument, which will be used to deduce the trait specialization
|
||||
* @tparam ...Args The types of the arguments to pass to the serialize function
|
||||
* @param arg The first argument to pass to the serialize function
|
||||
* @param ...args The rest of the arguments to pass to the serialize function
|
||||
* @return Whether successful or not
|
||||
*/
|
||||
template<typename... Args, typename Trait, typename = utility::has_deduce_serialize_t<Trait, bit_measure, Args...>>
|
||||
[[nodiscard]] bool serialize(Trait&& arg, Args&&... args) noexcept(utility::is_deduce_serialize_noexcept_v<Trait, bit_measure, Args...>)
|
||||
{
|
||||
return serialize_traits<utility::deduce_trait_t<Trait, bit_measure, Args...>>::serialize(*this, std::forward<Trait>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t m_NumBitsWritten;
|
||||
uint32_t m_TotalBits;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/crc.h"
|
||||
#include "../utility/endian.h"
|
||||
#include "../utility/meta.h"
|
||||
|
||||
#include "byte_buffer.h"
|
||||
#include "serialize_traits.h"
|
||||
#include "stream_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A stream for reading objects from a tightly packed buffer
|
||||
* @tparam Policy The underlying representation of the buffer
|
||||
*/
|
||||
template<typename Policy>
|
||||
class bit_reader
|
||||
{
|
||||
public:
|
||||
static constexpr bool writing = false;
|
||||
static constexpr bool reading = true;
|
||||
|
||||
/**
|
||||
* @brief Construct a reader with the parameters passed to the underlying policy
|
||||
* @param ...args The arguments to pass to the policy
|
||||
*/
|
||||
template<typename... Ts,
|
||||
typename = std::enable_if_t<std::is_constructible_v<Policy, Ts...>>>
|
||||
bit_reader(Ts&&... args)
|
||||
noexcept(std::is_nothrow_constructible_v<Policy, Ts...>) :
|
||||
m_Policy(std::forward<Ts>(args) ...),
|
||||
m_Scratch(0),
|
||||
m_ScratchBits(0),
|
||||
m_WordIndex(0) {}
|
||||
|
||||
bit_reader(const bit_reader&) = delete;
|
||||
|
||||
bit_reader(bit_reader&& other) noexcept :
|
||||
m_Policy(std::move(other.m_Policy)),
|
||||
m_Scratch(other.m_Scratch),
|
||||
m_ScratchBits(other.m_ScratchBits),
|
||||
m_WordIndex(other.m_WordIndex)
|
||||
{
|
||||
other.m_Scratch = 0;
|
||||
other.m_ScratchBits = 0;
|
||||
other.m_WordIndex = 0;
|
||||
}
|
||||
|
||||
bit_reader& operator=(const bit_reader&) = delete;
|
||||
|
||||
bit_reader& operator=(bit_reader&& rhs) noexcept
|
||||
{
|
||||
m_Policy = std::move(rhs.m_Policy);
|
||||
m_Scratch = rhs.m_Scratch;
|
||||
m_ScratchBits = rhs.m_ScratchBits;
|
||||
m_WordIndex = rhs.m_WordIndex;
|
||||
|
||||
rhs.m_Scratch = 0;
|
||||
rhs.m_ScratchBits = 0;
|
||||
rhs.m_WordIndex = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the buffer that this reader is currently serializing from
|
||||
* @return The buffer
|
||||
*/
|
||||
[[nodiscard]] const uint8_t* get_buffer() const noexcept { return reinterpret_cast<const uint8_t*>(m_Policy.get_buffer()); }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bits which have been read from the buffer
|
||||
* @return The number of bits which have been read
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_num_bits_serialized() const noexcept { return m_Policy.get_num_bits_serialized(); }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bytes which have been read from the buffer
|
||||
* @return The number of bytes which have been read
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_num_bytes_serialized() const noexcept { return get_num_bits_serialized() > 0U ? ((get_num_bits_serialized() - 1U) / 8U + 1U) : 0U; }
|
||||
|
||||
/**
|
||||
* @brief Returns whether the @p num_bits be read from the buffer
|
||||
* @param num_bits The number of bits to test
|
||||
* @return Whether the number of bits can be read from the buffer
|
||||
*/
|
||||
[[nodiscard]] bool can_serialize_bits(uint32_t num_bits) const noexcept { return m_Policy.can_serialize_bits(num_bits); }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bits which have not been read yet
|
||||
* @note The same as get_total_bits() - get_num_bits_serialized()
|
||||
* @return The remaining space in the buffer
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_remaining_bits() const noexcept { return get_total_bits() - get_num_bits_serialized(); }
|
||||
|
||||
/**
|
||||
* @brief Returns the size of the buffer, in bits
|
||||
* @return The size of the buffer, in bits
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_total_bits() const noexcept { return m_Policy.get_total_bits(); }
|
||||
|
||||
/**
|
||||
* @brief Reads the first 32 bits of the buffer and compares it to a checksum of the @p protocol_version and the rest of the buffer
|
||||
* @param protocol_version A unique version number
|
||||
* @return Whether the checksum matches what was written
|
||||
*/
|
||||
[[nodiscard]] bool serialize_checksum(uint32_t protocol_version) noexcept
|
||||
{
|
||||
BS_ASSERT(get_num_bits_serialized() == 0);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(32U));
|
||||
|
||||
uint32_t num_bytes = (get_total_bits() - 1U) / 8U + 1U;
|
||||
const uint32_t* buffer = m_Policy.get_buffer();
|
||||
|
||||
// Generate checksum to compare against
|
||||
uint32_t generated_checksum = utility::crc_uint32(reinterpret_cast<const uint8_t*>(&protocol_version), reinterpret_cast<const uint8_t*>(buffer + 1), num_bytes - 4);
|
||||
|
||||
// Advance the reader by the size of the checksum (32 bits / 1 word)
|
||||
m_WordIndex++;
|
||||
|
||||
BS_ASSERT(m_Policy.extend(32U));
|
||||
|
||||
// Read the checksum
|
||||
uint32_t checksum = *buffer;
|
||||
|
||||
// Compare the checksum
|
||||
return generated_checksum == checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer up to the given number of bytes
|
||||
* @param num_bytes The byte number to pad to
|
||||
* @return Returns false if the current size of the buffer is bigger than @p num_bytes or if the padded bits are not zeros.
|
||||
*/
|
||||
[[nodiscard]] bool pad_to_size(uint32_t num_bytes) noexcept
|
||||
{
|
||||
uint32_t num_bits_read = get_num_bits_serialized();
|
||||
|
||||
BS_ASSERT(num_bytes * 8U >= num_bits_read);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(num_bytes * 8U - num_bits_read));
|
||||
|
||||
uint32_t remainder = (num_bytes * 8U - num_bits_read) % 32U;
|
||||
uint32_t zero;
|
||||
|
||||
// Test the last word more carefully, as it may have data
|
||||
if (remainder != 0U)
|
||||
{
|
||||
bool status = serialize_bits(zero, remainder);
|
||||
BS_ASSERT(status && zero == 0);
|
||||
}
|
||||
|
||||
uint32_t offset = get_num_bits_serialized() / 32;
|
||||
uint32_t max = num_bytes / 4;
|
||||
|
||||
// Test for zeros in padding
|
||||
for (uint32_t i = offset; i < max; i++)
|
||||
{
|
||||
bool status = serialize_bits(zero, 32);
|
||||
BS_ASSERT(status && zero == 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer up with the given number of bytes
|
||||
* @param num_bytes The amount of bytes to pad
|
||||
* @return Returns false if the current size of the buffer is bigger than @p num_bytes or if the padded bits are not zeros.
|
||||
*/
|
||||
[[nodiscard]] bool pad(uint32_t num_bytes) noexcept
|
||||
{
|
||||
return pad_to_size(get_num_bytes_serialized() + num_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer with up to 8 zeros, so that the next read is byte-aligned
|
||||
* @notes Return false if the padded bits are not zeros
|
||||
* @return Returns false if the padded bits are not zeros
|
||||
*/
|
||||
[[nodiscard]] bool align() noexcept
|
||||
{
|
||||
uint32_t remainder = get_num_bits_serialized() % 8U;
|
||||
if (remainder != 0U)
|
||||
{
|
||||
uint32_t zero;
|
||||
bool status = serialize_bits(zero, 8U - remainder);
|
||||
|
||||
BS_ASSERT(status && zero == 0U && get_num_bits_serialized() % 8U == 0U);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads the first @p num_bits bits of @p value from the buffer
|
||||
* @param value The value to serialize
|
||||
* @param num_bits The number of bits of the @p value to serialize
|
||||
* @return Returns false if @p num_bits is less than 1 or greater than 32 or if reading the given number of bits would overflow the buffer
|
||||
*/
|
||||
[[nodiscard]] bool serialize_bits(uint32_t& value, uint32_t num_bits) noexcept
|
||||
{
|
||||
BS_ASSERT(num_bits > 0U && num_bits <= 32U);
|
||||
|
||||
BS_ASSERT(m_Policy.extend(num_bits));
|
||||
|
||||
// This is actually slower
|
||||
// Possibly due to unlikely branching
|
||||
/*if (num_bits == 32U && m_ScratchBits == 0U)
|
||||
{
|
||||
const uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
|
||||
|
||||
value = utility::to_big_endian32(*ptr);
|
||||
|
||||
m_WordIndex++;
|
||||
|
||||
return true;
|
||||
}*/
|
||||
|
||||
if (m_ScratchBits < num_bits)
|
||||
{
|
||||
const uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
|
||||
|
||||
uint64_t ptr_value = static_cast<uint64_t>(utility::to_big_endian32(*ptr)) << (32U - m_ScratchBits);
|
||||
m_Scratch |= ptr_value;
|
||||
m_ScratchBits += 32U;
|
||||
m_WordIndex++;
|
||||
}
|
||||
|
||||
uint32_t offset = 64U - num_bits;
|
||||
value = static_cast<uint32_t>(m_Scratch >> offset);
|
||||
|
||||
m_Scratch <<= num_bits;
|
||||
m_ScratchBits -= num_bits;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads the first @p num_bits bits of the given byte array, 32 bits at a time
|
||||
* @param bytes The bytes to serialize
|
||||
* @param num_bits The number of bits of the @p bytes to serialize
|
||||
* @return Returns false if @p num_bits is less than 1 or if reading the given number of bits would overflow the buffer
|
||||
*/
|
||||
[[nodiscard]] bool serialize_bytes(uint8_t* bytes, uint32_t num_bits) noexcept
|
||||
{
|
||||
BS_ASSERT(num_bits > 0U);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(num_bits));
|
||||
|
||||
// Read the byte array as words
|
||||
uint32_t* word_buffer = reinterpret_cast<uint32_t*>(bytes);
|
||||
uint32_t num_words = num_bits / 32U;
|
||||
|
||||
if (m_ScratchBits % 32U == 0U && num_words > 0U)
|
||||
{
|
||||
BS_ASSERT(m_Policy.extend(num_words * 32U));
|
||||
|
||||
// If the read buffer is word-aligned, just memcpy it
|
||||
std::memcpy(word_buffer, m_Policy.get_buffer() + m_WordIndex, num_words * 4U);
|
||||
|
||||
m_WordIndex += num_words;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the buffer is not word-aligned, serialize a word at a time
|
||||
for (uint32_t i = 0U; i < num_words; i++)
|
||||
{
|
||||
uint32_t value;
|
||||
BS_ASSERT(serialize_bits(value, 32U));
|
||||
|
||||
// Casting a byte-array to an int is wrong on little-endian systems
|
||||
// We have to swap the bytes around
|
||||
word_buffer[i] = utility::to_big_endian32(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Early exit if the word-count matches
|
||||
if (num_bits % 32 == 0)
|
||||
return true;
|
||||
|
||||
uint32_t remaining_bits = num_bits - num_words * 32U;
|
||||
|
||||
uint32_t num_bytes = (remaining_bits - 1U) / 8U + 1U;
|
||||
for (uint32_t i = 0; i < num_bytes; i++)
|
||||
{
|
||||
uint32_t value;
|
||||
BS_ASSERT(serialize_bits(value, (std::min)(remaining_bits - i * 8U, 8U)));
|
||||
|
||||
bytes[num_words * 4 + i] = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads from the buffer, using the given @p Trait.
|
||||
* @note The Trait type in this function must always be explicitly declared
|
||||
* @tparam Trait A template specialization of serialize_trait<>
|
||||
* @tparam ...Args The types of the arguments to pass to the serialize function
|
||||
* @param ...args The arguments to pass to the serialize function
|
||||
* @return Whether successful or not
|
||||
*/
|
||||
template<typename Trait, typename... Args, typename = utility::has_serialize_t<Trait, bit_reader, Args...>>
|
||||
[[nodiscard]] bool serialize(Args&&... args) noexcept(utility::is_serialize_noexcept_v<Trait, bit_reader, Args...>)
|
||||
{
|
||||
return serialize_traits<Trait>::serialize(*this, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads from the buffer, by trying to deduce the trait.
|
||||
* @note The Trait type in this function is always implicit and will be deduced from the first argument if possible.
|
||||
* If the trait cannot be deduced it will not compile.
|
||||
* @tparam Trait The type of the first argument, which will be used to deduce the trait specialization
|
||||
* @tparam ...Args The types of the arguments to pass to the serialize function
|
||||
* @param arg The first argument to pass to the serialize function
|
||||
* @param ...args The rest of the arguments to pass to the serialize function
|
||||
* @return Whether successful or not
|
||||
*/
|
||||
template<typename... Args, typename Trait, typename = utility::has_deduce_serialize_t<Trait, bit_reader, Args...>>
|
||||
[[nodiscard]] bool serialize(Trait&& arg, Args&&... args) noexcept(utility::is_deduce_serialize_noexcept_v<Trait, bit_reader, Args...>)
|
||||
{
|
||||
return serialize_traits<utility::deduce_trait_t<Trait, bit_reader, Args...>>::serialize(*this, std::forward<Trait>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Policy m_Policy;
|
||||
|
||||
uint64_t m_Scratch;
|
||||
uint32_t m_ScratchBits;
|
||||
uint32_t m_WordIndex;
|
||||
};
|
||||
|
||||
using fixed_bit_reader = bit_reader<fixed_policy>;
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/crc.h"
|
||||
#include "../utility/endian.h"
|
||||
#include "../utility/meta.h"
|
||||
|
||||
#include "byte_buffer.h"
|
||||
#include "serialize_traits.h"
|
||||
#include "stream_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A stream for writing objects tightly into a buffer
|
||||
* @tparam Policy The underlying representation of the buffer
|
||||
*/
|
||||
template<typename Policy>
|
||||
class bit_writer
|
||||
{
|
||||
public:
|
||||
static constexpr bool writing = true;
|
||||
static constexpr bool reading = false;
|
||||
|
||||
/**
|
||||
* @brief Construct a writer with the parameters passed to the underlying policy
|
||||
* @param ...args The arguments to pass to the policy
|
||||
*/
|
||||
template<typename... Ts,
|
||||
typename = std::enable_if_t<std::is_constructible_v<Policy, Ts...>>>
|
||||
bit_writer(Ts&&... args)
|
||||
noexcept(std::is_nothrow_constructible_v<Policy, Ts...>) :
|
||||
m_Policy(std::forward<Ts>(args) ...),
|
||||
m_Scratch(0),
|
||||
m_ScratchBits(0),
|
||||
m_WordIndex(0) {}
|
||||
|
||||
bit_writer(const bit_writer&) = delete;
|
||||
|
||||
bit_writer(bit_writer&& other) noexcept :
|
||||
m_Policy(std::move(other.m_Policy)),
|
||||
m_Scratch(other.m_Scratch),
|
||||
m_ScratchBits(other.m_ScratchBits),
|
||||
m_WordIndex(other.m_WordIndex)
|
||||
{
|
||||
other.m_Scratch = 0;
|
||||
other.m_ScratchBits = 0;
|
||||
other.m_WordIndex = 0;
|
||||
}
|
||||
|
||||
bit_writer& operator=(const bit_writer&) = delete;
|
||||
|
||||
bit_writer& operator=(bit_writer&& rhs) noexcept
|
||||
{
|
||||
m_Policy = std::move(rhs.m_Policy);
|
||||
m_Scratch = rhs.m_Scratch;
|
||||
m_ScratchBits = rhs.m_ScratchBits;
|
||||
m_WordIndex = rhs.m_WordIndex;
|
||||
|
||||
rhs.m_Scratch = 0;
|
||||
rhs.m_ScratchBits = 0;
|
||||
rhs.m_WordIndex = 0;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the buffer that this writer is currently serializing into
|
||||
* @return The buffer
|
||||
*/
|
||||
[[nodiscard]] uint8_t* get_buffer() const noexcept { return reinterpret_cast<uint8_t*>(m_Policy.get_buffer()); }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bits which have been written to the buffer
|
||||
* @return The number of bits which have been written
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_num_bits_serialized() const noexcept { return m_Policy.get_num_bits_serialized(); }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bytes which have been written to the buffer
|
||||
* @return The number of bytes which have been written
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_num_bytes_serialized() const noexcept { return get_num_bits_serialized() > 0U ? ((get_num_bits_serialized() - 1U) / 8U + 1U) : 0U; }
|
||||
|
||||
/**
|
||||
* @brief Returns whether the @p num_bits can fit in the buffer
|
||||
* @param num_bits The number of bits to test
|
||||
* @return Whether the number of bits can fit in the buffer
|
||||
*/
|
||||
[[nodiscard]] bool can_serialize_bits(uint32_t num_bits) const noexcept { return m_Policy.can_serialize_bits(num_bits); }
|
||||
|
||||
/**
|
||||
* @brief Returns the number of bits which have not been written yet
|
||||
* @note The same as get_total_bits() - get_num_bits_serialized()
|
||||
* @return The remaining space in the buffer
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_remaining_bits() const noexcept { return get_total_bits() - get_num_bits_serialized(); }
|
||||
|
||||
/**
|
||||
* @brief Returns the size of the buffer, in bits
|
||||
* @return The size of the buffer, in bits
|
||||
*/
|
||||
[[nodiscard]] uint32_t get_total_bits() const noexcept { return m_Policy.get_total_bits(); }
|
||||
|
||||
/**
|
||||
* @brief Flushes any remaining bits into the buffer. Use this when you no longer intend to write anything to the buffer.
|
||||
* @return The number of bytes written to the buffer
|
||||
*/
|
||||
uint32_t flush() noexcept
|
||||
{
|
||||
if (m_ScratchBits > 0U)
|
||||
{
|
||||
uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
|
||||
uint32_t ptr_value = static_cast<uint32_t>(m_Scratch >> 32U);
|
||||
*ptr = utility::to_big_endian32(ptr_value);
|
||||
|
||||
m_Scratch = 0U;
|
||||
m_ScratchBits = 0U;
|
||||
m_WordIndex++;
|
||||
}
|
||||
|
||||
return get_num_bits_serialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Instructs the writer that you intend to use `serialize_checksum()` later on, and to reserve the first 32 bits.
|
||||
* @return Returns false if anything has already been written to the buffer or if there's no space to write the checksum
|
||||
*/
|
||||
[[nodiscard]] bool prepend_checksum() noexcept
|
||||
{
|
||||
BS_ASSERT(get_num_bits_serialized() == 0);
|
||||
|
||||
BS_ASSERT(m_Policy.extend(32U));
|
||||
|
||||
// Advance the reader by the size of the checksum (32 bits / 1 word)
|
||||
m_WordIndex++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a checksum of the @p protocol_version and the rest of the buffer as the first 32 bits
|
||||
* @param protocol_version A unique version number
|
||||
* @return The number of bytes written to the buffer
|
||||
*/
|
||||
uint32_t serialize_checksum(uint32_t protocol_version) noexcept
|
||||
{
|
||||
uint32_t num_bits = flush();
|
||||
|
||||
BS_ASSERT(num_bits > 32U);
|
||||
|
||||
// Copy protocol version to buffer
|
||||
uint32_t* buffer = m_Policy.get_buffer();
|
||||
*buffer = protocol_version;
|
||||
|
||||
// Generate checksum of version + data
|
||||
uint32_t checksum = utility::crc_uint32(reinterpret_cast<uint8_t*>(buffer), get_num_bytes_serialized());
|
||||
|
||||
// Put checksum at beginning
|
||||
*buffer = checksum;
|
||||
|
||||
return num_bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer up to the given number of bytes with zeros
|
||||
* @param num_bytes The byte number to pad to
|
||||
* @return Returns false if the current size of the buffer is bigger than @p num_bytes
|
||||
*/
|
||||
[[nodiscard]] bool pad_to_size(uint32_t num_bytes) noexcept
|
||||
{
|
||||
uint32_t num_bits_written = get_num_bits_serialized();
|
||||
|
||||
BS_ASSERT(num_bytes * 8U >= num_bits_written);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(num_bytes * 8U - num_bits_written));
|
||||
|
||||
if (num_bits_written == 0)
|
||||
{
|
||||
BS_ASSERT(m_Policy.extend(num_bytes * 8U - num_bits_written));
|
||||
|
||||
std::memset(m_Policy.get_buffer(), 0, num_bytes);
|
||||
|
||||
m_Scratch = 0;
|
||||
m_ScratchBits = 0;
|
||||
m_WordIndex = num_bytes / 4;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t remainder = (num_bytes * 8U - num_bits_written) % 32U;
|
||||
uint32_t zero = 0;
|
||||
|
||||
// Align to byte
|
||||
if (remainder != 0U)
|
||||
BS_ASSERT(serialize_bits(zero, remainder));
|
||||
|
||||
uint32_t offset = get_num_bits_serialized() / 32;
|
||||
uint32_t max = num_bytes / 4;
|
||||
|
||||
// Serialize words
|
||||
for (uint32_t i = offset; i < max; i++)
|
||||
BS_ASSERT(serialize_bits(zero, 32));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer up with the given number of bytes
|
||||
* @param num_bytes The amount of bytes to pad
|
||||
* @return Returns false if the current size of the buffer is bigger than @p num_bytes or if the padded bits are not zeros.
|
||||
*/
|
||||
[[nodiscard]] bool pad(uint32_t num_bytes) noexcept
|
||||
{
|
||||
return pad_to_size(get_num_bytes_serialized() + num_bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pads the buffer with up to 8 zeros, so that the next write is byte-aligned
|
||||
* @return Success
|
||||
*/
|
||||
[[nodiscard]] bool align() noexcept
|
||||
{
|
||||
uint32_t remainder = m_ScratchBits % 8U;
|
||||
if (remainder != 0U)
|
||||
{
|
||||
uint32_t zero = 0U;
|
||||
bool status = serialize_bits(zero, 8U - remainder);
|
||||
|
||||
BS_ASSERT(status && get_num_bits_serialized() % 8U == 0U);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the first @p num_bits bits of @p value into the buffer
|
||||
* @param value The value to serialize
|
||||
* @param num_bits The number of bits of the @p value to serialize
|
||||
* @return Returns false if @p num_bits is less than 1 or greater than 32 or if writing the given number of bits would overflow the buffer
|
||||
*/
|
||||
[[nodiscard]] bool serialize_bits(uint32_t value, uint32_t num_bits) noexcept
|
||||
{
|
||||
BS_ASSERT(num_bits > 0U && num_bits <= 32U);
|
||||
|
||||
BS_ASSERT(m_Policy.extend(num_bits));
|
||||
|
||||
// This is actually slower
|
||||
// Possibly due to unlikely branching
|
||||
/*if (num_bits == 32U && m_ScratchBits == 0U)
|
||||
{
|
||||
uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
|
||||
|
||||
*ptr = utility::to_big_endian32(value);
|
||||
|
||||
m_WordIndex++;
|
||||
|
||||
return true;
|
||||
}*/
|
||||
|
||||
uint32_t offset = 64U - num_bits - m_ScratchBits;
|
||||
uint64_t ls_value = static_cast<uint64_t>(value) << offset;
|
||||
|
||||
m_Scratch |= ls_value;
|
||||
m_ScratchBits += num_bits;
|
||||
|
||||
if (m_ScratchBits >= 32U)
|
||||
{
|
||||
uint32_t* ptr = m_Policy.get_buffer() + m_WordIndex;
|
||||
uint32_t ptr_value = static_cast<uint32_t>(m_Scratch >> 32U);
|
||||
*ptr = utility::to_big_endian32(ptr_value);
|
||||
m_Scratch <<= 32ULL;
|
||||
m_ScratchBits -= 32U;
|
||||
m_WordIndex++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the first @p num_bits bits of the given byte array, 32 bits at a time
|
||||
* @param bytes The bytes to serialize
|
||||
* @param num_bits The number of bits of the @p bytes to serialize
|
||||
* @return Returns false if @p num_bits is less than 1 or if writing the given number of bits would overflow the buffer
|
||||
*/
|
||||
[[nodiscard]] bool serialize_bytes(const uint8_t* bytes, uint32_t num_bits) noexcept
|
||||
{
|
||||
BS_ASSERT(num_bits > 0U);
|
||||
|
||||
BS_ASSERT(can_serialize_bits(num_bits));
|
||||
|
||||
// Write the byte array as words
|
||||
const uint32_t* word_buffer = reinterpret_cast<const uint32_t*>(bytes);
|
||||
uint32_t num_words = num_bits / 32U;
|
||||
|
||||
if (m_ScratchBits % 32U == 0U && num_words > 0U)
|
||||
{
|
||||
BS_ASSERT(m_Policy.extend(num_words * 32U));
|
||||
|
||||
// If the written buffer is word-aligned, just memcpy it
|
||||
std::memcpy(m_Policy.get_buffer() + m_WordIndex, word_buffer, num_words * 4U);
|
||||
|
||||
m_WordIndex += num_words;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the buffer is not word-aligned, serialize a word at a time
|
||||
for (uint32_t i = 0U; i < num_words; i++)
|
||||
{
|
||||
// Casting a byte-array to an int is wrong on little-endian systems
|
||||
// We have to swap the bytes around
|
||||
uint32_t value = utility::to_big_endian32(word_buffer[i]);
|
||||
BS_ASSERT(serialize_bits(value, 32U));
|
||||
}
|
||||
}
|
||||
|
||||
// Early exit if the word-count matches
|
||||
if (num_bits % 32U == 0U)
|
||||
return true;
|
||||
|
||||
uint32_t remaining_bits = num_bits - num_words * 32U;
|
||||
|
||||
uint32_t num_bytes = (remaining_bits - 1U) / 8U + 1U;
|
||||
for (uint32_t i = 0U; i < num_bytes; i++)
|
||||
{
|
||||
uint32_t value = static_cast<uint32_t>(bytes[num_words * 4U + i]);
|
||||
BS_ASSERT(serialize_bits(value, (std::min)(remaining_bits - i * 8U, 8U)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the contents of the buffer into the given @p writer. Essentially copies the entire buffer without modifying it.
|
||||
* @param writer The writer to copy into
|
||||
* @return Returns false if writing would overflow the buffer
|
||||
*/
|
||||
[[nodiscard]] bool serialize_into(bit_writer& writer) const noexcept
|
||||
{
|
||||
uint8_t* buffer = reinterpret_cast<uint8_t*>(m_Policy.get_buffer());
|
||||
uint32_t num_bits = get_num_bits_serialized();
|
||||
uint32_t remainder_bits = num_bits % 8U;
|
||||
|
||||
BS_ASSERT(writer.serialize_bytes(buffer, num_bits - remainder_bits));
|
||||
|
||||
if (remainder_bits > 0U)
|
||||
{
|
||||
uint32_t byte_value = buffer[num_bits / 8U] >> (8U - remainder_bits);
|
||||
BS_ASSERT(writer.serialize_bits(byte_value, remainder_bits));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes to the buffer, using the given @p Trait.
|
||||
* @note The Trait type in this function must always be explicitly declared
|
||||
* @tparam Trait A template specialization of serialize_trait<>
|
||||
* @tparam ...Args The types of the arguments to pass to the serialize function
|
||||
* @param ...args The arguments to pass to the serialize function
|
||||
* @return Whether successful or not
|
||||
*/
|
||||
template<typename Trait, typename... Args, typename = utility::has_serialize_t<Trait, bit_writer, Args...>>
|
||||
[[nodiscard]] bool serialize(Args&&... args) noexcept(utility::is_serialize_noexcept_v<Trait, bit_writer, Args...>)
|
||||
{
|
||||
return serialize_traits<Trait>::serialize(*this, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes to the buffer, by trying to deduce the trait.
|
||||
* @note The Trait type in this function is always implicit and will be deduced from the first argument if possible.
|
||||
* If the trait cannot be deduced it will not compile.
|
||||
* @tparam Trait The type of the first argument, which will be used to deduce the trait specialization
|
||||
* @tparam ...Args The types of the arguments to pass to the serialize function
|
||||
* @param arg The first argument to pass to the serialize function
|
||||
* @param ...args The rest of the arguments to pass to the serialize function
|
||||
* @return Whether successful or not
|
||||
*/
|
||||
template<typename... Args, typename Trait, typename = utility::has_deduce_serialize_t<Trait, bit_writer, Args...>>
|
||||
[[nodiscard]] bool serialize(Trait&& arg, Args&&... args) noexcept(utility::is_deduce_serialize_noexcept_v<Trait, bit_writer, Args...>)
|
||||
{
|
||||
return serialize_traits<utility::deduce_trait_t<Trait, bit_writer, Args...>>::serialize(*this, std::forward<Trait>(arg), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Policy m_Policy;
|
||||
|
||||
uint64_t m_Scratch;
|
||||
int m_ScratchBits;
|
||||
size_t m_WordIndex;
|
||||
};
|
||||
|
||||
using fixed_bit_writer = bit_writer<fixed_policy>;
|
||||
|
||||
template<typename T>
|
||||
using growing_bit_writer = bit_writer<growing_policy<T>>;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A byte buffer aligned to 4 bytes.
|
||||
* Can be used with bit_reader and bit_writer.
|
||||
* @note Size must be a multiple of 4
|
||||
*/
|
||||
template<size_t Size>
|
||||
struct byte_buffer
|
||||
{
|
||||
static_assert(Size % 4 == 0, "Buffer size must be a multiple of 4");
|
||||
|
||||
alignas(uint32_t) uint8_t Bytes[Size];
|
||||
|
||||
uint8_t& operator[](size_t i) noexcept { return Bytes[i]; }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A class for specializing trait serialization functions
|
||||
* @tparam Trait Make a specialization on this type
|
||||
* @tparam Void Use std::enable_if here if you need to, otherwise leave empty
|
||||
*/
|
||||
template<typename Trait, typename Void = void>
|
||||
struct serialize_traits;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "byte_buffer.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
struct fixed_policy
|
||||
{
|
||||
/**
|
||||
* @brief Construct a stream pointing to the given byte array with @p num_bytes size
|
||||
* @param bytes The byte array to serialize to/from. Must be 4-byte aligned and the size must be a multiple of 4
|
||||
* @param num_bytes The number of bytes in the array
|
||||
*/
|
||||
fixed_policy(void* buffer, uint32_t num_bits) noexcept :
|
||||
m_Buffer(static_cast<uint32_t*>(buffer)),
|
||||
m_NumBitsSerialized(0),
|
||||
m_TotalBits(num_bits) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a stream pointing to the given @p buffer
|
||||
* @param buffer The buffer to serialize to/from
|
||||
* @param num_bits The maximum number of bits that we can read
|
||||
*/
|
||||
template<size_t Size>
|
||||
fixed_policy(byte_buffer<Size>& buffer, uint32_t num_bits) noexcept :
|
||||
m_Buffer(reinterpret_cast<uint32_t*>(buffer.Bytes)),
|
||||
m_NumBitsSerialized(0),
|
||||
m_TotalBits(num_bits) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a stream pointing to the given @p buffer
|
||||
* @param buffer The buffer to serialize to/from
|
||||
*/
|
||||
template<size_t Size>
|
||||
fixed_policy(byte_buffer<Size>& buffer) noexcept :
|
||||
m_Buffer(reinterpret_cast<uint32_t*>(buffer.Bytes)),
|
||||
m_NumBitsSerialized(0),
|
||||
m_TotalBits(Size * 8) {}
|
||||
|
||||
uint32_t* get_buffer() const noexcept { return m_Buffer; }
|
||||
|
||||
// TODO: Transition sizes to size_t
|
||||
uint32_t get_num_bits_serialized() const noexcept { return m_NumBitsSerialized; }
|
||||
|
||||
bool can_serialize_bits(uint32_t num_bits) const noexcept { return m_NumBitsSerialized + num_bits <= m_TotalBits; }
|
||||
|
||||
uint32_t get_total_bits() const noexcept { return m_TotalBits; }
|
||||
|
||||
bool extend(uint32_t num_bits) noexcept
|
||||
{
|
||||
if (!can_serialize_bits(num_bits))
|
||||
return false;
|
||||
|
||||
m_NumBitsSerialized += num_bits;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t* m_Buffer;
|
||||
// TODO: Transition sizes to size_t
|
||||
uint32_t m_NumBitsSerialized;
|
||||
uint32_t m_TotalBits;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct growing_policy
|
||||
{
|
||||
growing_policy(T& container) noexcept :
|
||||
m_Buffer(container),
|
||||
m_NumBitsSerialized(0) {}
|
||||
|
||||
uint32_t* get_buffer() const noexcept { return m_Buffer.data(); }
|
||||
|
||||
uint32_t get_num_bits_serialized() const noexcept { return m_NumBitsSerialized; }
|
||||
|
||||
bool can_serialize_bits(uint32_t num_bits) const noexcept { return true; }
|
||||
|
||||
uint32_t get_total_bits() const noexcept { return (std::numeric_limits<uint32_t>::max)(); }
|
||||
|
||||
bool extend(uint32_t num_bits)
|
||||
{
|
||||
m_NumBitsSerialized += num_bits;
|
||||
uint32_t num_bytes = (m_NumBitsSerialized - 1) / 8U + 1;
|
||||
m_Buffer.resize(num_bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
T& m_Buffer;
|
||||
|
||||
uint32_t m_NumBitsSerialized;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/meta.h"
|
||||
#include "../utility/parameter.h"
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
#include "../traits/bool_trait.h"
|
||||
#include "../traits/integral_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief Wrapper type for subsets of arrays
|
||||
* @tparam T The type of the array
|
||||
*/
|
||||
template<typename T, typename = T>
|
||||
struct array_subset;
|
||||
|
||||
/**
|
||||
* @brief A trait used for serializing a subset of an array of objects
|
||||
* @tparam T The type of the object in the array
|
||||
* @tparam Trait
|
||||
*/
|
||||
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 constexpr (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 constexpr (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 constexpr (Stream::writing)
|
||||
{
|
||||
BS_ASSERT(previous < current);
|
||||
difference = current - previous;
|
||||
BS_ASSERT(difference > 0);
|
||||
}
|
||||
|
||||
// +1 (1 bit)
|
||||
bool plus_one;
|
||||
if constexpr (Stream::writing)
|
||||
plus_one = difference == 1;
|
||||
BS_ASSERT(stream.template serialize<bool>(plus_one));
|
||||
if (plus_one)
|
||||
{
|
||||
if constexpr (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 constexpr (Stream::reading)
|
||||
current = previous + difference;
|
||||
previous = current;
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Writes a subset of the array @p values into the writer
|
||||
* @tparam Compare A function type which returns a bool
|
||||
* @tparam ...Args The types of any additional arguments
|
||||
* @param writer The stream to write to
|
||||
* @param values The array of objects to serialize
|
||||
* @param max_size The size of the array
|
||||
* @param compare A function which returns true if the object should be written, false otherwise
|
||||
* @param ...args Any additional arguments to use when serializing each individual object
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream, typename Compare, typename... Args>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, T* values, int max_size, Compare compare, Args&&... args) noexcept
|
||||
{
|
||||
int prev_index = -1;
|
||||
for (int index = 0; index < max_size; index++)
|
||||
{
|
||||
if (!compare(values[index]))
|
||||
continue;
|
||||
|
||||
BS_ASSERT(serialize_index(writer, prev_index, index, max_size));
|
||||
|
||||
BS_ASSERT(writer.template serialize<Trait>(values[index], std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
BS_ASSERT(serialize_index(writer, prev_index, max_size, max_size));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes a subset of a serialized array into @p values
|
||||
* @tparam ...Args The types of any additional arguments
|
||||
* @param reader The stream to read from
|
||||
* @param values The array of objects to read into
|
||||
* @param max_size The size of the array
|
||||
* @param compare Not used, but kept for compatibility with the serialize write function
|
||||
* @param ...args Any additional arguments to use when serializing each individual object
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream, typename... Args>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, T* values, int max_size, Args&&... args) noexcept
|
||||
{
|
||||
int prev_index = -1;
|
||||
int index = 0;
|
||||
while (true)
|
||||
{
|
||||
BS_ASSERT(serialize_index(reader, prev_index, index, max_size));
|
||||
|
||||
if (index == max_size)
|
||||
break;
|
||||
|
||||
BS_ASSERT(reader.template serialize<Trait>(values[index], std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/meta.h"
|
||||
#include "../utility/parameter.h"
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A trait used to serialize a boolean as a single bit
|
||||
*/
|
||||
template<>
|
||||
struct serialize_traits<bool>
|
||||
{
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, in<bool> value) noexcept
|
||||
{
|
||||
uint32_t unsigned_value = value;
|
||||
|
||||
return writer.serialize_bits(unsigned_value, 1U);
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, out<bool> value) noexcept
|
||||
{
|
||||
uint32_t unsigned_value;
|
||||
|
||||
BS_ASSERT(reader.serialize_bits(unsigned_value, 1U));
|
||||
|
||||
value = unsigned_value;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A trait used to serialize multiple boolean values
|
||||
*/
|
||||
template<size_t Size>
|
||||
struct serialize_traits<bool[Size]>
|
||||
{
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, const bool* values) noexcept
|
||||
{
|
||||
uint32_t unsigned_value;
|
||||
for (size_t i = 0; i < Size; i++)
|
||||
{
|
||||
unsigned_value = values[i];
|
||||
BS_ASSERT(writer.serialize_bits(unsigned_value, 1U));
|
||||
}
|
||||
|
||||
return writer.serialize_bits(unsigned_value, 1U);
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, bool* values) noexcept
|
||||
{
|
||||
uint32_t unsigned_value;
|
||||
for (size_t i = 0; i < Size; i++)
|
||||
{
|
||||
BS_ASSERT(reader.serialize_bits(unsigned_value, 1U));
|
||||
|
||||
values[i] = unsigned_value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/meta.h"
|
||||
#include "../utility/parameter.h"
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
#include "../traits/integral_traits.h"
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief Wrapper type for compiletime known integer bounds
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T, std::underlying_type_t<T> = (std::numeric_limits<T>::min)(), std::underlying_type_t<T> = (std::numeric_limits<T>::max)()>
|
||||
struct bounded_enum;
|
||||
|
||||
/**
|
||||
* @brief A trait used to serialize an enum type with runtime bounds
|
||||
*/
|
||||
template<typename T>
|
||||
struct serialize_traits<T, typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
{
|
||||
using value_type = std::underlying_type_t<T>;
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, T value, value_type min = 0, value_type max = (std::numeric_limits<value_type>::max)()) noexcept
|
||||
{
|
||||
value_type unsigned_value = static_cast<value_type>(value);
|
||||
|
||||
return writer.template serialize<value_type>(unsigned_value, min, max);
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, T& value, value_type min = 0, value_type max = (std::numeric_limits<value_type>::max)()) noexcept
|
||||
{
|
||||
value_type unsigned_value;
|
||||
|
||||
BS_ASSERT(reader.template serialize<value_type>(unsigned_value, min, max));
|
||||
|
||||
value = static_cast<T>(unsigned_value);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A trait used to serialize an enum type with compiletime bounds
|
||||
*/
|
||||
template<typename T, std::underlying_type_t<T> Min, std::underlying_type_t<T> Max>
|
||||
struct serialize_traits<bounded_enum<T, Min, Max>, typename std::enable_if_t<std::is_enum_v<T>>>
|
||||
{
|
||||
using value_type = std::underlying_type_t<T>;
|
||||
using bound_type = bounded_int<value_type, Min, Max>;
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, T value) noexcept
|
||||
{
|
||||
value_type unsigned_value = static_cast<value_type>(value);
|
||||
|
||||
return writer.template serialize<bound_type>(unsigned_value);
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, T& value) noexcept
|
||||
{
|
||||
value_type unsigned_value;
|
||||
|
||||
BS_ASSERT(reader.template serialize<bound_type>(unsigned_value));
|
||||
|
||||
value = static_cast<T>(unsigned_value);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/meta.h"
|
||||
#include "../utility/parameter.h"
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A trait used to serialize a float as-is, without any bound checking or quantization
|
||||
*/
|
||||
template<>
|
||||
struct serialize_traits<float>
|
||||
{
|
||||
/**
|
||||
* @brief Serializes a whole float into the writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The float to serialize
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, in<float> value) noexcept
|
||||
{
|
||||
uint32_t tmp;
|
||||
std::memcpy(&tmp, &value, sizeof(float));
|
||||
|
||||
BS_ASSERT(writer.serialize_bits(tmp, 32));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serializes a whole float from the reader
|
||||
* @param reader The stream to read from
|
||||
* @param value The float to serialize to
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, float& value) noexcept
|
||||
{
|
||||
uint32_t tmp;
|
||||
|
||||
BS_ASSERT(reader.serialize_bits(tmp, 32));
|
||||
|
||||
std::memcpy(&value, &tmp, sizeof(float));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A trait used to serialize a double as-is, without any bound checking or quantization
|
||||
*/
|
||||
template<>
|
||||
struct serialize_traits<double>
|
||||
{
|
||||
/**
|
||||
* @brief Serializes a whole double into the writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The double to serialize
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, in<double> value) noexcept
|
||||
{
|
||||
uint32_t tmp[2];
|
||||
std::memcpy(tmp, &value, sizeof(double));
|
||||
|
||||
BS_ASSERT(writer.serialize_bits(tmp[0], 32));
|
||||
BS_ASSERT(writer.serialize_bits(tmp[1], 32));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serializes a whole double from the reader
|
||||
* @param reader The stream to read from
|
||||
* @param value The double to serialize to
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, double& value) noexcept
|
||||
{
|
||||
uint32_t tmp[2];
|
||||
|
||||
BS_ASSERT(reader.serialize_bits(tmp[0], 32));
|
||||
BS_ASSERT(reader.serialize_bits(tmp[1], 32));
|
||||
|
||||
std::memcpy(&value, tmp, sizeof(double));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/bits.h"
|
||||
#include "../utility/meta.h"
|
||||
#include "../utility/parameter.h"
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief Wrapper type for compiletime known integer bounds
|
||||
* @tparam T
|
||||
*/
|
||||
template<typename T, T = (std::numeric_limits<T>::min)(), T = (std::numeric_limits<T>::max)()>
|
||||
struct bounded_int;
|
||||
|
||||
#pragma region const integral types
|
||||
/**
|
||||
* @brief A trait used to serialize integer values with compiletime bounds
|
||||
* @tparam T A type matching an integer value
|
||||
* @tparam Min The lower bound. Inclusive
|
||||
* @tparam Max The upper bound. Inclusive
|
||||
*/
|
||||
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> && !std::is_const_v<T>>>
|
||||
{
|
||||
static_assert(sizeof(T) <= 8, "Integers larger than 8 bytes are currently not supported. You will have to write this functionality yourself");
|
||||
|
||||
/**
|
||||
* @brief Writes an integer into the @p writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The value to serialize
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, in<T> value) noexcept
|
||||
{
|
||||
static_assert(Min < Max);
|
||||
|
||||
BS_ASSERT(value >= Min && value <= Max);
|
||||
|
||||
constexpr uint32_t num_bits = utility::bits_in_range(Min, Max);
|
||||
|
||||
static_assert(num_bits <= sizeof(T) * 8);
|
||||
|
||||
if constexpr (sizeof(T) > 4 && 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));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads an integer from the @p writer into @p value
|
||||
* @param reader The stream to read from
|
||||
* @param value The value to serialize
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, T& value) noexcept
|
||||
{
|
||||
static_assert(Min < Max);
|
||||
|
||||
constexpr uint32_t num_bits = utility::bits_in_range(Min, Max);
|
||||
|
||||
static_assert(num_bits <= sizeof(T) * 8);
|
||||
|
||||
if constexpr (sizeof(T) > 4 && 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, 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 = static_cast<T>(unsigned_value) + Min;
|
||||
}
|
||||
|
||||
BS_ASSERT(value >= Min && value <= Max);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#pragma endregion
|
||||
|
||||
#pragma region integral types
|
||||
/**
|
||||
* @brief A trait used to serialize integer values with runtime bounds
|
||||
* @tparam T A type matching an integer value
|
||||
*/
|
||||
template<typename T>
|
||||
struct serialize_traits<T, typename std::enable_if_t<std::is_integral_v<T> && !std::is_const_v<T>>>
|
||||
{
|
||||
static_assert(sizeof(T) <= 8, "Integers larger than 8 bytes are currently not supported. You will have to write this functionality yourself");
|
||||
|
||||
/**
|
||||
* @brief Writes an integer into the @p writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The value to serialize
|
||||
* @param min The minimum bound that @p value can be. Inclusive
|
||||
* @param max The maximum bound that @p value can be. Inclusive
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, in<T> value, T min, T max) noexcept
|
||||
{
|
||||
BS_ASSERT(min < max);
|
||||
|
||||
BS_ASSERT(value >= min && value <= max);
|
||||
|
||||
uint32_t num_bits = utility::bits_in_range(min, max);
|
||||
|
||||
BS_ASSERT(num_bits <= sizeof(T) * 8);
|
||||
|
||||
if constexpr (sizeof(T) > 4)
|
||||
{
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads an integer from the @p reader into @p value
|
||||
* @param reader The stream to read from
|
||||
* @param value The value to read into
|
||||
* @param min The minimum bound that @p value can be. Inclusive
|
||||
* @param max The maximum bound that @p value can be. Inclusive
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, T& value, T min, T max) noexcept
|
||||
{
|
||||
BS_ASSERT(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 (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, num_bits - 32));
|
||||
value |= static_cast<T>(unsigned_value) << 32;
|
||||
|
||||
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);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes or reads an integer into the @p stream
|
||||
* @param stream The stream to serialize to/from
|
||||
* @param value The value to serialize
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream, typename U>
|
||||
static bool serialize(Stream& stream, U&& value) noexcept
|
||||
{
|
||||
return serialize_traits<bounded_int<T>>::serialize(stream, std::forward<U>(value));
|
||||
}
|
||||
};
|
||||
#pragma endregion
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
#pragma once
|
||||
#include "../quantization/bounded_range.h"
|
||||
#include "../quantization/half_precision.h"
|
||||
#include "../quantization/smallest_three.h"
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/meta.h"
|
||||
#include "../utility/parameter.h"
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief A trait used to serialize a single-precision float as half-precision
|
||||
*/
|
||||
template<>
|
||||
struct serialize_traits<half_precision>
|
||||
{
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& stream, in<float> value) noexcept
|
||||
{
|
||||
uint32_t int_value = half_precision::quantize(value);
|
||||
|
||||
BS_ASSERT(stream.serialize_bits(int_value, 16));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& stream, out<float> value) noexcept
|
||||
{
|
||||
uint32_t int_value;
|
||||
|
||||
BS_ASSERT(stream.serialize_bits(int_value, 16));
|
||||
|
||||
value = half_precision::dequantize(int_value);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A trait used to quantize and serialize a float to be within a given range and precision
|
||||
*/
|
||||
template<>
|
||||
struct serialize_traits<bounded_range>
|
||||
{
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& stream, in<bounded_range> range, in<float> value) noexcept
|
||||
{
|
||||
uint32_t int_value = range.quantize(value);
|
||||
|
||||
BS_ASSERT(stream.serialize_bits(int_value, range.get_bits_required()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& stream, in<bounded_range> range, out<float> value) noexcept
|
||||
{
|
||||
uint32_t int_value;
|
||||
|
||||
BS_ASSERT(stream.serialize_bits(int_value, range.get_bits_required()));
|
||||
|
||||
value = range.dequantize(int_value);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A trait used to quantize and serialize quaternions using the smallest-three algorithm
|
||||
*/
|
||||
template<typename Q, size_t BitsPerElement>
|
||||
struct serialize_traits<smallest_three<Q, BitsPerElement>>
|
||||
{
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& stream, in<Q> value) noexcept
|
||||
{
|
||||
quantized_quaternion quantized_quat = smallest_three<Q, BitsPerElement>::quantize(value);
|
||||
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.m, 2));
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.a, BitsPerElement));
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.b, BitsPerElement));
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.c, BitsPerElement));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& stream, out<Q> value) noexcept
|
||||
{
|
||||
quantized_quaternion quantized_quat;
|
||||
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.m, 2));
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.a, BitsPerElement));
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.b, BitsPerElement));
|
||||
BS_ASSERT(stream.serialize_bits(quantized_quat.c, BitsPerElement));
|
||||
|
||||
value = smallest_three<Q, BitsPerElement>::dequantize(quantized_quat);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
#pragma once
|
||||
#include "../utility/assert.h"
|
||||
#include "../utility/bits.h"
|
||||
#include "../utility/meta.h"
|
||||
#include "../utility/parameter.h"
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
/**
|
||||
* @brief Wrapper type for compiletime known string max_size
|
||||
*/
|
||||
template<typename T, size_t I>
|
||||
struct bounded_string;
|
||||
|
||||
#pragma region const char*
|
||||
/**
|
||||
* @brief A trait used to serialize bounded c-style strings
|
||||
*/
|
||||
template<>
|
||||
struct serialize_traits<const char*>
|
||||
{
|
||||
/**
|
||||
* @brief Writes a c-style string into the @p writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The string to serialize
|
||||
* @param max_size The maximum expected length of the string, including the null terminator
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, const char* value, uint32_t max_size) noexcept
|
||||
{
|
||||
uint32_t length = static_cast<uint32_t>(std::char_traits<char>::length(value));
|
||||
|
||||
BS_ASSERT(length < max_size);
|
||||
|
||||
if (length == 0)
|
||||
return true;
|
||||
|
||||
uint32_t num_bits = utility::bits_to_represent(max_size);
|
||||
|
||||
BS_ASSERT(writer.serialize_bits(length, num_bits));
|
||||
|
||||
return writer.serialize_bytes(reinterpret_cast<const uint8_t*>(value), length * 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a c-style string from the @p reader into @p value
|
||||
* @param reader The stream to read from
|
||||
* @param value A pointer to the buffer that should be read into. The size of this buffer should be at least @p max_size
|
||||
* @param max_size The maximum expected length of the string, including the null terminator
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, char* value, uint32_t max_size) noexcept
|
||||
{
|
||||
uint32_t num_bits = utility::bits_to_represent(max_size);
|
||||
|
||||
uint32_t length;
|
||||
BS_ASSERT(reader.serialize_bits(length, num_bits));
|
||||
|
||||
BS_ASSERT(length < max_size);
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
value[0] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
BS_ASSERT(reader.serialize_bytes(reinterpret_cast<uint8_t*>(value), length * 8));
|
||||
|
||||
value[length] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A trait used to serialize bounded c-style strings with compiletime bounds
|
||||
* @tparam MaxSize The maximum expected length of the string, including the null terminator
|
||||
*/
|
||||
template<size_t MaxSize>
|
||||
struct serialize_traits<bounded_string<const char*, MaxSize>>
|
||||
{
|
||||
/**
|
||||
* @brief Writes a c-style string into the @p writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The string to serialize
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, const char* value) noexcept
|
||||
{
|
||||
uint32_t length = static_cast<uint32_t>(std::char_traits<char>::length(value));
|
||||
|
||||
BS_ASSERT(length < MaxSize);
|
||||
|
||||
if (length == 0)
|
||||
return true;
|
||||
|
||||
constexpr uint32_t num_bits = utility::bits_to_represent(MaxSize);
|
||||
|
||||
BS_ASSERT(writer.serialize_bits(length, num_bits));
|
||||
|
||||
return writer.serialize_bytes(reinterpret_cast<const uint8_t*>(value), length * 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a c-style string from the @p reader into @p value
|
||||
* @param reader The stream to read from
|
||||
* @param value A pointer to the buffer that should be read into. The size of this buffer should be at least @p max_size
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, char* value) noexcept
|
||||
{
|
||||
constexpr uint32_t num_bits = utility::bits_to_represent(MaxSize);
|
||||
|
||||
uint32_t length;
|
||||
BS_ASSERT(reader.serialize_bits(length, num_bits));
|
||||
|
||||
BS_ASSERT(length < MaxSize);
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
value[0] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
BS_ASSERT(reader.serialize_bytes(reinterpret_cast<uint8_t*>(value), length * 8));
|
||||
|
||||
value[length] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#pragma endregion
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
/**
|
||||
* @brief A trait used to serialize bounded c-style UTF-8 strings
|
||||
*/
|
||||
template<>
|
||||
struct serialize_traits<const char8_t*>
|
||||
{
|
||||
/**
|
||||
* @brief Writes a c-style UTF-8 string into the @p writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The string to serialize
|
||||
* @param max_size The maximum expected length of the string, including the null terminator
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, const char8_t* value, uint32_t max_size) noexcept
|
||||
{
|
||||
uint32_t length = static_cast<uint32_t>(std::char_traits<char8_t>::length(value));
|
||||
|
||||
BS_ASSERT(length < max_size);
|
||||
|
||||
if (length == 0)
|
||||
return true;
|
||||
|
||||
uint32_t num_bits = utility::bits_to_represent(max_size);
|
||||
|
||||
BS_ASSERT(writer.serialize_bits(length, num_bits));
|
||||
|
||||
return writer.serialize_bytes(reinterpret_cast<const uint8_t*>(value), length * 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a c-style UTF-8 string from the @p reader into @p value
|
||||
* @param reader The stream to read from
|
||||
* @param value A pointer to the buffer that should be read into. The size of this buffer should be at least @p max_size
|
||||
* @param max_size The maximum expected length of the string, including the null terminator
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, char8_t* value, uint32_t max_size) noexcept
|
||||
{
|
||||
uint32_t num_bits = utility::bits_to_represent(max_size);
|
||||
|
||||
uint32_t length;
|
||||
BS_ASSERT(reader.serialize_bits(length, num_bits));
|
||||
|
||||
BS_ASSERT(length < max_size);
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
value[0] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
BS_ASSERT(reader.serialize_bytes(reinterpret_cast<uint8_t*>(value), length * 8));
|
||||
|
||||
value[length] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#pragma region std::basic_string
|
||||
/**
|
||||
* @brief A trait used to serialize any combination of std::basic_string
|
||||
* @tparam T The character type to use
|
||||
* @tparam Traits The trait type for the T type
|
||||
* @tparam Alloc The allocator to use
|
||||
*/
|
||||
template<typename T, typename Traits, typename Alloc>
|
||||
struct serialize_traits<std::basic_string<T, Traits, Alloc>>
|
||||
{
|
||||
/**
|
||||
* @brief Writes a string into the @p writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The string to serialize
|
||||
* @param max_size The maximum expected length of the string, excluding the null terminator
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, in<std::basic_string<T, Traits, Alloc>> value, uint32_t max_size) noexcept
|
||||
{
|
||||
uint32_t length = static_cast<uint32_t>(value.size());
|
||||
|
||||
BS_ASSERT(length <= max_size);
|
||||
|
||||
uint32_t num_bits = utility::bits_to_represent(max_size);
|
||||
|
||||
BS_ASSERT(writer.serialize_bits(length, num_bits));
|
||||
|
||||
if (length == 0)
|
||||
return true;
|
||||
|
||||
return writer.serialize_bytes(reinterpret_cast<const uint8_t*>(value.c_str()), length * sizeof(T) * 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a string from the @p reader into @p value
|
||||
* @param reader The stream to read from
|
||||
* @param value The string to read into. It will be resized if the read string won't fit
|
||||
* @param max_size The maximum expected length of the string, excluding the null terminator
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, out<std::basic_string<T, Traits, Alloc>> value, uint32_t max_size)
|
||||
{
|
||||
uint32_t num_bits = utility::bits_to_represent(max_size);
|
||||
|
||||
uint32_t length;
|
||||
BS_ASSERT(reader.serialize_bits(length, num_bits));
|
||||
|
||||
BS_ASSERT(length <= max_size);
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
value->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
value->resize(length);
|
||||
|
||||
BS_ASSERT(reader.serialize_bytes(reinterpret_cast<uint8_t*>(value->data()), length * sizeof(T) * 8));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A trait used to serialize any combination of std::basic_string with compiletime bounds
|
||||
* @tparam T The character type to use
|
||||
* @tparam Traits The trait type for the T type
|
||||
* @tparam Alloc The allocator to use
|
||||
* @tparam MaxSize The maximum expected length of the string, excluding the null terminator
|
||||
*/
|
||||
template<typename T, typename Traits, typename Alloc, size_t MaxSize>
|
||||
struct serialize_traits<bounded_string<std::basic_string<T, Traits, Alloc>, MaxSize>>
|
||||
{
|
||||
/**
|
||||
* @brief Writes a string into the @p writer
|
||||
* @param writer The stream to write to
|
||||
* @param value The string to serialize
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_writing_t<Stream>
|
||||
static serialize(Stream& writer, in<std::basic_string<T, Traits, Alloc>> value) noexcept
|
||||
{
|
||||
uint32_t length = static_cast<uint32_t>(value.size());
|
||||
|
||||
BS_ASSERT(length <= MaxSize);
|
||||
|
||||
constexpr uint32_t num_bits = utility::bits_to_represent(MaxSize);
|
||||
|
||||
BS_ASSERT(writer.serialize_bits(length, num_bits));
|
||||
|
||||
if (length == 0)
|
||||
return true;
|
||||
|
||||
return writer.serialize_bytes(reinterpret_cast<const uint8_t*>(value.c_str()), length * sizeof(T) * 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a string from the @p reader into @p value
|
||||
* @param reader The stream to read from
|
||||
* @param value The string to read into. It will be resized if the read string won't fit
|
||||
* @return Success
|
||||
*/
|
||||
template<typename Stream>
|
||||
typename utility::is_reading_t<Stream>
|
||||
static serialize(Stream& reader, out<std::basic_string<T, Traits, Alloc>> value)
|
||||
{
|
||||
constexpr uint32_t num_bits = utility::bits_to_represent(MaxSize);
|
||||
|
||||
uint32_t length;
|
||||
BS_ASSERT(reader.serialize_bits(length, num_bits));
|
||||
|
||||
BS_ASSERT(length <= MaxSize);
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
value->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
value->resize(length);
|
||||
|
||||
BS_ASSERT(reader.serialize_bytes(reinterpret_cast<uint8_t*>(value->data()), length * sizeof(T) * 8));
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#pragma endregion
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef BS_DEBUG_BREAK
|
||||
#if defined(_WIN32) // Windows
|
||||
#define BS_BREAKPOINT() __debugbreak()
|
||||
#elif defined(__linux__) // Linux
|
||||
#include <csignal>
|
||||
#define BS_BREAKPOINT() std::raise(SIGTRAP)
|
||||
#else // Non-supported
|
||||
#define BS_BREAKPOINT() throw
|
||||
#endif
|
||||
|
||||
#define BS_ASSERT(...) if (!(__VA_ARGS__)) { BS_BREAKPOINT(); return false; }
|
||||
#else // BS_DEBUG_BREAK
|
||||
#define BS_ASSERT(...) if (!(__VA_ARGS__)) { return false; }
|
||||
|
||||
#define BS_BREAKPOINT() throw
|
||||
#endif // BS_DEBUG_BREAK
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace bitstream::utility
|
||||
{
|
||||
constexpr inline uint32_t bits_to_represent(uintmax_t n)
|
||||
{
|
||||
uint32_t r = 0;
|
||||
|
||||
if (n >> 32) { r += 32U; n >>= 32U; }
|
||||
if (n >> 16) { r += 16U; n >>= 16U; }
|
||||
if (n >> 8) { r += 8U; n >>= 8U; }
|
||||
if (n >> 4) { r += 4U; n >>= 4U; }
|
||||
if (n >> 2) { r += 2U; n >>= 2U; }
|
||||
if (n >> 1) { r += 1U; n >>= 1U; }
|
||||
|
||||
return r + static_cast<uint32_t>(n);
|
||||
}
|
||||
|
||||
constexpr inline uint32_t bits_in_range(intmax_t min, intmax_t max)
|
||||
{
|
||||
return bits_to_represent(static_cast<uintmax_t>(max) - static_cast<uintmax_t>(min));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace bitstream::utility
|
||||
{
|
||||
inline constexpr auto CHECKSUM_TABLE = []()
|
||||
{
|
||||
constexpr uint32_t POLYNOMIAL = 0xEDB88320;
|
||||
|
||||
std::array<uint32_t, 0x100> table{};
|
||||
|
||||
for (uint32_t i = 0; i < 0x100; ++i)
|
||||
{
|
||||
uint32_t item = i;
|
||||
for (uint32_t bit = 0; bit < 8; ++bit)
|
||||
item = ((item & 1) != 0) ? (POLYNOMIAL ^ (item >> 1)) : (item >> 1);
|
||||
table[i] = item;
|
||||
}
|
||||
|
||||
return table;
|
||||
}();
|
||||
|
||||
inline constexpr uint32_t crc_uint32(const uint8_t* bytes, uint32_t size)
|
||||
{
|
||||
uint32_t result = 0xFFFFFFFF;
|
||||
|
||||
for (uint32_t i = 0; i < size; i++)
|
||||
result = CHECKSUM_TABLE[(result & 0xFF) ^ *(bytes + i)] ^ (result >> 8);
|
||||
|
||||
return ~result;
|
||||
}
|
||||
|
||||
inline constexpr uint32_t crc_uint32(const uint8_t* checksum, const uint8_t* bytes, uint32_t size)
|
||||
{
|
||||
uint32_t result = 0xFFFFFFFF;
|
||||
|
||||
for (uint32_t i = 0; i < 4; i++)
|
||||
result = CHECKSUM_TABLE[(result & 0xFF) ^ *(checksum + i)] ^ (result >> 8);
|
||||
|
||||
for (uint32_t i = 0; i < size; i++)
|
||||
result = CHECKSUM_TABLE[(result & 0xFF) ^ *(bytes + i)] ^ (result >> 8);
|
||||
|
||||
return ~result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(__cpp_lib_endian) && __cpp_lib_endian >= 201907L
|
||||
#include <bit>
|
||||
#else // __cpp_lib_endian
|
||||
#ifndef BS_LITTLE_ENDIAN
|
||||
// Detect with GCC 4.6's macro.
|
||||
#if defined(__BYTE_ORDER__)
|
||||
#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
#define BS_LITTLE_ENDIAN true
|
||||
#elif (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
#define BS_LITTLE_ENDIAN false
|
||||
#else
|
||||
#error "Unknown machine byteorder endianness detected. Need to manually define BS_LITTLE_ENDIAN."
|
||||
#endif
|
||||
// Detect with GLIBC's endian.h.
|
||||
#elif defined(__GLIBC__)
|
||||
#include <endian.h>
|
||||
#if (__BYTE_ORDER == __LITTLE_ENDIAN)
|
||||
#define BS_LITTLE_ENDIAN true
|
||||
#elif (__BYTE_ORDER == __BIG_ENDIAN)
|
||||
#define BS_LITTLE_ENDIAN false
|
||||
#else
|
||||
#error "Unknown machine byteorder endianness detected. Need to manually define BS_LITTLE_ENDIAN."
|
||||
#endif
|
||||
// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro.
|
||||
#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)
|
||||
#define BS_LITTLE_ENDIAN true
|
||||
#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)
|
||||
#define BS_LITTLE_ENDIAN false
|
||||
// Detect with architecture macros.
|
||||
#elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__)
|
||||
#define BS_LITTLE_ENDIAN false
|
||||
#elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__)
|
||||
#define BS_LITTLE_ENDIAN true
|
||||
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
|
||||
#define BS_LITTLE_ENDIAN true
|
||||
#else
|
||||
#error "Unknown machine byteorder endianness detected. Need to manually define BS_LITTLE_ENDIAN."
|
||||
#endif
|
||||
#endif // BS_LITTLE_ENDIAN
|
||||
#endif // __cpp_lib_endian
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace bitstream::utility
|
||||
{
|
||||
inline constexpr bool little_endian()
|
||||
{
|
||||
#ifdef BS_LITTLE_ENDIAN
|
||||
#if BS_LITTLE_ENDIAN
|
||||
return true;
|
||||
#else // BS_LITTLE_ENDIAN
|
||||
return false;
|
||||
#endif // BS_LITTLE_ENDIAN
|
||||
#else // defined(BS_LITTLE_ENDIAN)
|
||||
return std::endian::native == std::endian::little;
|
||||
#endif // defined(BS_LITTLE_ENDIAN)
|
||||
}
|
||||
|
||||
inline uint32_t endian_swap32(uint32_t value)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return _byteswap_ulong(value);
|
||||
#elif defined(__linux__)
|
||||
return __builtin_bswap32(value);
|
||||
#else
|
||||
const uint32_t first = (value << 24) & 0xFF000000;
|
||||
const uint32_t second = (value << 8) & 0x00FF0000;
|
||||
const uint32_t third = (value >> 8) & 0x0000FF00;
|
||||
const uint32_t fourth = (value >> 24) & 0x000000FF;
|
||||
|
||||
return first | second | third | fourth;
|
||||
#endif // _WIN32 || __linux__
|
||||
}
|
||||
|
||||
inline uint32_t to_big_endian32(uint32_t value)
|
||||
{
|
||||
if constexpr (little_endian())
|
||||
return endian_swap32(value);
|
||||
else
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include "../stream/serialize_traits.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace bitstream::utility
|
||||
{
|
||||
// Check if type has a serializable trait
|
||||
template<typename Void, typename T, typename Stream, typename... Args>
|
||||
struct has_serialize : std::false_type {};
|
||||
|
||||
template<typename T, typename Stream, typename... Args>
|
||||
struct has_serialize<std::void_t<decltype(serialize_traits<T>::serialize(std::declval<Stream&>(), std::declval<Args>()...))>, T, Stream, Args...> : std::true_type {};
|
||||
|
||||
template<typename T, typename Stream, typename... Args>
|
||||
using has_serialize_t = std::void_t<decltype(serialize_traits<T>::serialize(std::declval<Stream&>(), std::declval<Args>()...))>;
|
||||
|
||||
template<typename T, typename Stream, typename... Args>
|
||||
constexpr bool has_serialize_v = has_serialize<void, T, Stream, Args...>::value;
|
||||
|
||||
|
||||
// Check if stream is writing or reading
|
||||
template<typename T, typename R = bool>
|
||||
using is_writing_t = std::enable_if_t<T::writing, R>;
|
||||
|
||||
template<typename T, typename R = bool>
|
||||
using is_reading_t = std::enable_if_t<T::reading, R>;
|
||||
|
||||
|
||||
// Check if type is noexcept, if it exists
|
||||
template<typename Void, typename T, typename Stream, typename... Args>
|
||||
struct is_serialize_noexcept : std::false_type {};
|
||||
|
||||
template<typename T, typename Stream, typename... Args>
|
||||
struct is_serialize_noexcept<std::enable_if_t<has_serialize_v<T, Stream, Args...>>, T, Stream, Args...> :
|
||||
std::bool_constant<noexcept(serialize_traits<T>::serialize(std::declval<Stream&>(), std::declval<Args>()...))> {};
|
||||
|
||||
template<typename T, typename Stream, typename... Args>
|
||||
constexpr bool is_serialize_noexcept_v = is_serialize_noexcept<void, T, Stream, Args...>::value;
|
||||
|
||||
|
||||
// Get the underlying type without &, &&, * or const
|
||||
template<typename T>
|
||||
using base_t = typename std::remove_const_t<std::remove_pointer_t<std::decay_t<T>>>;
|
||||
|
||||
|
||||
// Meta functions for guessing the trait type from the first argument
|
||||
template<typename Void, typename Trait, typename Stream, typename... Args>
|
||||
struct deduce_trait
|
||||
{
|
||||
using type = Trait;
|
||||
};
|
||||
|
||||
// Non-const value
|
||||
template<typename Trait, typename Stream, typename... Args>
|
||||
struct deduce_trait<std::enable_if_t<
|
||||
!std::is_pointer_v<std::decay_t<Trait>> &&
|
||||
has_serialize_v<base_t<Trait>, Stream, Trait, Args...>>,
|
||||
Trait, Stream, Args...>
|
||||
{
|
||||
using type = base_t<Trait>;
|
||||
};
|
||||
|
||||
// Const value
|
||||
template<typename Trait, typename Stream, typename... Args>
|
||||
struct deduce_trait<std::enable_if_t<
|
||||
!std::is_pointer_v<std::decay_t<Trait>> &&
|
||||
has_serialize_v<std::add_const_t<base_t<Trait>>, Stream, Trait, Args...>>,
|
||||
Trait, Stream, Args...>
|
||||
{
|
||||
using type = std::add_const_t<base_t<Trait>>;
|
||||
};
|
||||
|
||||
// Non-const pointer
|
||||
template<typename Trait, typename Stream, typename... Args>
|
||||
struct deduce_trait<std::enable_if_t<
|
||||
std::is_pointer_v<std::decay_t<Trait>> &&
|
||||
has_serialize_v<std::add_pointer_t<base_t<Trait>>, Stream, Trait, Args...>>,
|
||||
Trait, Stream, Args...>
|
||||
{
|
||||
using type = std::add_pointer_t<base_t<Trait>>;
|
||||
};
|
||||
|
||||
// Const pointer
|
||||
template<typename Trait, typename Stream, typename... Args>
|
||||
struct deduce_trait<std::enable_if_t<
|
||||
std::is_pointer_v<std::decay_t<Trait>> &&
|
||||
has_serialize_v<std::add_pointer_t<std::add_const_t<base_t<Trait>>>, Stream, Trait, Args...>>,
|
||||
Trait, Stream, Args...>
|
||||
{
|
||||
using type = std::add_pointer_t<std::add_const_t<base_t<Trait>>>;
|
||||
};
|
||||
|
||||
template<typename Trait, typename Stream, typename... Args>
|
||||
using deduce_trait_t = typename deduce_trait<void, Trait, Stream, Args...>::type;
|
||||
|
||||
|
||||
// Shorthands for deduced type_traits
|
||||
template<typename Trait, typename Stream, typename... Args>
|
||||
using has_deduce_serialize_t = has_serialize_t<deduce_trait_t<Trait, Stream, Args...>, Stream, Trait, Args...>;
|
||||
|
||||
template<typename Trait, typename Stream, typename... Args>
|
||||
constexpr bool is_deduce_serialize_noexcept_v = is_serialize_noexcept_v<deduce_trait_t<Trait, Stream, Args...>, Stream, Trait, Args...>;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include "assert.h"
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef __cpp_constexpr_dynamic_alloc
|
||||
#define BS_CONSTEXPR constexpr
|
||||
#else // __cpp_constexpr_dynamic_alloc
|
||||
#define BS_CONSTEXPR
|
||||
#endif // __cpp_constexpr_dynamic_alloc
|
||||
|
||||
namespace bitstream
|
||||
{
|
||||
#ifdef BS_DEBUG_BREAK
|
||||
template<typename T>
|
||||
class out
|
||||
{
|
||||
public:
|
||||
BS_CONSTEXPR out(T& value) noexcept :
|
||||
m_Value(value),
|
||||
m_Constructed(false) {}
|
||||
|
||||
out(const out&) = delete;
|
||||
|
||||
out(out&&) = delete;
|
||||
|
||||
BS_CONSTEXPR ~out()
|
||||
{
|
||||
if (!m_Constructed)
|
||||
BS_BREAKPOINT();
|
||||
}
|
||||
|
||||
template<typename U, typename = std::enable_if_t<std::is_assignable_v<T&, U>>>
|
||||
BS_CONSTEXPR out& operator=(U&& arg) noexcept(std::is_nothrow_assignable_v<T&, U>)
|
||||
{
|
||||
m_Value = std::forward<U>(arg);
|
||||
|
||||
m_Constructed = true;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BS_CONSTEXPR T* operator->() noexcept
|
||||
{
|
||||
m_Constructed = true;
|
||||
return &m_Value;
|
||||
}
|
||||
|
||||
BS_CONSTEXPR T& operator*() noexcept
|
||||
{
|
||||
m_Constructed = true;
|
||||
return m_Value;
|
||||
}
|
||||
|
||||
private:
|
||||
T& m_Value;
|
||||
bool m_Constructed;
|
||||
};
|
||||
#else
|
||||
template<typename T>
|
||||
class out
|
||||
{
|
||||
public:
|
||||
BS_CONSTEXPR out(T& value) noexcept :
|
||||
m_Value(value) {}
|
||||
|
||||
out(const out&) = delete;
|
||||
|
||||
out(out&&) = delete;
|
||||
|
||||
template<typename U, typename = std::enable_if_t<std::is_assignable_v<T&, U>>>
|
||||
BS_CONSTEXPR out& operator=(U&& arg) noexcept(std::is_nothrow_assignable_v<T&, U>)
|
||||
{
|
||||
m_Value = std::forward<U>(arg);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
BS_CONSTEXPR T* operator->() noexcept { return &m_Value; }
|
||||
|
||||
BS_CONSTEXPR T& operator*() noexcept { return m_Value; }
|
||||
|
||||
private:
|
||||
T& m_Value;
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Passes by const or const reference depending on size
|
||||
*/
|
||||
template<typename T>
|
||||
using in = std::conditional_t<(sizeof(T) <= 16 && std::is_trivially_copy_constructible_v<T>), std::add_const_t<T>, std::add_lvalue_reference_t<std::add_const_t<T>>>;
|
||||
|
||||
/**
|
||||
* @brief Passes by reference
|
||||
*/
|
||||
template<typename Stream, typename T>
|
||||
using inout = std::conditional_t<Stream::writing, in<T>, std::add_lvalue_reference_t<T>>;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Test type
|
||||
*/
|
||||
template<typename Lambda>
|
||||
class finally
|
||||
{
|
||||
public:
|
||||
constexpr finally(Lambda func) noexcept :
|
||||
m_Lambda(func) {}
|
||||
|
||||
~finally()
|
||||
{
|
||||
m_Lambda();
|
||||
}
|
||||
|
||||
private:
|
||||
Lambda m_Lambda;
|
||||
};
|
||||
|
||||
template<typename Lambda>
|
||||
finally(Lambda func) -> finally<Lambda>;
|
||||
}
|
||||
Reference in New Issue
Block a user