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 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 = *(
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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.";
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue