mirror of https://github.com/SeanOMik/libki.git
util: Implement a common interface for bit streams
This commit is contained in:
parent
050a63152b
commit
4e3b3de391
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 = *(
|
||||||
|
|
|
@ -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>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,47 +96,68 @@ 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;
|
||||||
|
@ -171,4 +166,18 @@ namespace ki
|
||||||
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;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
*/
|
||||||
|
|
Loading…
Reference in New Issue