util: Implement a common interface for bit streams

This commit is contained in:
Joshua Scott 2018-11-18 17:10:21 +00:00
parent 050a63152b
commit 4e3b3de391
17 changed files with 260 additions and 162 deletions

View File

@ -28,8 +28,8 @@ namespace pclass
virtual Value get_value() const = 0;
virtual const PropertyClass *get_object() const = 0;
virtual void write_value_to(BitStream &stream) const = 0;
virtual void read_value_from(BitStream &stream) = 0;
virtual void write_value_to(BitStreamBase &stream) const = 0;
virtual void read_value_from(BitStreamBase &stream) = 0;
private:
std::string m_name;
@ -53,13 +53,13 @@ namespace pclass
Value get_value() const final override;
const PropertyClass *get_object() const final override;
void write_value_to(BitStream &stream) const final override;
void read_value_from(BitStream &stream) final override;
void write_value_to(BitStreamBase &stream) const final override;
void read_value_from(BitStreamBase &stream) final override;
virtual Value get_value(int index) const = 0;
virtual const PropertyClass *get_object(int index) const = 0;
virtual void write_value_to(BitStream &stream, int index) const = 0;
virtual void read_value_from(BitStream &stream, int index) = 0;
virtual void write_value_to(BitStreamBase &stream, int index) const = 0;
virtual void read_value_from(BitStreamBase &stream, int index) = 0;
};
}
}

View File

@ -161,12 +161,12 @@ namespace pclass
>
struct value_rw_helper
{
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
{
prop.get_type().write_to(stream, prop.m_value);
}
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
{
prop.get_type().read_from(stream, Value(prop.m_value));
}
@ -187,12 +187,12 @@ namespace pclass
>::type
>
{
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
{
prop.get_type().write_to(stream, *prop.m_value);
}
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
{
prop.get_type().read_from(stream, Value(*prop.m_value));
}
@ -215,12 +215,12 @@ namespace pclass
return value_object_helper<ValueT>::get_object(prop);
}
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
{
value_rw_helper<ValueT>::write(prop, stream);
}
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
{
value_rw_helper<ValueT>::read(prop, stream);
}
@ -260,12 +260,12 @@ namespace pclass
return std::is_pointer<ValueT>::value;
}
void write_value_to(BitStream &stream) const override
void write_value_to(BitStreamBase &stream) const override
{
value_helper<ValueT>::write(*this, stream);
}
void read_value_from(BitStream &stream) override
void read_value_from(BitStreamBase &stream) override
{
value_helper<ValueT>::read(*this, stream);
}

View File

@ -38,17 +38,16 @@ namespace pclass
}
template <class ClassT>
ClassType<ClassT> &define_class(
const std::string &name, const Type *base_class = nullptr)
ClassType<ClassT> &define_class(const std::string &name)
{
// If the caller does not specify a base class, automatically make
// ki::pclass::PropertyClass the base class (if it has been defined)
if (base_class == nullptr && has_type("class PropertyClass"))
base_class = &get_type("class PropertyClass");
return define_class<ClassT>(name, nullptr);
}
auto *type = new ClassType<ClassT>(name, base_class, *this);
define_type(type);
return *type;
template <class ClassT>
ClassType<ClassT> &define_class(
const std::string &name, const Type &base_class)
{
return define_class<ClassT>(name, &base_class);
}
template <typename EnumT>
@ -71,6 +70,20 @@ namespace pclass
protected:
void define_type(Type *type);
template <class ClassT>
ClassType<ClassT> &define_class(
const std::string &name, const Type *base_class)
{
// If the caller does not specify a base class, automatically make
// ki::pclass::PropertyClass the base class (if it has been defined)
if (base_class == nullptr && has_type("class PropertyClass"))
base_class = &get_type("class PropertyClass");
auto *type = new ClassType<ClassT>(name, base_class, *this);
define_type(type);
return *type;
}
private:
TypeList m_types;
TypeNameMap m_type_name_lookup;

View File

