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

View File

@ -161,12 +161,12 @@ namespace pclass
> >
struct value_rw_helper 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); 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)); prop.get_type().read_from(stream, Value(prop.m_value));
} }
@ -187,12 +187,12 @@ namespace pclass
>::type >::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); 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)); prop.get_type().read_from(stream, Value(*prop.m_value));
} }
@ -215,12 +215,12 @@ namespace pclass
return value_object_helper<ValueT>::get_object(prop); 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); 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); value_rw_helper<ValueT>::read(prop, stream);
} }
@ -260,12 +260,12 @@ namespace pclass
return std::is_pointer<ValueT>::value; 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); 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); value_helper<ValueT>::read(*this, stream);
} }

View File

@ -38,17 +38,16 @@ namespace pclass
} }
template <class ClassT> template <class ClassT>
ClassType<ClassT> &define_class( ClassType<ClassT> &define_class(const std::string &name)
const std::string &name, const Type *base_class = nullptr)
{ {
// If the caller does not specify a base class, automatically make return define_class<ClassT>(name, nullptr);
// 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); template <class ClassT>
define_type(type); ClassType<ClassT> &define_class(
return *type; const std::string &name, const Type &base_class)
{
return define_class<ClassT>(name, &base_class);
} }
template <typename EnumT> template <typename EnumT>
@ -71,6 +70,20 @@ namespace pclass
protected: protected:
void define_type(Type *type); 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: private:
TypeList m_types; TypeList m_types;
TypeNameMap m_type_name_lookup; TypeNameMap m_type_name_lookup;

View File

@ -40,14 +40,14 @@ namespace pclass
return false; 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()) if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
this->get_type().write_to(stream, this->at(index)); 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()) if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
@ -73,14 +73,14 @@ namespace pclass
return true; 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()) if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
this->get_type().write_to(stream, *this->at(index)); 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()) if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");

View File

@ -45,7 +45,7 @@ namespace pclass
return new ClassT(*this, get_type_system()); 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 &object = dynamic_cast<const PropertyClass &>(value.get<ClassT>());
const auto &properties = object.get_properties(); const auto &properties = object.get_properties();
@ -53,7 +53,7 @@ namespace pclass
(*it)->write_value_to(stream); (*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 &object = dynamic_cast<PropertyClass &>(value.get<ClassT>());
auto &properties = object.get_properties(); auto &properties = object.get_properties();

View File

@ -12,7 +12,7 @@ namespace pclass
typename std::enable_if<std::is_floating_point<ValueT>::value>::type 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 // Reinterpret the reference as a reference to an integer
const uint_type &v = *( const uint_type &v = *(
@ -35,7 +35,7 @@ namespace pclass
typename std::enable_if<std::is_floating_point<ValueT>::value>::type 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 // Reinterpret the reference as a reference to an integer
uint_type &v = *( uint_type &v = *(

View File

@ -12,7 +12,7 @@ namespace pclass
typename std::enable_if<is_integral<ValueT>::value>::type 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); stream.write<ValueT>(value);
} }
@ -24,7 +24,7 @@ namespace pclass
typename std::enable_if<is_integral<ValueT>::value>::type 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>(); value = stream.read<ValueT>();
} }

View File

@ -11,7 +11,7 @@ namespace pclass
template <typename ValueT, typename Enable = void> template <typename ValueT, typename Enable = void>
struct PrimitiveTypeWriter 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 // Provide a compiler error if this is not specialized
static_assert( static_assert(
@ -27,7 +27,7 @@ namespace pclass
template <typename ValueT, typename Enable = void> template <typename ValueT, typename Enable = void>
struct PrimitiveTypeReader 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 // Provide a compiler error if this is not specialized
static_assert( static_assert(
@ -50,14 +50,14 @@ namespace pclass
m_kind = kind::PRIMITIVE; 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>()) if (!value.is<ValueT>())
throw std::runtime_error("Invalid call to Type::write_to -- value type does not match 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>()); 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>()) if (!value.is<ValueT>())
throw std::runtime_error("Invalid call to Type::read_from -- value type does not match 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>; using type = std::basic_string<_Elem, _Traits, _Alloc>;
public: 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 // Write the length as an unsigned short
stream.write<uint16_t>(value.length()); stream.write<uint16_t>(value.length());
@ -38,7 +38,7 @@ namespace pclass
using type = std::basic_string<_Elem, _Traits, _Alloc>; using type = std::basic_string<_Elem, _Traits, _Alloc>;
public: 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 // Read the length and create a new string with the correct capacity
auto length = stream.read<uint16_t>(); auto length = stream.read<uint16_t>();

View File

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

View File

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

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <type_traits> #include <type_traits>
#include <sstream>
#include "ki/util/BitTypes.h" #include "ki/util/BitTypes.h"
#define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000 #define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000
@ -8,9 +9,10 @@
namespace ki 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: public:
/** /**
@ -45,33 +47,33 @@ namespace ki
void set_bit(int bit); void set_bit(int bit);
}; };
explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE); virtual ~BitStreamBase() {}
~BitStream();
/** /**
* @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. * Sets the position of the stream.
* @param position The new 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. * 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 < template <
typename IntegerT, typename IntegerT,
@ -79,42 +81,14 @@ namespace ki
> >
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value) IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value)
{ {
IntegerT value = 0; return static_cast<IntegerT>(read(bits));
// 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;
} }
/** /**
* Writes a value to the buffer that occupies a defined number of bits. * Writes a value to the buffer that occupies a defined number of bits.
* @param value The value to write. * @tparam IntegerT The type of value (must be an integral type).
* @param bits The number of bits to use. * @param[in] value The value to write.
* @param[in] bits The number of bits to use. Defaults to the bitsize of IntegerT.
*/ */
template < template <
typename IntegerT, typename IntegerT,
@ -122,53 +96,88 @@ namespace ki
> >
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value) void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value)
{ {
// Iterate until we've written all of the bits write(static_cast<uint64_t>(value), 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);
}
} }
/** /**
* Copy memory from an external buffer into the bitstream's buffer from the current position. * Copy memory from an external buffer into the bitstream's buffer from the current position.
* @param src The buffer to copy data from. * @param[in] src The buffer to copy data from.
* @param bitsize The number of bits to copy from the src buffer. * @param[in] bitsize The number of bits to copy from the src buffer.
*/ */
void write_copy(uint8_t *src, std::size_t bitsize); void write_copy(uint8_t *src, std::size_t bitsize);
/** /**
* Copy memory from the bitstream's buffer into an external buffer. * Copy memory from the bitstream's buffer into an external buffer.
* @param dst The destination buffer to copy data to. * @param[out] dst The destination buffer to copy data to.
* @param bitsize The number of bits to copy into the dst buffer. * @param[in] bitsize The number of bits to copy into the dst buffer.
*/ */
void read_copy(uint8_t *dst, std::size_t bitsize); 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: private:
uint8_t *m_buffer; uint8_t * m_buffer;
std::size_t m_buffer_size; std::size_t m_buffer_size;
stream_pos m_position; stream_pos m_position;
void expand_buffer(); void expand_buffer();
void validate_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."); 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 // The caller must specify an index
throw runtime_error("Called write_value_to() on a dynamic property. Use write_value_to(index) instead."); 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 // The caller must specify an index
throw runtime_error("Called read_value_from() on a dynamic property. Use read_value_from(index) instead."); 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; 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; std::ostringstream oss;
oss << "Type '" << m_name << "' does not implement Type::write_to."; oss << "Type '" << m_name << "' does not implement Type::write_to.";
throw runtime_error(oss.str()); 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; std::ostringstream oss;
oss << "Type '" << m_name << "' does not implement Type::read_from."; oss << "Type '" << m_name << "' does not implement Type::read_from.";

View File

@ -15,7 +15,7 @@ namespace serialization
m_root_object = nullptr; 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 // Write the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_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 we have an object, write the type hash, otherwise, write NULL (0).
if (object) if (object)
@ -118,7 +118,7 @@ namespace serialization
stream.write<uint32_t>(NULL); 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 // Write any object headers
presave_object(object, stream); 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 // Remember where we started writing the property data
const auto start_pos = stream.tell(); 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 // Read the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_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/BitStream.h"
#include "ki/util/exception.h"
#include <limits> #include <limits>
#include <exception>
#include <cstring> #include <cstring>
#include <stdexcept>
#include <cmath> #include <cmath>
namespace ki namespace ki
@ -137,6 +136,40 @@ namespace ki
return copy; 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) BitStream::BitStream(const size_t buffer_size)
{ {
m_buffer = new uint8_t[buffer_size] { 0 }; m_buffer = new uint8_t[buffer_size] { 0 };
@ -170,38 +203,75 @@ namespace ki
return m_buffer; 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 // Do we have these bits available to read?
const auto bytes = bitsize / 8; if ((m_position + bits).as_bits() > m_buffer_size * 8)
auto written_bytes = 0;
while (written_bytes < bytes)
{ {
write<uint8_t>(src[written_bytes]); std::ostringstream oss;
written_bytes++; oss << "Not enough data in buffer to read "
<< static_cast<uint16_t>(bits) << "bits.";
throw runtime_error(oss.str());
} }
// Copy left over bits // Iterate until we've read all of the bits
const auto bits = bitsize % 8; uint64_t value = 0;
if (bits > 0) auto unread_bits = bits;
write<uint8_t>(src[bytes + 1], 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) return value;
{
// 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 void BitStream::write(const uint64_t value, const uint8_t bits)
const auto bits = bitsize % 8; {
if (bits > 0) // Iterate until we've written all of the bits
dst[bytes + 1] = read<uint8_t>(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() void BitStream::expand_buffer()
@ -216,7 +286,7 @@ namespace ki
// Has the buffer reached maximum size? // Has the buffer reached maximum size?
if (new_size == m_buffer_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 // Allocate a new buffer, copy everything over, and then delete the old buffer
auto *new_buffer = new uint8_t[new_size] { 0 }; auto *new_buffer = new uint8_t[new_size] { 0 };
@ -230,7 +300,7 @@ namespace ki
{ {
// Make sure we haven't underflowed // Make sure we haven't underflowed
if (m_position.get_byte() < 0) 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 // Expand the buffer if we've overflowed
if (m_position.get_byte() >= m_buffer_size) if (m_position.get_byte() >= m_buffer_size)

View File

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