@ -40,14 +40,14 @@ namespace pclass
return false;
}
void write_value_to(BitStream &stream, const int index) const override
void write_value_to(BitStreamBase &stream, const int index) const override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
this->get_type().write_to(stream, this->at(index));
}
void read_value_from(BitStream &stream, const int index) override
void read_value_from(BitStreamBase &stream, const int index) override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
@ -73,14 +73,14 @@ namespace pclass
return true;
}
void write_value_to(BitStream &stream, const int index) const override
void write_value_to(BitStreamBase &stream, const int index) const override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
this->get_type().write_to(stream, *this->at(index));
}
void read_value_from(BitStream &stream, const int index) override
void read_value_from(BitStreamBase &stream, const int index) override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");

View File

@ -45,7 +45,7 @@ namespace pclass
return new ClassT(*this, get_type_system());
}
void write_to(BitStream &stream, const Value &value) const override
void write_to(BitStreamBase &stream, const Value &value) const override
{
const auto &object = dynamic_cast<const PropertyClass &>(value.get<ClassT>());
const auto &properties = object.get_properties();
@ -53,7 +53,7 @@ namespace pclass
(*it)->write_value_to(stream);
}
void read_from(BitStream &stream, Value &value) const override
void read_from(BitStreamBase &stream, Value &value) const override
{
auto &object = dynamic_cast<PropertyClass &>(value.get<ClassT>());
auto &properties = object.get_properties();

View File

@ -12,7 +12,7 @@ namespace pclass
typename std::enable_if<std::is_floating_point<ValueT>::value>::type
>
{
static void write_to(BitStream &stream, const ValueT &value)
static void write_to(BitStreamBase &stream, const ValueT &value)
{
// Reinterpret the reference as a reference to an integer
const uint_type &v = *(
@ -35,7 +35,7 @@ namespace pclass
typename std::enable_if<std::is_floating_point<ValueT>::value>::type
>
{
static void read_from(BitStream &stream, ValueT &value)
static void read_from(BitStreamBase &stream, ValueT &value)
{
// Reinterpret the reference as a reference to an integer
uint_type &v = *(

View File

@ -12,7 +12,7 @@ namespace pclass
typename std::enable_if<is_integral<ValueT>::value>::type
>
{
static void write_to(BitStream &stream, const ValueT &value)
static void write_to(BitStreamBase &stream, const ValueT &value)
{
stream.write<ValueT>(value);
}
@ -24,7 +24,7 @@ namespace pclass
typename std::enable_if<is_integral<ValueT>::value>::type
>
{
static void read_from(BitStream &stream, ValueT &value)
static void read_from(BitStreamBase &stream, ValueT &value)
{
value = stream.read<ValueT>();
}

View File

@ -11,7 +11,7 @@ namespace pclass
template <typename ValueT, typename Enable = void>
struct PrimitiveTypeWriter
{
static void write_to(BitStream &stream, const ValueT &value)
static void write_to(BitStreamBase &stream, const ValueT &value)
{
// Provide a compiler error if this is not specialized
static_assert(
@ -27,7 +27,7 @@ namespace pclass
template <typename ValueT, typename Enable = void>
struct PrimitiveTypeReader
{
static void read_from(BitStream &stream, ValueT &value)
static void read_from(BitStreamBase &stream, ValueT &value)
{
// Provide a compiler error if this is not specialized
static_assert(
@ -50,14 +50,14 @@ namespace pclass
m_kind = kind::PRIMITIVE;
}
void write_to(BitStream &stream, const Value &value) const override
void write_to(BitStreamBase &stream, const Value &value) const override
{
if (!value.is<ValueT>())
throw std::runtime_error("Invalid call to Type::write_to -- value type does not match ValueT.");
PrimitiveTypeWriter<ValueT>::write_to(stream, value.get<ValueT>());
}
void read_from(BitStream &stream, Value &value) const override
void read_from(BitStreamBase &stream, Value &value) const override
{
if (!value.is<ValueT>())
throw std::runtime_error("Invalid call to Type::read_from -- value type does not match ValueT.");

View File

@ -16,7 +16,7 @@ namespace pclass
using type = std::basic_string<_Elem, _Traits, _Alloc>;
public:
static void write_to(BitStream &stream, const type &value)
static void write_to(BitStreamBase &stream, const type &value)
{
// Write the length as an unsigned short
stream.write<uint16_t>(value.length());
@ -38,7 +38,7 @@ namespace pclass
using type = std::basic_string<_Elem, _Traits, _Alloc>;
public:
static void read_from(BitStream &stream, type &value)
static void read_from(BitStreamBase &stream, type &value)
{
// Read the length and create a new string with the correct capacity
auto length = stream.read<uint16_t>();

View File

@ -50,8 +50,8 @@ namespace pclass
const TypeSystem &get_type_system() const;
virtual PropertyClass *instantiate() const;
virtual void write_to(BitStream &stream, const Value &value) const;
virtual void read_from(BitStream &stream, Value &value) const;
virtual void write_to(BitStreamBase &stream, const Value &value) const;
virtual void read_from(BitStreamBase &stream, Value &value) const;
protected:
kind m_kind;

View File

@ -45,17 +45,17 @@ namespace serialization
bool is_file, flags flags);
virtual ~SerializerBinary() {}
void save(const pclass::PropertyClass *object, BitStream &stream);
void load(pclass::PropertyClass *&dest, BitStream &stream);
void save(const pclass::PropertyClass *object, BitStreamBase &stream);
void load(pclass::PropertyClass *&dest, BitStreamBase &stream);
protected:
virtual void presave_object(const pclass::PropertyClass *object, BitStream &stream) const;
void save_object(const pclass::PropertyClass *object, BitStream &stream) const;
void save_property(const pclass::PropertyBase *prop, BitStream &stream) const;
virtual void presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const;
void save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const;
void save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const;
virtual void preload_object(pclass::PropertyClass *&dest, BitStream &stream) const;
void load_object(pclass::PropertyClass *&dest, BitStream &stream) const;
void load_property(pclass::PropertyBase *prop, BitStream &stream) const;
virtual void preload_object(pclass::PropertyClass *&dest, BitStreamBase &stream) const;
void load_object(pclass::PropertyClass *&dest, BitStreamBase &stream) const;
void load_property(pclass::PropertyBase *prop, BitStreamBase &stream) const;
private:
const pclass::TypeSystem *m_type_system;

View File

@ -1,6 +1,7 @@
#pragma once
#include <cstdint>
#include <type_traits>
#include <sstream>
#include "ki/util/BitTypes.h"
#define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000
@ -8,9 +9,10 @@
namespace ki
{
/**
* A readable/writeable stream of bits.
* An abstract base class that provides a common interface for
* writing to, reading from, and querying bit streams.
*/
class BitStream
class BitStreamBase
{
public:
/**
@ -45,33 +47,33 @@ namespace ki
void set_bit(int bit);
};
explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE);
~BitStream();
virtual ~BitStreamBase() {}
/**
* @return The stream's current position.
* @returns The stream's current position.
*/
stream_pos tell() const;
virtual stream_pos tell() const = 0;
/**
* Sets the position of the stream.
* @param position The new position of the stream.
*/
void seek(stream_pos position);
virtual void seek(stream_pos position) = 0;
/**
* @return The current size of the internal buffer.
* @returns The current size of the internal buffer.
*/
std::size_t capacity() const;
virtual std::size_t capacity() const = 0;
/**
* @return A pointer to the start of the internal buffer.
* @returns A pointer to the start of the internal buffer.
*/
const uint8_t *data() const;
virtual const uint8_t *data() const = 0;
/**
* Reads a value from the buffer given a defined number of bits.
* @param bits The number of bits to read.
* @param[in] bits The number of bits to read. Defaults to the bitsize of IntegerT.
* @returns The value read from the buffer.
*/
template <
typename IntegerT,
@ -79,42 +81,14 @@ namespace ki
>
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value)
{
IntegerT value = 0;
// Iterate until we've read all of the bits
auto unread_bits = bits;
while (unread_bits > 0)
{
// Calculate how many bits to read from the current byte based on how many bits
// are left and how many bits we still need to read
const uint8_t bits_available = (8 - m_position.get_bit());
const auto bit_count = unread_bits < bits_available ? unread_bits : bits_available;
// Find the bit-mask based on how many bits are being read
const uint8_t bit_mask = ((1 << bit_count) - 1) << m_position.get_bit();
// Read the bits from the current byte and position them on the least-signficant bit
const uint8_t bits_value = (m_buffer[m_position.get_byte()] & bit_mask) >> m_position.get_bit();
// Position the value of the bits we just read based on how many bits of the value
// we've already read
const uint8_t read_bits = bits - unread_bits;
value |= (IntegerT)bits_value << read_bits;
// Remove the bits we just read from the count of unread bits
unread_bits -= bit_count;
// Move forward the number of bits we just read
seek(tell() + bit_count);
}
return value;
return static_cast<IntegerT>(read(bits));
}
/**
* Writes a value to the buffer that occupies a defined number of bits.
* @param value The value to write.
* @param bits The number of bits to use.
* @tparam IntegerT The type of value (must be an integral type).
* @param[in] value The value to write.
* @param[in] bits The number of bits to use. Defaults to the bitsize of IntegerT.
*/
template <
typename IntegerT,
@ -122,47 +96,68 @@ namespace ki
>
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value)
{
// Iterate until we've written all of the bits
auto unwritten_bits = bits;
while (unwritten_bits > 0)
{
// Calculate how many bits to write based on how many bits are left in the current byte
// and how many bits from the value we still need to write
const uint8_t bits_available = (8 - m_position.get_bit());
const auto bit_count = unwritten_bits < bits_available ? unwritten_bits : bits_available;
// Find the bit-mask based on how many bits are being written, and how many bits we've
// already written
const uint8_t written_bits = bits - unwritten_bits;
IntegerT bit_mask = (IntegerT)((1 << bit_count) - 1) << written_bits;
// Get the bits from the value and position them at the current bit position
uint8_t value_byte = ((value & bit_mask) >> written_bits) & 0xFF;
value_byte <<= m_position.get_bit();
// Write the bits into the byte we're currently at
m_buffer[m_position.get_byte()] |= value_byte;
unwritten_bits -= bit_count;
// Move forward the number of bits we just wrote
seek(tell() + bit_count);
}
write(static_cast<uint64_t>(value), bits);
}
/**
* Copy memory from an external buffer into the bitstream's buffer from the current position.
* @param src The buffer to copy data from.
* @param bitsize The number of bits to copy from the src buffer.
* @param[in] src The buffer to copy data from.
* @param[in] bitsize The number of bits to copy from the src buffer.
*/
void write_copy(uint8_t *src, std::size_t bitsize);
/**
* Copy memory from the bitstream's buffer into an external buffer.
* @param dst The destination buffer to copy data to.
* @param bitsize The number of bits to copy into the dst buffer.
* @param[out] dst The destination buffer to copy data to.
* @param[in] bitsize The number of bits to copy into the dst buffer.
*/
void read_copy(uint8_t *dst, std::size_t bitsize);
protected:
virtual uint64_t read(uint8_t bits) = 0;
virtual void write(uint64_t value, uint8_t bits) = 0;
};
/**
* A read/write-able stream of bits.
*/
class BitStream : public BitStreamBase
{
public:
explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE);
virtual ~BitStream();
stream_pos tell() const override;
void seek(stream_pos position) override;
std::size_t capacity() const override;
const uint8_t *data() const override;
/**
* @copydoc BitStreamBase::read<IntegerT>(uint8_t)
* @throws ki::runtime_error Not enough data available to read the specified number of bits.
*/
template <
typename IntegerT,
typename = std::enable_if<is_integral<IntegerT>::value>
>
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value)
{
return BitStreamBase::read<IntegerT>(bits);
}
template <
typename IntegerT,
typename = std::enable_if<is_integral<IntegerT>::value>
>
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value)
{
BitStreamBase::write<IntegerT>(value, bits);
}
protected:
uint64_t read(uint8_t bits) override;
void write(uint64_t value, uint8_t bits) override;
private:
uint8_t * m_buffer;
std::size_t m_buffer_size;
@ -171,4 +166,18 @@ namespace ki
void expand_buffer();
void validate_buffer();
};
/**
* TODO: Documentation
*/
class BitStreamSection : public BitStreamBase
{
public:
explicit BitStreamSection(BitStreamBase &stream, std::size_t size);
private:
BitStreamBase &m_stream;
stream_pos m_position;
std::size_t m_size;
};
}

View File

@ -73,13 +73,13 @@ namespace pclass
throw runtime_error("Called get_object() on a dynamic property. Use get_object(index) instead.");
}
void DynamicPropertyBase::write_value_to(BitStream &stream) const
void DynamicPropertyBase::write_value_to(BitStreamBase &stream) const
{
// The caller must specify an index
throw runtime_error("Called write_value_to() on a dynamic property. Use write_value_to(index) instead.");
}
void DynamicPropertyBase::read_value_from(BitStream &stream)
void DynamicPropertyBase::read_value_from(BitStreamBase &stream)
{
// The caller must specify an index
throw runtime_error("Called read_value_from() on a dynamic property. Use read_value_from(index) instead.");

View File

@ -38,14 +38,14 @@ namespace pclass
return m_type_system;
}
void Type::write_to(BitStream& stream, const Value& value) const
void Type::write_to(BitStreamBase &stream, const Value& value) const
{
std::ostringstream oss;
oss << "Type '" << m_name << "' does not implement Type::write_to.";
throw runtime_error(oss.str());
}
void Type::read_from(BitStream& stream, Value& value) const
void Type::read_from(BitStreamBase &stream, Value& value) const
{
std::ostringstream oss;
oss << "Type '" << m_name << "' does not implement Type::read_from.";

View File

@ -15,7 +15,7 @@ namespace serialization
m_root_object = nullptr;
}
void SerializerBinary::save(const pclass::PropertyClass* object, BitStream& stream)
void SerializerBinary::save(const pclass::PropertyClass* object, BitStreamBase &stream)
{
// Write the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS))
@ -109,7 +109,7 @@ namespace serialization
}
}
void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStream& stream) const
void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const
{
// If we have an object, write the type hash, otherwise, write NULL (0).
if (object)
@ -118,7 +118,7 @@ namespace serialization
stream.write<uint32_t>(NULL);
}
void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStream& stream) const
void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const
{
// Write any object headers
presave_object(object, stream);
@ -156,7 +156,7 @@ namespace serialization
}
}
void SerializerBinary::save_property(const pclass::PropertyBase *prop, BitStream& stream) const
void SerializerBinary::save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const
{
// Remember where we started writing the property data
const auto start_pos = stream.tell();
@ -227,7 +227,7 @@ namespace serialization
}
}
void SerializerBinary::load(pclass::PropertyClass*& dest, BitStream& stream)
void SerializerBinary::load(pclass::PropertyClass*& dest, BitStreamBase &stream)
{
// Read the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS))
@ -240,17 +240,17 @@ namespace serialization
}
}
void SerializerBinary::preload_object(pclass::PropertyClass*& dest, BitStream& stream) const
void SerializerBinary::preload_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const
{
}
void SerializerBinary::load_object(pclass::PropertyClass*& dest, BitStream& stream) const
void SerializerBinary::load_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const
{
}
void SerializerBinary::load_property(pclass::PropertyBase* prop, BitStream& stream) const
void SerializerBinary::load_property(pclass::PropertyBase* prop, BitStreamBase &stream) const
{
}

View File

@ -1,8 +1,7 @@
#include "ki/util/BitStream.h"
#include "ki/util/exception.h"
#include <limits>
#include <exception>
#include <cstring>
#include <stdexcept>
#include <cmath>
namespace ki
@ -137,6 +136,40 @@ namespace ki
return copy;
}
void BitStreamBase::write_copy(uint8_t *src, const std::size_t bitsize)
{
// Copy all whole bytes
const auto bytes = bitsize / 8;
auto written_bytes = 0;
while (written_bytes < bytes)
{
write<uint8_t>(src[written_bytes]);
written_bytes++;
}
// Copy left over bits
const auto bits = bitsize % 8;
if (bits > 0)
write<uint8_t>(src[bytes + 1], bits);
}
void BitStreamBase::read_copy(uint8_t *dst, const std::size_t bitsize)
{
// Copy all whole bytes
const auto bytes = bitsize / 8;
auto read_bytes = 0;
while (read_bytes < bytes)
{
dst[read_bytes] = read<uint8_t>();
read_bytes++;
}
// Copy left over bits
const auto bits = bitsize % 8;
if (bits > 0)
dst[bytes + 1] = read<uint8_t>(bits);
}
BitStream::BitStream(const size_t buffer_size)
{
m_buffer = new uint8_t[buffer_size] { 0 };
@ -170,38 +203,75 @@ namespace ki
return m_buffer;
}
void BitStream::write_copy(uint8_t *src, const std::size_t bitsize)
uint64_t BitStream::read(const uint8_t bits)
{
// Copy all whole bytes
const auto bytes = bitsize / 8;
auto written_bytes = 0;
while (written_bytes < bytes)
// Do we have these bits available to read?
if ((m_position + bits).as_bits() > m_buffer_size * 8)
{
write<uint8_t>(src[written_bytes]);
written_bytes++;
std::ostringstream oss;
oss << "Not enough data in buffer to read "
<< static_cast<uint16_t>(bits) << "bits.";
throw runtime_error(oss.str());
}
// Copy left over bits
const auto bits = bitsize % 8;
if (bits > 0)
write<uint8_t>(src[bytes + 1], bits);
// Iterate until we've read all of the bits
uint64_t value = 0;
auto unread_bits = bits;
while (unread_bits > 0)
{
// Calculate how many bits to read from the current byte based on how many bits
// are left and how many bits we still need to read
const uint8_t bits_available = (8 - m_position.get_bit());
const auto bit_count = unread_bits < bits_available ? unread_bits : bits_available;
// Find the bit-mask based on how many bits are being read
const uint8_t bit_mask = (1 << bit_count) - 1 << m_position.get_bit();
// Read the bits from the current byte and position them on the least-signficant bit
const uint8_t bits_value = (m_buffer[m_position.get_byte()] & bit_mask) >> m_position.get_bit();
// Position the value of the bits we just read based on how many bits of the value
// we've already read
const uint8_t read_bits = bits - unread_bits;
value |= static_cast<uint64_t>(bits_value) << read_bits;
// Remove the bits we just read from the count of unread bits
unread_bits -= bit_count;
// Move forward the number of bits we just read
seek(tell() + bit_count);
}
void BitStream::read_copy(uint8_t *dst, const std::size_t bitsize)
{
// Copy all whole bytes
const auto bytes = bitsize / 8;
auto read_bytes = 0;
while (read_bytes < bytes)
{
dst[read_bytes] = read<uint8_t>();
read_bytes++;
return value;
}
// Copy left over bits
const auto bits = bitsize % 8;
if (bits > 0)
dst[bytes + 1] = read<uint8_t>(bits);
void BitStream::write(const uint64_t value, const uint8_t bits)
{
// Iterate until we've written all of the bits
auto unwritten_bits = bits;
while (unwritten_bits > 0)
{
// Calculate how many bits to write based on how many bits are left in the current byte
// and how many bits from the value we still need to write
const uint8_t bits_available = (8 - m_position.get_bit());
const auto bit_count = unwritten_bits < bits_available ? unwritten_bits : bits_available;
// Find the bit-mask based on how many bits are being written, and how many bits we've
// already written
const uint8_t written_bits = bits - unwritten_bits;
const auto bit_mask = static_cast<uint64_t>((1 << bit_count) - 1) << written_bits;
// Get the bits from the value and position them at the current bit position
uint8_t value_byte = ((value & bit_mask) >> written_bits) & 0xFF;
value_byte <<= m_position.get_bit();
// Write the bits into the byte we're currently at
m_buffer[m_position.get_byte()] |= value_byte;
unwritten_bits -= bit_count;
// Move forward the number of bits we just wrote
seek(tell() + bit_count);
}
}
void BitStream::expand_buffer()
@ -216,7 +286,7 @@ namespace ki
// Has the buffer reached maximum size?
if (new_size == m_buffer_size)
throw std::runtime_error("Buffer cannot be expanded as it has reached maximum size.");
throw runtime_error("Buffer cannot be expanded as it has reached maximum size.");
// Allocate a new buffer, copy everything over, and then delete the old buffer
auto *new_buffer = new uint8_t[new_size] { 0 };
@ -230,7 +300,7 @@ namespace ki
{
// Make sure we haven't underflowed
if (m_position.get_byte() < 0)
throw std::runtime_error("Position of buffer is less than 0!");
throw runtime_error("Position of buffer is less than 0!");
// Expand the buffer if we've overflowed
if (m_position.get_byte() >= m_buffer_size)

View File

@ -254,3 +254,9 @@ TEST_CASE("BitStream buffer expansion", "[bit-stream]")
delete bit_stream;
SUCCEED();
}
/**
* TODO: Test reading outside of buffer (should throw)
* TODO: Test read/write copy on BitStream
* TODO: Test BitStreamSection
*/