etc: Implement deserialization and object copying

Also make BitStream tests use REQUIRE instead of SUCCESS and FAIL for better output.
This commit is contained in:
Joshua Scott 2018-11-27 11:51:56 +00:00
parent ad448befe6
commit 3a22c992b5
21 changed files with 1563 additions and 605 deletions

View File

@ -13,9 +13,16 @@ namespace pclass
class PropertyBase
{
public:
// Do not allow copy assignment. Once a property has been constructed,
// it shouldn't be able to change.
PropertyBase & operator=(const PropertyBase &that) = delete;
PropertyBase(PropertyClass &object,
const std::string &name, const Type &type);
virtual ~PropertyBase() { }
virtual ~PropertyBase() {}
PropertyBase(PropertyClass &object,
const PropertyBase &that);
std::string get_name() const;
hash_t get_name_hash() const;
@ -27,9 +34,10 @@ namespace pclass
virtual Value get_value() const = 0;
virtual const PropertyClass *get_object() const = 0;
virtual void set_object(PropertyClass *object) = 0;
virtual void write_value_to(BitStreamBase &stream) const = 0;
virtual void read_value_from(BitStreamBase &stream) = 0;
virtual void write_value_to(BitStream &stream) const = 0;
virtual void read_value_from(BitStream &stream) = 0;
private:
std::string m_name;
@ -44,22 +52,32 @@ namespace pclass
class DynamicPropertyBase : public PropertyBase
{
public:
// Do not allow copy assignment. Once a property has been constructed,
// it shouldn't be able to change.
DynamicPropertyBase & operator=(const DynamicPropertyBase &that) = delete;
DynamicPropertyBase(PropertyClass &object,
const std::string &name, const Type &type);
virtual ~DynamicPropertyBase() {}
DynamicPropertyBase(PropertyClass &object,
const DynamicPropertyBase &that);
bool is_dynamic() const override;
virtual std::size_t get_element_count() const = 0;
virtual void set_element_count(std::size_t size) = 0;
Value get_value() const final override;
const PropertyClass *get_object() const final override;
void write_value_to(BitStreamBase &stream) const final override;
void read_value_from(BitStreamBase &stream) final override;
void set_object(PropertyClass *object) final override;
void write_value_to(BitStream &stream) const final override;
void read_value_from(BitStream &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(BitStreamBase &stream, int index) const = 0;
virtual void read_value_from(BitStreamBase &stream, int index) = 0;
virtual void set_object(PropertyClass *object, int index) = 0;
virtual void write_value_to(BitStream &stream, int index) const = 0;
virtual void read_value_from(BitStream &stream, int index) = 0;
};
}
}

View File

@ -7,10 +7,16 @@
#define _KI_PCLASS ki::pclass::PropertyClass
#define _KI_PCLASS_CONSTRUCTOR(derived) \
explicit derived(const _KI_TYPE &type, const _KI_TYPE_SYSTEM &type_system)
explicit derived(const _KI_TYPE &type, const _KI_TYPE_SYSTEM &type_system)
#define _KI_PCLASS_COPY_CONSTRUCTOR(derived) \
derived(const derived &that)
#define _KI_PCLASS_CONSTRUCT_BASE(base) \
: base(type, type_system)
: base(type, type_system)
#define _KI_PCLASS_COPY_CONSTRUCT_BASE(base) \
: base(that)
#define DERIVED_PCLASS(derived, base) class derived : public base
#define PCLASS(n) DERIVED_PCLASS(n, _KI_PCLASS)
@ -23,11 +29,22 @@ _KI_PCLASS_CONSTRUCTOR(derived) \
_KI_PCLASS_CONSTRUCTOR(derived) \
_KI_PCLASS_CONSTRUCT_BASE(base)
#define PCLASS_COPY_CONSTRUCTOR(derived) \
_KI_PCLASS_COPY_CONSTRUCTOR(derived) \
_KI_PCLASS_COPY_CONSTRUCT_BASE(_KI_PCLASS)
#define DERIVED_PCLASS_COPY_CONSTRUCTOR(derived, base) \
_KI_PCLASS_COPY_CONSTRUCTOR(derived) \
_KI_PCLASS_COPY_CONSTRUCT_BASE(base)
#define TYPE(n) type_system.get_type(n)
#define INIT_PROPERTY(identifier, type) \
, identifier(*this, #identifier, TYPE(type))
#define INIT_PROPERTY_COPY(identifier) \
, identifier(*this, that.##identifier)
#define INIT_PROPERTY_VALUE(identifier, type, value) \
, identifier(*this, #identifier, TYPE(type), value)
@ -43,18 +60,28 @@ namespace pclass
*/
class PropertyClass
{
friend PropertyBase;
public:
explicit PropertyClass(const Type &type, const TypeSystem &type_system);
virtual ~PropertyClass();
virtual ~PropertyClass() {}
PropertyClass(const PropertyClass &that);
PropertyClass &operator=(const PropertyClass &that);
const Type &get_type() const;
PropertyList &get_properties();
const PropertyList &get_properties() const;
virtual PropertyClass *copy() const;
virtual void on_created() const {}
protected:
void add_property(PropertyBase &prop);
private:
const Type *m_type;
PropertyList *m_properties;
PropertyList m_properties;
};
}
}

View File

@ -7,6 +7,7 @@ namespace ki
{
namespace pclass
{
class PropertyClass;
class PropertyBase;
/**
@ -14,25 +15,75 @@ namespace pclass
*/
class PropertyList
{
friend PropertyBase;
friend PropertyClass;
public:
using const_iterator = std::vector<PropertyBase *>::const_iterator;
/**
* TODO: Documentation
*/
class iterator
{
friend PropertyList;
public:
PropertyBase &operator*() const;
PropertyBase *operator->() const;
iterator &operator++();
iterator operator++(int);
bool operator==(const iterator &that) const;
bool operator!=(const iterator &that) const;
private:
PropertyList *m_list;
int m_index;
explicit iterator(PropertyList &list, const int index = 0);
};
/**
* TODO: Documentation
*/
class const_iterator
{
friend PropertyList;
public:
const PropertyBase &operator*() const;
const PropertyBase *operator->() const;
const_iterator &operator++();
const_iterator operator++(int);
bool operator==(const const_iterator &that) const;
bool operator!=(const const_iterator &that) const;
private:
const PropertyList *m_list;
int m_index;
explicit const_iterator(const PropertyList &list, const int index = 0);
};
std::size_t get_property_count() const;
bool has_property(const std::string &name) const;
bool has_property(hash_t hash) const;
PropertyBase &get_property(int index);
const PropertyBase &get_property(int index) const;
PropertyBase &get_property(const std::string &name);
const PropertyBase &get_property(const std::string &name) const;
PropertyBase &get_property(hash_t hash);
const PropertyBase &get_property(hash_t hash) const;
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
protected:
void add_property(PropertyBase *prop);
void add_property(PropertyBase &prop);
private:
std::vector<PropertyBase *> m_properties;

View File

@ -10,6 +10,7 @@ namespace pclass
template <typename ValueT>
class StaticProperty;
/// @cond DOXYGEN_SKIP
/**
* A helper utility that provides the right implementation of construct()
* and get_object() based on characteristics of type: ValueT.
@ -28,6 +29,13 @@ namespace pclass
return ValueT();
}
static ValueT copy(const StaticProperty<ValueT> &prop)
{
// In cases where ValueT is not a pointer, and does not derive from PropertyClass,
// just call the copy constructor.
return ValueT(prop.m_value);
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{
// ValueT does not derive from PropertyClass, and so, this property is not
@ -36,6 +44,15 @@ namespace pclass
"Tried calling get_object() on a property that does not store an object."
);
}
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
{
// ValueT does not derive from PropertyClass, and so, this property is not
// storing an object.
throw runtime_error(
"Tried calling set_object() on a property that does not store an object."
);
}
};
/**
@ -43,8 +60,9 @@ namespace pclass
* - A pointer; but
* - does not derive from PropertyClass
*
* This should construct to a nullptr, and throw an exception
* when get_object()is called.
* This should:
* - Construct to a nullptr; and
* - Throw an exception when get_object() is called.
*/
template <typename ValueT>
struct value_object_helper<
@ -66,6 +84,13 @@ namespace pclass
return nullptr;
}
static ValueT copy(const StaticProperty<ValueT> &prop)
{
// The copy constructor for all pointers is to copy the pointer
// without creating a new copy of the object it's pointing too.
return prop.m_value;
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{
// ValueT does not derive from PropertyClass, and so, this property is not
@ -74,6 +99,15 @@ namespace pclass
"Tried calling get_object() on a property that does not store an object."
);
}
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
{
// ValueT does not derive from PropertyClass, and so, this property is not
// storing an object.
throw runtime_error(
"Tried calling set_object() on a property that does not store an object."
);
}
};
/**
@ -81,8 +115,10 @@ namespace pclass
* - A pointer; and
* - does derive from PropertyClass
*
* This should construct to a nullptr, and return a pointer to
* a ValueT instance (as a PropertyClass *) when get_object() is called.
* This should:
* - Construct to a nullptr; and
* - Return a pointer to a ValueT instance (as a PropertyClass *)
* when get_object() is called.
*/
template <typename ValueT>
struct value_object_helper<
@ -104,12 +140,26 @@ namespace pclass
return nullptr;
}
static ValueT copy(const StaticProperty<ValueT> &prop)
{
// The copy constructor for all pointers is to copy the pointer
// without creating a new copy of the object it's pointing too.
return prop.m_value;
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{
// ValueT does derive from PropertyClass, and we have a pointer to an instance
// of ValueT, so we can cast down to a PropertyClass pointer.
return dynamic_cast<const PropertyClass *>(prop.m_value);
}
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
{
// ValueT does derive from PropertyClass, and we have a pointer to an instance
// of PropertyClass, so cast the pointer up to a ValueT.
prop.m_value = dynamic_cast<ValueT>(object);
}
};
/**
@ -117,9 +167,11 @@ namespace pclass
* - Not a pointer; and
* - does derive from PropertyClass
*
* This should construct an instance of ValueT by passing the property
* type and the type's type system, and return a pointer to a ValueT
* instance (as a PropertyClass *) when get_object() is called.
* This should:
* - Construct an instance of ValueT by passing the property
* type and the type's type system; and
* - Return a pointer to a ValueT instance (as a PropertyClass *)
* when get_object() is called.
*/
template <typename ValueT>
struct value_object_helper<
@ -143,12 +195,29 @@ namespace pclass
return ValueT(type, type.get_type_system());
}
static ValueT copy(const StaticProperty<ValueT> &prop)
{
// Derivitives of PropertyClass implement a clone method that returns
// a clone as a pointer.
ValueT *value_ptr = dynamic_cast<ValueT *>(prop.m_value.clone());
ValueT value = *value_ptr;
delete value_ptr;
return value;
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{
// ValueT does derive from PropertyClass, and we have an instance of ValueT,
// so we can cast down to a PropertyClass pointer.
return dynamic_cast<const PropertyClass *>(&prop.m_value);
}
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
{
// ValueT does derive from PropertyClass, but we don't store a pointer,
// so we need to copy the value in.
prop.m_value = *object;
}
};
/**
@ -161,24 +230,24 @@ namespace pclass
>
struct value_rw_helper
{
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
{
prop.get_type().write_to(stream, prop.m_value);
}
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
{
prop.get_type().read_from(stream, Value(prop.m_value));
}
};
/**
* Specialization for when ValueT is a pointer.
*
* Dereference the pointer before creating Value instances.
* This is so that the Value stores a pointer to a ValueT instance,
* rather than storing a pointer to a pointer.
*/
* Specialization for when ValueT is a pointer.
*
* Dereference the pointer before creating Value instances.
* This is so that the Value stores a pointer to a ValueT instance,
* rather than storing a pointer to a pointer.
*/
template <typename ValueT>
struct value_rw_helper<
ValueT,
@ -187,12 +256,12 @@ namespace pclass
>::type
>
{
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
{
prop.get_type().write_to(stream, *prop.m_value);
}
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
{
prop.get_type().read_from(stream, Value(*prop.m_value));
}
@ -210,21 +279,32 @@ namespace pclass
return value_object_helper<ValueT>::construct(type);
}
static ValueT copy(const StaticProperty<ValueT> &prop)
{
return value_object_helper<ValueT>::copy(prop);
}
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{
return value_object_helper<ValueT>::get_object(prop);
}
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
{
value_object_helper<ValueT>::set_object(prop, object);
}
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
{
value_rw_helper<ValueT>::write(prop, stream);
}
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
{
value_rw_helper<ValueT>::read(prop, stream);
}
};
/// @endcond
/**
* TODO: Documentation
@ -237,6 +317,10 @@ namespace pclass
friend value_rw_helper<ValueT>;
public:
// Do not allow copy assignment. Once a property has been constructed,
// it shouldn't be able to change.
StaticProperty<ValueT> &operator=(const StaticProperty<ValueT> &that) = delete;
StaticProperty(PropertyClass &object,
const std::string &name, const Type &type)
: PropertyBase(object, name, type)
@ -250,34 +334,19 @@ namespace pclass
m_value = value;
}
constexpr bool is_dynamic() const override
{
return false;
}
StaticProperty(PropertyClass &object, const StaticProperty<ValueT> &that)
: PropertyBase(object, that)
, m_value(value_helper<ValueT>::copy(that))
{}
constexpr bool is_pointer() const override
{
return std::is_pointer<ValueT>::value;
}
void write_value_to(BitStreamBase &stream) const override
constexpr bool is_dynamic() const override
{
value_helper<ValueT>::write(*this, stream);
}
void read_value_from(BitStreamBase &stream) override
{
value_helper<ValueT>::read(*this, stream);
}
const PropertyClass *get_object() const override
{
return value_helper<ValueT>::get_object(*this);
}
ValueT &get()
{
return m_value;
return false;
}
Value get_value() const override
@ -285,12 +354,52 @@ namespace pclass
return m_value;
}
operator ValueT &() const
const PropertyClass *get_object() const override
{
return value_helper<ValueT>::get_object(*this);
}
void set_object(PropertyClass *object) override
{
return value_helper<ValueT>::set_object(*this, object);
}
void write_value_to(BitStream &stream) const override
{
value_helper<ValueT>::write(*this, stream);
}
void read_value_from(BitStream &stream) override
{
value_helper<ValueT>::read(*this, stream);
}
ValueT &get()
{
return m_value;
}
ValueT *operator&() const
const ValueT &get() const
{
return m_value;
}
operator ValueT &()
{
return m_value;
}
operator const ValueT &() const
{
return m_value;
}
ValueT *operator&()
{
return &m_value;
}
const ValueT *operator&() const
{
return &m_value;
}

View File

@ -7,159 +7,307 @@ namespace ki
{
namespace pclass
{
// Forward declare for our helpers
template <typename ValueT>
class VectorPropertyBase : public std::vector<ValueT>, public DynamicPropertyBase
class VectorProperty;
/// @cond DOXYGEN_SKIP
/**
*
*/
template <
typename ValueT,
typename IsPointerEnable = void,
typename IsBaseEnable = void
>
struct vector_value_object_helper
{
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
{
return ValueT(prop.at(index));
}
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
{
// ValueT does not derive from PropertyClass, and so, this property is not
// storing an object.
throw runtime_error(
"Tried calling get_object() on a property that does not store an object."
);
}
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
{
// ValueT does not derive from PropertyClass, and so, this property is not
// storing an object.
throw runtime_error(
"Tried calling set_object() on a property that does not store an object."
);
}
};
/**
*
*/
template <typename ValueT>
struct vector_value_object_helper<
ValueT,
typename std::enable_if<
std::is_pointer<ValueT>::value
>::type,
typename std::enable_if<
!std::is_base_of<
PropertyClass,
typename std::remove_pointer<ValueT>::type
>::value
>::type
>
{
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
{
return prop.at(index);
}
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
{
// ValueT does not derive from PropertyClass, and so, this property is not
// storing an object.
throw runtime_error(
"Tried calling get_object() on a property that does not store an object."
);
}
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
{
// ValueT does not derive from PropertyClass, and so, this property is not
// storing an object.
throw runtime_error(
"Tried calling set_object() on a property that does not store an object."
);
}
};
/**
*
*/
template <typename ValueT>
struct vector_value_object_helper<
ValueT,
typename std::enable_if<
!std::is_pointer<ValueT>::value
>::type,
typename std::enable_if<
std::is_base_of<
PropertyClass,
typename std::remove_pointer<ValueT>::type
>::value
>::type
>
{
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
{
ValueT *value_ptr = dynamic_cast<ValueT *>(prop.at(index).copy());
ValueT value = *value_ptr;
delete value_ptr;
return value;
}
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
return dynamic_cast<PropertyClass *>(&prop.at(index));
}
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
prop.at(index) = dynamic_cast<ValueT>(*object);
delete object;
}
};
/**
*
*/
template <typename ValueT>
struct vector_value_object_helper<
ValueT,
typename std::enable_if<
std::is_pointer<ValueT>::value
>::type,
typename std::enable_if<
std::is_base_of<
PropertyClass,
typename std::remove_pointer<ValueT>::type
>::value
>::type
>
{
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
{
return prop.at(index);
}
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
return dynamic_cast<PropertyClass *>(prop.at(index));
}
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
prop.at(index) = dynamic_cast<ValueT>(object);
}
};
/**
*
*/
template <
typename ValueT,
typename IsPointerEnable = void
>
struct vector_value_rw_helper
{
static void write_value_to(const VectorProperty<ValueT> &prop, BitStream &stream, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
prop.get_type().write_to(stream, prop.at(index));
}
static void read_value_from(VectorProperty<ValueT> &prop, BitStream &stream, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
prop.get_type().read_from(stream, Value(prop.at(index)));
}
};
/**
*
*/
template <typename ValueT>
struct vector_value_rw_helper<
ValueT,
typename std::enable_if<std::is_pointer<ValueT>::value>::type
>
{
static void write_value_to(const VectorProperty<ValueT> &prop, BitStream &stream, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
prop.get_type().write_to(stream, *prop.at(index));
}
static void read_value_from(VectorProperty<ValueT> &prop, BitStream &stream, const int index)
{
if (index < 0 || index >= prop.size())
throw runtime_error("Index out of bounds.");
prop.get_type().read_from(stream, Value(*prop.at(index)));
}
};
/**
*
*/
template <typename ValueT>
struct vector_value_helper
{
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
{
return vector_value_object_helper<ValueT>::copy(prop, index);
}
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
{
return vector_value_object_helper<ValueT>::get_object(prop, index);
}
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
{
vector_value_object_helper<ValueT>::set_object(prop, object, index);
}
static void write_value_to(const VectorProperty<ValueT> &prop, BitStream &stream, const int index)
{
vector_value_rw_helper<ValueT>::write_value_to(prop, stream, index);
}
static void read_value_from(VectorProperty<ValueT> &prop, BitStream &stream, const int index)
{
vector_value_rw_helper<ValueT>::read_value_from(prop, stream, index);
}
};
/// @endcond
template <typename ValueT>
class VectorProperty : public std::vector<ValueT>, public DynamicPropertyBase
{
public:
VectorPropertyBase(PropertyClass &object,
// Do not allow copy assignment. Once a property has been constructed,
// it shouldn't be able to change.
VectorProperty<ValueT> &operator=(const VectorProperty<ValueT> &that) = delete;
VectorProperty(PropertyClass &object,
const std::string &name, const Type &type)
: DynamicPropertyBase(object, name, type) { }
: DynamicPropertyBase(object, name, type)
{}
VectorProperty(PropertyClass &object,
const VectorProperty<ValueT> &that)
: DynamicPropertyBase(object, that)
{
// Copy vector values into this vector
for (auto i = 0; i < this->size(); i++)
this->push_back(vector_value_helper<ValueT>::copy(*this, i));
}
constexpr bool is_pointer() const override
{
return std::is_pointer<ValueT>::value;
}
std::size_t get_element_count() const override
{
return this->size();
}
void set_element_count(const std::size_t size) override
{
this->resize(size);
}
Value get_value(int index) const override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
return this->at(index);
}
};
template <typename ValueT, typename Enable = void>
class VectorPropertyBase2 : public VectorPropertyBase<ValueT>
{
VectorPropertyBase2(PropertyClass &object,
const std::string &name, const Type &type)
: VectorPropertyBase<ValueT>(object, name, type) { }
constexpr bool is_pointer() const override
{
return false;
}
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(BitStreamBase &stream, const int index) override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
this->get_type().read_from(stream, this->at(index));
}
};
template <typename ValueT>
class VectorPropertyBase2<
ValueT,
typename std::enable_if<
std::is_pointer<ValueT>::value
>::type
> : public VectorPropertyBase<ValueT>
{
public:
VectorPropertyBase2(PropertyClass &object,
const std::string &name, const Type &type)
: VectorPropertyBase<ValueT>(object, name, type) { }
constexpr bool is_pointer() const override
{
return true;
}
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(BitStreamBase &stream, const int index) override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
this->get_type().read_from(stream, Value(*this->at(index)));
}
};
template <
typename ValueT,
typename _IsBaseEnable = void,
typename _IsPointerEnable = void
>
class VectorProperty : public VectorPropertyBase2<ValueT>
{
public:
VectorProperty(PropertyClass &object,
const std::string &name, const Type &type)
: VectorPropertyBase2<ValueT>(object, name, type) { }
const PropertyClass *get_object(const int index) const override
{
// We aren't holding an object at all, whoever called this is mistaken.
throw runtime_error(
"Tried calling get_object(index) on a property that does not store an object."
);
return vector_value_helper<ValueT>::get_object(*this, index);
}
};
template <typename ValueT>
class VectorProperty<
ValueT,
typename std::enable_if<
std::is_base_of<
PropertyClass,
typename std::remove_pointer<ValueT>::type
>::value
>::type,
typename std::enable_if<
!std::is_pointer<ValueT>::value
>::type
> : public VectorPropertyBase2<ValueT>
{
public:
VectorProperty(PropertyClass &object,
const std::string &name, const Type &type)
: VectorPropertyBase2<ValueT>(object, name, type) { }
const PropertyClass *get_object(const int index) const override
void set_object(PropertyClass *object, int index) override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
return dynamic_cast<PropertyClass *>(&this->at(index));
return vector_value_helper<ValueT>::set_object(*this, object, index);
}
};
template <typename ValueT>
class VectorProperty<
ValueT,
typename std::enable_if<
std::is_base_of<
PropertyClass,
typename std::remove_pointer<ValueT>::type
>::value
>::type,
typename std::enable_if<
std::is_pointer<ValueT>::value
>::type
> : public VectorPropertyBase2<ValueT>
{
public:
VectorProperty(PropertyClass &object,
const std::string &name, const Type &type)
: VectorPropertyBase2<ValueT>(object, name, type) { }
const PropertyClass *get_object(const int index) const override
void write_value_to(BitStream &stream, const int index) const override
{
if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds.");
return dynamic_cast<PropertyClass *>(this->at(index));
vector_value_helper<ValueT>::write_value_to(*this, stream, index);
}
void read_value_from(BitStream &stream, const int index) override
{
vector_value_helper<ValueT>::read_value_from(*this, stream, index);
}
};
}

View File

@ -4,7 +4,6 @@
#include "ki/pclass/Property.h"
#include "ki/pclass/types/Type.h"
#include "ki/pclass/PropertyClass.h"
#include "ki/util/exception.h"
namespace ki
{
@ -45,20 +44,20 @@ namespace pclass
return new ClassT(*this, get_type_system());
}
void write_to(BitStreamBase &stream, const Value &value) const override
void write_to(BitStream &stream, const Value &value) const override
{
const auto &object = dynamic_cast<const PropertyClass &>(value.get<ClassT>());
const auto &properties = object.get_properties();
for (auto it = properties.begin(); it != properties.end(); ++it)
(*it)->write_value_to(stream);
it->write_value_to(stream);
}
void read_from(BitStreamBase &stream, Value &value) const override
void read_from(BitStream &stream, Value &value) const override
{
auto &object = dynamic_cast<PropertyClass &>(value.get<ClassT>());
auto &properties = object.get_properties();
for (auto it = properties.begin(); it != properties.end(); ++it)
(*it)->read_value_from(stream);
it->read_value_from(stream);
}
};
}

View File

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

View File

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

View File

@ -11,7 +11,7 @@ namespace pclass
template <typename ValueT, typename Enable = void>
struct PrimitiveTypeWriter
{
static void write_to(BitStreamBase &stream, const ValueT &value)
static void write_to(BitStream &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(BitStreamBase &stream, ValueT &value)
static void read_from(BitStream &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(BitStreamBase &stream, const Value &value) const override
void write_to(BitStream &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(BitStreamBase &stream, Value &value) const override
void read_from(BitStream &stream, Value &value) const override
{
if (!value.is<ValueT>())
throw std::runtime_error("Invalid call to Type::read_from -- value type does not match ValueT.");

View File

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

View File

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

View File

@ -45,17 +45,17 @@ namespace serialization
bool is_file, flags flags);
virtual ~SerializerBinary() {}
void save(const pclass::PropertyClass *object, BitStreamBase &stream);
void load(pclass::PropertyClass *&dest, BitStreamBase &stream);
void save(const pclass::PropertyClass *object, BitStream &stream);
void load(pclass::PropertyClass *&dest, BitStream &stream, std::size_t size);
protected:
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 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 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;
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;
private:
const pclass::TypeSystem *m_type_system;

View File

@ -3,181 +3,343 @@
#include <type_traits>
#include <sstream>
#include "ki/util/BitTypes.h"
#include "exception.h"
#define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000
#define KI_BITBUFFER_DEFAULT_SIZE 0x2000
namespace ki
{
// Forward declaration so that buffers can create segments of themselves
class BitBufferSegment;
/**
* An abstract base class that provides a common interface for
* writing to, reading from, and querying bit streams.
*
*/
class BitStreamBase
class BitBufferBase
{
friend BitBufferSegment;
public:
/**
* Represents a position in a BitStream's buffer.
*
*/
struct stream_pos
struct buffer_pos
{
explicit stream_pos(intmax_t byte = 0, int bit = 0);
stream_pos(const stream_pos &cp);
explicit buffer_pos(uint32_t byte = 0, int bit = 0);
buffer_pos(const buffer_pos &cp);
intmax_t as_bits() const;
intmax_t get_byte() const;
uint32_t as_bytes() const;
uint32_t as_bits() const;
uint32_t get_byte() const;
uint8_t get_bit() const;
stream_pos operator +(const stream_pos &rhs) const;
stream_pos operator -(const stream_pos &rhs) const;
stream_pos operator +(const int &rhs) const;
stream_pos operator -(const int &rhs) const;
stream_pos &operator +=(stream_pos lhs);
stream_pos &operator -=(stream_pos lhs);
stream_pos &operator +=(int bits);
stream_pos &operator -=(int bits);
stream_pos &operator ++();
stream_pos &operator --();
stream_pos operator ++(int increment);
stream_pos operator --(int increment);
buffer_pos operator +(const buffer_pos &rhs) const;
buffer_pos operator -(const buffer_pos &rhs) const;
buffer_pos operator +(const int &rhs) const;
buffer_pos operator -(const int &rhs) const;
buffer_pos &operator +=(buffer_pos lhs);
buffer_pos &operator -=(buffer_pos lhs);
buffer_pos &operator +=(int bits);
buffer_pos &operator -=(int bits);
buffer_pos &operator ++();
buffer_pos &operator --();
buffer_pos operator ++(int increment);
buffer_pos operator --(int increment);
private:
intmax_t m_byte;
uint32_t m_byte;
uint8_t m_bit;
void set_bit(int bit);
};
virtual ~BitStreamBase() {}
virtual ~BitBufferBase() {};
/**
* @returns The stream's current position.
* @returns
*/
virtual stream_pos tell() const = 0;
virtual uint8_t *data() const = 0;
/**
* Sets the position of the stream.
* @param position The new position of the stream.
* @returns The size of the buffer in bytes.
*/
virtual void seek(stream_pos position) = 0;
virtual std::size_t size() const = 0;
/**
* @returns The current size of the internal buffer.
*/
virtual std::size_t capacity() const = 0;
* Resize the buffer to the specified size.
* If the current size is larger than the specified new size, then the data is truncated.
* @param[in] new_size The size to resize the buffer to in bytes.
*/
virtual void resize(std::size_t new_size) = 0;
/**
* @returns A pointer to the start of the internal buffer.
* @param[in] from The position of the start of the segment.
* @param[in] bitsize The size of the segment in bits.
* @returns A new segment of this buffer.
*/
virtual const uint8_t *data() const = 0;
virtual BitBufferSegment *segment(buffer_pos from, std::size_t bitsize);
/**
* Reads a value from the buffer given a defined number of bits.
* @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,
typename = std::enable_if<is_integral<IntegerT>::value>
>
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value)
* Reads a value from the buffer.
* @tparam ValueT The type of value (must be an integral type).
* @param[in] position Where in the buffer to retrieve the value from.
* @param[in] bits The number of bits the value occupies in the buffer.
* @returns The n-bit value retrieved from the buffer.
* @throws ki::runtime_error Buffer is not large enough to read the specified number of bits at the specified position.
*/
template <typename ValueT>
ValueT read(const buffer_pos position,
const uint8_t bits = bitsizeof<ValueT>::value) const
{
return static_cast<IntegerT>(read(bits));
// Check for buffer overflow
if ((position + bits).as_bytes() > size())
{
std::ostringstream oss;
oss << "Buffer is not large enough to read a "
<< static_cast<uint16_t>(bits) << "-bit value at specified position. "
<< "(byte=" << position.get_byte() << ", bit=" << static_cast<uint16_t>(position.get_bit())
<< ", size=" << size() << ")";
throw runtime_error(oss.str());
}
return static_cast<ValueT>(read(position, bits));
}
/**
* Writes a value to the buffer that occupies a defined number of bits.
* @tparam IntegerT The type of value (must be an integral type).
* Writes a value to the buffer.
* @tparam ValueT 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.
* @param[in] position Where in the buffer to write the value to.
* @param[in] bits The number of bits the value should occupy in the buffer. Defaults to the bitsize of ValueT.
* @throws ki::runtime_error Buffer is not large enough to write the specified number of bits at the specified position.
*/
template <
typename IntegerT,
typename = std::enable_if<is_integral<IntegerT>::value>
>
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value)
template <typename ValueT>
void write(const ValueT value, const buffer_pos position,
const uint8_t bits = bitsizeof<ValueT>::value)
{
write(static_cast<uint64_t>(value), bits);
// Check for buffer overflow
if ((position + bits).as_bytes() > size())
{
std::ostringstream oss;
oss << "Buffer is not large enough to write a "
<< static_cast<uint16_t>(bits) << "-bit value at specified position. "
<< "(byte=" << position.get_byte() << ", bit=" << static_cast<uint16_t>(position.get_bit())
<< ", size=" << size() << ")";
throw runtime_error(oss.str());
}
write(static_cast<uint64_t>(value), position, bits);
}
/**
* Copy memory from an external buffer into the bitstream's buffer from the current position.
* @param[in] src The buffer to copy data from.
* @param[in] bitsize The number of bits to copy from the src buffer.
* Copy memory from this buffer into an external buffer.
* @param[out] dst The destination buffer to copy data to.
* @param[in] position Where in the buffer to start copying data from.
* @param[in] bitsize The number of bits to copy into the destination buffer.
*/
void write_copy(uint8_t *src, std::size_t bitsize);
void read_copy(uint8_t *dst, buffer_pos position, std::size_t bitsize) const;
/**
* Copy memory from the bitstream's buffer into an external buffer.
* @param[out] dst The destination buffer to copy data to.
* @param[in] bitsize The number of bits to copy into the dst buffer.
* Copy memory from an external buffer into this buffer at the specified position.
* @param[in] src The source buffer to copy data from.
* @param[in] position Where in the buffer to start copying data to.
* @param[in] bitsize The number of bits to copy from the source buffer.
*/
void read_copy(uint8_t *dst, std::size_t bitsize);
void write_copy(uint8_t *src, buffer_pos position, std::size_t bitsize);
protected:
virtual uint64_t read(uint8_t bits) = 0;
virtual void write(uint64_t value, uint8_t bits) = 0;
virtual uint64_t read(buffer_pos position, uint8_t bits) const = 0;
virtual void write(uint64_t value, buffer_pos position, uint8_t bits) = 0;
};
/**
*
*/
class BitBuffer : public BitBufferBase
{
public:
explicit BitBuffer(std::size_t buffer_size = KI_BITBUFFER_DEFAULT_SIZE);
~BitBuffer();
BitBuffer(const BitBuffer &that);
BitBuffer &operator=(const BitBuffer &that);
/**
* Create a new BitBuffer from an existing buffer.
* @param[in] buffer The buffer to take ownership of.
* @param[in] buffer_size The size of the buffer in bytes.
*/
explicit BitBuffer(uint8_t *buffer, std::size_t buffer_size);
std::size_t size() const override;
uint8_t* data() const override;
void resize(std::size_t new_size) override;
/**
* @copydoc BitBufferBase::read<ValueT>(buffer_pos, uint8_t)
*/
template <typename ValueT>
ValueT read(const buffer_pos position,
const uint8_t bits = bitsizeof<ValueT>::value) const
{
return BitBufferBase::read<ValueT>(position, bits);
}
/**
* @copydoc BitBufferBase::write<ValueT>(ValueT, buffer_pos, uint8_t)
*/
template <typename ValueT>
void write(const ValueT value, const buffer_pos position,
const uint8_t bits = bitsizeof<ValueT>::value)
{
return BitBufferBase::write<ValueT>(value, position, bits);
}
protected:
uint64_t read(buffer_pos position, uint8_t bits) const override;
void write(uint64_t value, buffer_pos position, uint8_t bits) override;
private:
uint8_t *m_buffer;
std::size_t m_buffer_size;
};
/**
*
*/
class BitBufferSegment : public BitBufferBase
{
public:
BitBufferSegment(BitBufferBase &buffer, buffer_pos from, std::size_t bitsize);
std::size_t size() const override;
void resize(std::size_t new_size) override;
uint8_t* data() const override;
BitBufferSegment *segment(buffer_pos from, std::size_t bitsize) override;
/**
* @copydoc BitBufferBase::read<ValueT>(buffer_pos, uint8_t)
*/
template <typename ValueT>
ValueT read(const buffer_pos position,
const uint8_t bits = bitsizeof<ValueT>::value) const
{
return BitBufferBase::read<ValueT>(position, bits);
}
/**
* @copydoc BitBufferBase::write<ValueT>(ValueT, buffer_pos, uint8_t)
*/
template <typename ValueT>
void write(const ValueT value, const buffer_pos position,
const uint8_t bits = bitsizeof<ValueT>::value)
{
return BitBufferBase::write<ValueT>(value, position, bits);
}
protected:
uint64_t read(buffer_pos position, uint8_t bits) const override;
void write(uint64_t value, buffer_pos position, uint8_t bits) override;
private:
BitBufferBase *m_buffer;
buffer_pos m_from;
std::size_t m_bitsize;
};
/**
* A read/write-able stream of bits.
*/
class BitStream : public BitStreamBase
class BitStream
{
public:
explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE);
using stream_pos = BitBuffer::buffer_pos;
explicit BitStream(BitBufferBase &buffer);
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;
BitStream(const BitStream &that);
BitStream &operator=(const BitStream &that);
/**
* @copydoc BitStreamBase::read<IntegerT>(uint8_t)
* @throws ki::runtime_error Not enough data available to read the specified number of bits.
* @returns The stream's current position.
*/
stream_pos tell() const;
/**
* Sets the position of the stream.
* @param[in] position The new position of the stream.
* @param[in] expand Whether or not to expand the buffer.
*/
void seek(stream_pos position, bool expand = true);
/**
* @returns The current size of the internal buffer.
*/
std::size_t capacity() const;
/**
* @returns The BitBuffer that the stream is reading/writing to.
*/
BitBufferBase &buffer() const;
/**
* Reads a value from the buffer.
* @tparam ValueT The type used to store the value.
* @param[in] bits The number of bits to read. Defaults to the bitsize of ValueT.
* @returns The value read from the buffer.
*/
template <
typename IntegerT,
typename = std::enable_if<is_integral<IntegerT>::value>
typename ValueT,
typename = std::enable_if<is_integral<ValueT>::value>
>
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value)
ValueT read(const uint8_t bits = bitsizeof<ValueT>::value)
{
return BitStreamBase::read<IntegerT>(bits);
ValueT value = m_buffer->read<ValueT>(m_position, bits);
m_position += bits;
return value;
}
/**
* Writes a value to the buffer.
* If the buffer is not big enough to write the value, then the buffer is resized.
* @tparam ValueT 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 ValueT.
*/
template <
typename IntegerT,
typename = std::enable_if<is_integral<IntegerT>::value>
typename ValueT,
typename = std::enable_if<is_integral<ValueT>::value>
>
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value)
void write(ValueT value, const uint8_t bits = bitsizeof<ValueT>::value)
{
BitStreamBase::write<IntegerT>(value, bits);
expand_buffer(m_position + bits);
m_buffer->write<ValueT>(value, m_position, bits);
m_position += bits;
}
protected:
uint64_t read(uint8_t bits) override;
void write(uint64_t value, uint8_t bits) override;
/**
* Copy memory from the BitStream's buffer into an external buffer at the current position.
* @param[out] dst The destination buffer to copy data to.
* @param[in] bitsize The number of bits to copy into the destination buffer.
*/
void read_copy(uint8_t *dst, std::size_t bitsize);
/**
* Copy memory from an external buffer into the BitStream's buffer from the current position.
* @param[in] src The source buffer to copy data from.
* @param[in] bitsize The number of bits to copy from the source buffer.
*/
void write_copy(uint8_t *src, std::size_t bitsize);
private:
uint8_t * m_buffer;
std::size_t m_buffer_size;
BitBufferBase *m_buffer;
stream_pos m_position;
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;
/**
* Expand the buffer such that a position becomes valid.
* @param[in] position The minimum position that should be accessible after expanding.
*/
void expand_buffer(stream_pos position) const;
};
}

View File

@ -1,5 +1,6 @@
#include "ki/pclass/types/ClassType.h"
#include "ki/pclass/TypeSystem.h"
#include "ki/util/exception.h"
namespace ki
{
@ -23,6 +24,8 @@ namespace pclass
if (!m_base_class)
throw runtime_error("base_class must inherit ClassTypeBase!");
}
else
m_base_class = nullptr;
}
bool ClassTypeBase::inherits(const Type &type) const

View File

@ -19,7 +19,19 @@ namespace pclass
m_type = &type;
// Add this property to the object's property list
object.get_properties().add_property(this);
object.add_property(*this);
}
PropertyBase::PropertyBase(PropertyClass &object,
const PropertyBase &that)
{
m_name = that.m_name;
m_name_hash = that.m_name_hash;
m_full_hash = that.m_full_hash;
m_type = that.m_type;
// Add this property to the object's property list
object.add_property(*this);
}
std::string PropertyBase::get_name() const
@ -54,7 +66,13 @@ namespace pclass
DynamicPropertyBase::DynamicPropertyBase(PropertyClass &object,
const std::string& name, const Type& type)
: PropertyBase(object, name, type) { }
: PropertyBase(object, name, type)
{}
DynamicPropertyBase::DynamicPropertyBase(PropertyClass &object,
const DynamicPropertyBase &that)
: PropertyBase(object, that)
{}
bool DynamicPropertyBase::is_dynamic() const
{
@ -73,13 +91,19 @@ namespace pclass
throw runtime_error("Called get_object() on a dynamic property. Use get_object(index) instead.");
}
void DynamicPropertyBase::write_value_to(BitStreamBase &stream) const
void DynamicPropertyBase::set_object(PropertyClass *object)
{
// The caller must specify an index
throw runtime_error("Called set_object() on a dynamic property. Use set_object(index) instead.");
}
void DynamicPropertyBase::write_value_to(BitStream &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(BitStreamBase &stream)
void DynamicPropertyBase::read_value_from(BitStream &stream)
{
// The caller must specify an index
throw runtime_error("Called read_value_from() on a dynamic property. Use read_value_from(index) instead.");

View File

@ -7,13 +7,19 @@ namespace pclass
PropertyClass::PropertyClass(const Type &type, const TypeSystem &type_system)
{
m_type = &type;
m_properties = new PropertyList();
}
PropertyClass::~PropertyClass()
PropertyClass::PropertyClass(const PropertyClass &that)
{
// Delete the list of properties
delete m_properties;
m_type = that.m_type;
m_properties = PropertyList();
}
PropertyClass &PropertyClass::operator=(const PropertyClass &that)
{
m_type = that.m_type;
m_properties = PropertyList();
return *this;
}
const Type& PropertyClass::get_type() const
@ -23,12 +29,22 @@ namespace pclass
PropertyList &PropertyClass::get_properties()
{
return *m_properties;
return m_properties;
}
const PropertyList& PropertyClass::get_properties() const
{
return *m_properties;
return m_properties;
}
PropertyClass *PropertyClass::copy() const
{
return new PropertyClass(*this);
}
void PropertyClass::add_property(PropertyBase &prop)
{
m_properties.add_property(prop);
}
}
}

View File

@ -8,12 +8,92 @@ namespace ki
{
namespace pclass
{
PropertyBase &PropertyList::iterator::operator*() const
{
return m_list->get_property(m_index);
}
PropertyBase *PropertyList::iterator::operator->() const
{
return &operator*();
}
PropertyList::iterator &PropertyList::iterator::operator++()
{
m_index++;
return *this;
}
PropertyList::iterator PropertyList::iterator::operator++(int)
{
const auto copy(*this);
operator++();
return copy;
}
bool PropertyList::iterator::operator==(const iterator &that) const
{
return this->m_list == that.m_list &&
this->m_index == that.m_index;
}
bool PropertyList::iterator::operator!=(const iterator &that) const
{
return !(*this == that);
}
PropertyList::iterator::iterator(PropertyList &list, const int index)
{
m_list = &list;
m_index = index;
}
const PropertyBase &PropertyList::const_iterator::operator*() const
{
return m_list->get_property(m_index);
}
const PropertyBase *PropertyList::const_iterator::operator->() const
{
return &operator*();
}
PropertyList::const_iterator &PropertyList::const_iterator::operator++()
{
m_index++;
return *this;
}
PropertyList::const_iterator PropertyList::const_iterator::operator++(int)
{
const auto copy(*this);
operator++();
return copy;
}
bool PropertyList::const_iterator::operator==(const const_iterator &that) const
{
return this->m_list == that.m_list &&
this->m_index == that.m_index;
}
bool PropertyList::const_iterator::operator!=(const const_iterator &that) const
{
return !(*this == that);
}
PropertyList::const_iterator::const_iterator(const PropertyList &list, const int index)
{
m_list = &list;
m_index = index;
}
std::size_t PropertyList::get_property_count() const
{
return m_properties.size();
}
bool PropertyList::has_property(const std::string& name) const
bool PropertyList::has_property(const std::string &name) const
{
return m_property_name_lookup.find(name)
!= m_property_name_lookup.end();
@ -25,7 +105,7 @@ namespace pclass
!= m_property_hash_lookup.end();
}
const PropertyBase& PropertyList::get_property(const int index) const
PropertyBase &PropertyList::get_property(const int index)
{
if (index >= 0 && index < m_properties.size())
return *m_properties[index];
@ -36,7 +116,7 @@ namespace pclass
throw runtime_error(oss.str());
}
const PropertyBase& PropertyList::get_property(const std::string& name) const
PropertyBase& PropertyList::get_property(const std::string& name)
{
const auto it = m_property_name_lookup.find(name);
if (it != m_property_name_lookup.end())
@ -47,7 +127,7 @@ namespace pclass
throw runtime_error(oss.str());
}
const PropertyBase& PropertyList::get_property(const hash_t hash) const
PropertyBase& PropertyList::get_property(const hash_t hash)
{
const auto it = m_property_hash_lookup.find(hash);
if (it != m_property_hash_lookup.end())
@ -60,48 +140,87 @@ namespace pclass
throw runtime_error(oss.str());
}
const PropertyBase &PropertyList::get_property(const int index) const
{
if (index >= 0 && index < m_properties.size())
return *m_properties[index];
std::ostringstream oss;
oss << "Property index out of range. (index=" << index
<< ", size=" << m_properties.size() << ")";
throw runtime_error(oss.str());
}
const PropertyBase& PropertyList::get_property(const std::string &name) const
{
const auto it = m_property_name_lookup.find(name);
if (it != m_property_name_lookup.end())
return *it->second;
std::ostringstream oss;
oss << "Could not find property with name: '" << name << "'.";
throw runtime_error(oss.str());
}
const PropertyBase &PropertyList::get_property(const hash_t hash) const
{
const auto it = m_property_hash_lookup.find(hash);
if (it != m_property_hash_lookup.end())
return *it->second;
std::ostringstream oss;
oss << "Could not find property with hash: 0x"
<< std::hex << std::setw(8) << std::setfill('0')
<< std::uppercase << hash << ".";
throw runtime_error(oss.str());
}
PropertyList::iterator PropertyList::begin()
{
return iterator(*this);
}
PropertyList::const_iterator PropertyList::begin() const
{
return m_properties.cbegin();
return const_iterator(*this);
}
PropertyList::iterator PropertyList::end()
{
return iterator(*this, m_properties.size());
}
PropertyList::const_iterator PropertyList::end() const
{
return m_properties.cend();
return const_iterator(*this, m_properties.size());
}
void PropertyList::add_property(PropertyBase *prop)
void PropertyList::add_property(PropertyBase &prop)
{
// Make sure a property with the same name as another isn't being added
if (has_property(prop->get_name()))
if (has_property(prop.get_name()))
{
// This pointer is going to be lost, so delete it now
delete prop;
std::ostringstream oss;
oss << "A property has already been added with name: '"
<< prop->get_name() << "'.";
<< prop.get_name() << "'.";
throw runtime_error(oss.str());
}
// Check for hash collisions
if (has_property(prop->get_full_hash()))
if (has_property(prop.get_full_hash()))
{
const auto &other = get_property(prop->get_full_hash());
// This pointer is going to be lost, so delete it now
delete prop;
const auto &other = get_property(prop.get_full_hash());
std::ostringstream oss;
oss << "Cannot add property '" << prop->get_name() << "'. "
oss << "Cannot add property '" << prop.get_name() << "'. "
<< "Hash collision with property '" << other.get_name() << "'.";
throw runtime_error(oss.str());
}
// Add the property to lookups
m_properties.push_back(prop);
m_property_name_lookup[prop->get_name()] = prop;
m_property_hash_lookup[prop->get_full_hash()] = prop;
m_properties.push_back(&prop);
m_property_name_lookup[prop.get_name()] = &prop;
m_property_hash_lookup[prop.get_full_hash()] = &prop;
}
}
}

View File

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

View File

@ -15,22 +15,23 @@ namespace serialization
m_root_object = nullptr;
}
void SerializerBinary::save(const pclass::PropertyClass* object, BitStreamBase &stream)
void SerializerBinary::save(const pclass::PropertyClass* object, BitStream &stream)
{
// Write the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS))
stream.write<uint32_t>(static_cast<uint32_t>(m_flags));
// Remember where we started writing data, so we can compress later
// if necessary.
const auto start_pos = stream.tell();
// If the contents of the stream are going to be compressed,
// reserve space to put the length
// reserve space to put the length and compression toggle.
const auto compression_header_pos = stream.tell();
if (FLAG_IS_SET(m_flags, flags::COMPRESSED))
{
stream.write<bool>(false);
stream.write<uint32_t>(0);
}
// Write the object to the stream
const auto start_pos = stream.tell();
m_root_object = object;
save_object(object, stream);
@ -39,17 +40,17 @@ namespace serialization
{
// Remember where the ending of the data is
const auto end_pos = stream.tell();
const auto size_bits = (end_pos - (start_pos + bitsizeof<uint32_t>::value)).as_bits();
const auto size_bytes = (size_bits / 8) + (size_bits % 8 > 0 ? 1 : 0);
const auto size_bits = (end_pos - start_pos).as_bits();
const auto size_bytes = (end_pos - start_pos).as_bytes();
// Make a copy of the uncompressed data
auto *uncompressed = new uint8_t[size_bytes];
auto *uncompressed = new uint8_t[size_bytes]{0};
stream.read_copy(uncompressed, size_bits);
// Setup compression
static const std::size_t bufsize = 1024;
auto *temp_buffer = new uint8_t[bufsize];
std::vector<uint8_t> compressed(size_bytes);
auto *temp_buffer = new uint8_t[bufsize]{0};
std::vector<uint8_t> compressed;
z_stream z;
z.zalloc = nullptr;
z.zfree = nullptr;
@ -93,23 +94,26 @@ namespace serialization
temp_buffer, temp_buffer + bufsize - z.avail_out);
deflateEnd(&z);
// Move the contents of the compressed buffer if the compressed
// buffer is smaller
stream.seek(start_pos);
stream.write<uint32_t>(size_bytes);
if (compressed.size() < size_bytes)
stream.write_copy(compressed.data(), compressed.size() * 8);
else
// Go back to the end of the data
stream.seek(end_pos);
// Cleanup temporary buffers
delete[] uncompressed;
delete[] temp_buffer;
// Write the compression header
const auto use_compression = compressed.size() < size_bytes;
stream.seek(compression_header_pos);
stream.write<bool>(use_compression);
stream.write<uint32_t>(size_bytes);
// Write the compressed data
if (use_compression)
stream.write_copy(compressed.data(), compressed.size() * 8);
else
// Go back to the end of the original data
stream.seek(end_pos);
}
}
void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const
void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStream &stream) const
{
// If we have an object, write the type hash, otherwise, write NULL (0).
if (object)
@ -118,7 +122,7 @@ namespace serialization
stream.write<uint32_t>(NULL);
}
void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const
void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStream &stream) const
{
// Write any object headers
presave_object(object, stream);
@ -154,9 +158,13 @@ namespace serialization
stream.write(size_bits);
stream.seek(end_pos);
}
// Re-align the stream so that our position lies on a byte
// TODO: Look into how/when the serialization re-aligns as this may not be the right place
stream.seek(BitStream::stream_pos(stream.tell().as_bytes(), 0));
}
void SerializerBinary::save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const
void SerializerBinary::save_property(const pclass::PropertyBase &prop, BitStream &stream) const
{
// Remember where we started writing the property data
const auto start_pos = stream.tell();
@ -166,52 +174,52 @@ namespace serialization
{
// Reserve space to put the size of the property
stream.write<uint32_t>(0);
stream.write<uint32_t>(prop->get_full_hash());
stream.write<uint32_t>(prop.get_full_hash());
}
// Is the property dynamic? (holding more than one value)
auto &property_type = prop->get_type();
if (prop->is_dynamic())
auto &property_type = prop.get_type();
if (prop.is_dynamic())
{
// Cast the property to a DynamicPropertyBase
const auto *dynamic_property =
dynamic_cast<const pclass::DynamicPropertyBase *>(prop);
const auto &dynamic_property =
dynamic_cast<const pclass::DynamicPropertyBase &>(prop);
// Write the number of elements
stream.write<uint16_t>(dynamic_property->get_element_count());
stream.write<uint32_t>(dynamic_property.get_element_count());
// Iterate through the elements
for (auto i = 0; i < dynamic_property->get_element_count(); i++)
for (auto i = 0; i < dynamic_property.get_element_count(); i++)
{
// Is this a collection of pointers?
if (prop->is_pointer())
if (prop.is_pointer())
{
// Is the property a collection of pointers to other objects?
if (property_type.get_kind() == pclass::Type::kind::CLASS)
// Write the value as a nested object
save_object(dynamic_property->get_object(i), stream);
save_object(dynamic_property.get_object(i), stream);
else
// Write the value as normal (let the property deal with dereferencing)
dynamic_property->write_value_to(stream, i);
dynamic_property.write_value_to(stream, i);
}
else
// If the value isn't a pointer, and it's not dynamic, just write it as a value
dynamic_property->write_value_to(stream, i);
dynamic_property.write_value_to(stream, i);
}
}
else if (prop->is_pointer())
else if (prop.is_pointer())
{
// Does this property hold a pointer to another object?
if (property_type.get_kind() == pclass::Type::kind::CLASS)
// Write the value as a nested object
save_object(prop->get_object(), stream);
save_object(prop.get_object(), stream);
else
// Write the value as normal (let the property deal with dereferencing)
prop->write_value_to(stream);
prop.write_value_to(stream);
}
else
// If the value isn't a pointer, and it's not dynamic, just write it as a value
prop->write_value_to(stream);
prop.write_value_to(stream);
// Finish writing the property header by writing the length
if (m_is_file)
@ -227,32 +235,233 @@ namespace serialization
}
}
void SerializerBinary::load(pclass::PropertyClass*& dest, BitStreamBase &stream)
void SerializerBinary::load(pclass::PropertyClass *&dest,
BitStream &stream, const std::size_t size)
{
// Create a new stream that reads a segment of the stream given to us
BitBufferBase *buffer = stream.buffer().segment(stream.tell(), size * 8);
auto segment_stream = BitStream(*buffer);
stream.seek(stream.tell() + size * 8, false);
// Read the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS))
m_flags = static_cast<flags>(stream.read<uint32_t>());
m_flags = static_cast<flags>(segment_stream.read<uint32_t>());
// Decompress the contents of the stream
if (FLAG_IS_SET(m_flags, flags::COMPRESSED))
{
// Read the compression header
const auto use_compression = segment_stream.read<bool>();
const auto uncompressed_size = segment_stream.read<uint32_t>();
// Work out how much data is available after the compression header
const auto start_pos = segment_stream.tell();
const auto end_pos = BitStream::stream_pos(buffer->size(), 0);
const auto data_available_bytes = (end_pos - start_pos).as_bytes();
// Has compression been used on this object?
if (use_compression)
{
// Create a buffer for the compressed data and read it in
BitBuffer compressed(data_available_bytes);
segment_stream.read_copy(compressed.data(), data_available_bytes);
// Uncompress the compressed buffer
auto *uncompressed = new BitBuffer(uncompressed_size);
uLong dest_len = uncompressed_size;
uncompress(uncompressed->data(), &dest_len,
compressed.data(), data_available_bytes);
// Delete the old buffer and use the new uncompressed buffer
delete buffer;
buffer = uncompressed;
}
else
{
// Use a segment of the current buffer as the new buffer
auto *segment_buffer = buffer->segment(segment_stream.tell(), data_available_bytes);
delete buffer;
buffer = segment_buffer;
}
// Create a new stream to read from the new buffer
segment_stream = BitStream(*buffer);
}
// Load the root object
try
{
load_object(dest, segment_stream);
}
catch (runtime_error &e)
{
delete buffer;
throw e;
}
// Free resources
delete buffer;
}
void SerializerBinary::preload_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const
void SerializerBinary::preload_object(pclass::PropertyClass *&dest, BitStream &stream) const
{
const auto type_hash = stream.read<pclass::hash_t>();
if (type_hash != 0)
{
// Instantiate the type
const auto &type = m_type_system->get_type(type_hash);
dest = type.instantiate();
}
else
// Instantiate to a nullptr
dest = nullptr;
}
void SerializerBinary::load_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const
void SerializerBinary::load_object(pclass::PropertyClass *&dest, BitStream &stream) const
{
// Read the object header
preload_object(dest, stream);
// Did we get an object or null?
if (!dest)
return;
auto &properties = dest->get_properties();
if (m_is_file)
{
// Read the object's size, and create a new BitBufferSegment to
// ensure that data is only read from inside this region.
const auto object_size =
stream.read<uint32_t>() - bitsizeof<uint32_t>::value;
auto *object_buffer = stream.buffer().segment(
stream.tell(), object_size
);
stream.seek(stream.tell() + object_size, false);
auto object_stream = BitStream(*object_buffer);
// Instead of loading properties sequentially, the file format specifies
// the hash of a property before writing its value, so we just need to
// iterate for how ever many properties the object has declared.
for (std::size_t i = 0;
i < properties.get_property_count(); i++)
{
// Read the property's size, and create a new BitBufferSegment to
// ensure that data is only read from inside this region.
const auto property_size =
object_stream.read<uint32_t>() - bitsizeof<uint32_t>::value;
auto *property_buffer = object_buffer->segment(
object_stream.tell(), property_size
);
auto property_stream = BitStream(*property_buffer);
object_stream.seek(object_stream.tell() + property_size, false);
// Get the property to load based on it's hash, and then load
// it's value.
const auto property_hash = property_stream.read<uint32_t>();
auto &prop = properties.get_property(property_hash);
load_property(prop, property_stream);
delete property_buffer;
}
delete object_buffer;
}
else
{
// Load properties in the order that the object's class declared them
for (auto it = properties.begin();
it != properties.end(); ++it)
load_property(*it, stream);
}
// All properties on this object have been set now, so call the
// created handler on the object
dest->on_created();
// Re-align the stream so that our position lies on a byte
stream.seek(BitStream::stream_pos(stream.tell().as_bytes(), 0), false);
}
void SerializerBinary::load_property(pclass::PropertyBase* prop, BitStreamBase &stream) const
void SerializerBinary::load_property(pclass::PropertyBase &prop, BitStream &stream) const
{
auto &property_type = prop.get_type();
if (prop.is_dynamic())
{
auto &dynamic_property =
dynamic_cast<pclass::DynamicPropertyBase &>(prop);
// How many elements are there in this dynamic property?
const auto element_count = stream.read<uint32_t>();
dynamic_property.set_element_count(element_count);
// Load each of the elements
for (uint16_t i = 0; i < element_count; i++)
{
// Is this a collection of pointers?
if (dynamic_property.is_pointer())
{
// Is the property a collection of pointers to other objects?
if (property_type.get_kind() == pclass::Type::kind::CLASS)
{
// Read the object as a nested object
pclass::PropertyClass *object = nullptr;
load_object(object, stream);
if (object)
{
// Does the nested object inherit from the property's type?
const auto &object_type =
dynamic_cast<const pclass::ClassTypeBase &>(
object->get_type()
);
if (!object_type.inherits(dynamic_property.get_type()))
{
std::ostringstream oss;
oss << "Type '" << object_type.get_name() << "' does not derive from "
<< "property type '" << prop.get_type().get_name() << "'.";
throw runtime_error(oss.str());
}
}
dynamic_property.set_object(object, i);
}
else
// Read the value as normal (let the property deal with dereferencing)
dynamic_property.read_value_from(stream, i);
}
else
// If the value isn't a pointer, and it's not dynamic, just read it as a value
dynamic_property.read_value_from(stream, i);
}
}
else if (prop.is_pointer())
{
// Does this property hold a pointer to another object?
if (property_type.get_kind() == pclass::Type::kind::CLASS)
{
// Read the object as a nested object
pclass::PropertyClass *object = nullptr;
load_object(object, stream);
if (object)
{
// Does the nested object inherit from the property's type?
const auto &object_type =
dynamic_cast<const pclass::ClassTypeBase &>(
object->get_type()
);
if (!object_type.inherits(prop.get_type()))
{
std::ostringstream oss;
oss << "Type '" << object_type.get_name() << "' does not derive from "
<< "property type '" << prop.get_type().get_name() << "'.";
throw runtime_error(oss.str());
}
}
prop.set_object(object);
}
else
// Read the value as normal (let the property deal with dereferencing)
prop.read_value_from(stream);
}
else
// If the value isn't a pointer, and it's not dynamic, just read it as a value
prop.read_value_from(stream);
}
}
}

View File

@ -6,34 +6,39 @@
namespace ki
{
BitStream::stream_pos::stream_pos(const intmax_t byte, const int bit)
BitBufferBase::buffer_pos::buffer_pos(const uint32_t byte, const int bit)
{
m_byte = byte;
set_bit(bit);
}
BitStream::stream_pos::stream_pos(const stream_pos& cp)
BitBufferBase::buffer_pos::buffer_pos(const buffer_pos& cp)
{
m_byte = cp.m_byte;
set_bit(cp.m_bit);
}
intmax_t BitStream::stream_pos::as_bits() const
uint32_t BitBufferBase::buffer_pos::as_bytes() const
{
return m_byte + (m_bit > 0 ? 1 : 0);
}
uint32_t BitBufferBase::buffer_pos::as_bits() const
{
return (m_byte * 8) + m_bit;
}
intmax_t BitStream::stream_pos::get_byte() const
uint32_t BitBufferBase::buffer_pos::get_byte() const
{
return m_byte;
}
uint8_t BitStream::stream_pos::get_bit() const
uint8_t BitBufferBase::buffer_pos::get_bit() const
{
return m_bit;
}
void BitStream::stream_pos::set_bit(int bit)
void BitBufferBase::buffer_pos::set_bit(int bit)
{
if (bit < 0)
{
@ -50,170 +55,179 @@ namespace ki
m_bit = bit;
}
BitStream::stream_pos BitStream::stream_pos::operator+(
const stream_pos &rhs) const
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator+(
const buffer_pos &rhs) const
{
return stream_pos(
return buffer_pos(
m_byte + rhs.m_byte, m_bit + rhs.m_bit
);
}
BitStream::stream_pos BitStream::stream_pos::operator-(
const stream_pos &rhs) const
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator-(
const buffer_pos &rhs) const
{
return stream_pos(
return buffer_pos(
m_byte - rhs.m_byte, m_bit - rhs.m_bit
);
}
BitStream::stream_pos BitStream::stream_pos::operator+(
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator+(
const int& rhs) const
{
return stream_pos(
return buffer_pos(
m_byte, m_bit + rhs
);
}
BitStream::stream_pos BitStream::stream_pos::operator-(
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator-(
const int& rhs) const
{
return stream_pos(
return buffer_pos(
m_byte, m_bit - rhs
);
}
BitStream::stream_pos& BitStream::stream_pos::operator+=(
const stream_pos rhs)
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator+=(
const buffer_pos rhs)
{
m_byte += rhs.m_byte;
set_bit(m_bit + rhs.m_bit);
return *this;
}
BitStream::stream_pos& BitStream::stream_pos::operator-=(
const stream_pos rhs)
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator-=(
const buffer_pos rhs)
{
m_byte -= rhs.m_byte;
set_bit(m_bit - rhs.m_bit);
return *this;
}
BitStream::stream_pos& BitStream::stream_pos::operator+=(const int bits)
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator+=(const int bits)
{
set_bit(m_bit + bits);
return *this;
}
BitStream::stream_pos& BitStream::stream_pos::operator-=(const int bits)
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator-=(const int bits)
{
set_bit(m_bit - bits);
return *this;
}
BitStream::stream_pos& BitStream::stream_pos::operator++()
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator++()
{
set_bit(m_bit + 1);
return *this;
}
BitStream::stream_pos& BitStream::stream_pos::operator--()
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator--()
{
set_bit(m_bit - 1);
return *this;
}
BitStream::stream_pos BitStream::stream_pos::operator++(int increment)
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator++(int increment)
{
auto copy(*this);
++(*this);
return copy;
}
BitStream::stream_pos BitStream::stream_pos::operator--(int increment)
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator--(int increment)
{
auto copy(*this);
--(*this);
return copy;
}
void BitStreamBase::write_copy(uint8_t *src, const std::size_t bitsize)
BitBufferSegment *BitBufferBase::segment(
const buffer_pos from, 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);
return new BitBufferSegment(*this, from, bitsize);
}
void BitStreamBase::read_copy(uint8_t *dst, const std::size_t bitsize)
void BitBufferBase::write_copy(uint8_t *src,
buffer_pos position, const std::size_t bitsize)
{
// Copy all whole bytes
const auto bytes = bitsize / 8;
auto read_bytes = 0;
while (read_bytes < bytes)
auto bits_left = bitsize;
while (bits_left > 0)
{
dst[read_bytes] = read<uint8_t>();
read_bytes++;
const auto bits = bits_left >= 8 ? 8 : bits_left;
write<uint8_t>(*(src++), position, bits);
bits_left -= bits;
position += bits;
}
// 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)
void BitBufferBase::read_copy(uint8_t *dst,
buffer_pos position, const std::size_t bitsize) const
{
m_buffer = new uint8_t[buffer_size] { 0 };
auto bits_left = bitsize;
while (bits_left > 0)
{
const auto bits = bits_left >= 8 ? 8 : bits_left;
*(dst++) = read<uint8_t>(position, bits);
bits_left -= bits;
position += bits;
}
}
BitBuffer::BitBuffer(const size_t buffer_size)
{
m_buffer = new uint8_t[buffer_size]{0};
m_buffer_size = buffer_size;
m_position = stream_pos(0, 0);
}
BitStream::~BitStream()
BitBuffer::~BitBuffer()
{
delete[] m_buffer;
m_buffer = nullptr;
m_buffer_size = 0;
}
BitStream::stream_pos BitStream::tell() const
BitBuffer::BitBuffer(const BitBuffer& that)
{
return m_position;
m_buffer_size = that.m_buffer_size;
m_buffer = new uint8_t[m_buffer_size]{0};
std::memcpy(m_buffer, that.m_buffer, m_buffer_size);
}
void BitStream::seek(const stream_pos position)
BitBuffer &BitBuffer::operator=(const BitBuffer &that)
{
m_position = position;
validate_buffer();
m_buffer_size = that.m_buffer_size;
m_buffer = new uint8_t[m_buffer_size]{0};
std::memcpy(m_buffer, that.m_buffer, m_buffer_size);
return (*this);
}
std::size_t BitStream::capacity() const
BitBuffer::BitBuffer(uint8_t *buffer, const std::size_t buffer_size)
{
m_buffer = buffer;
m_buffer_size = buffer_size;
}
std::size_t BitBuffer::size() const
{
return m_buffer_size;
}
const uint8_t *BitStream::data() const
void BitBuffer::resize(const std::size_t new_size)
{
auto *new_buffer = new uint8_t[new_size]{0};
std::memcpy(new_buffer, m_buffer, new_size >= m_buffer_size ? m_buffer_size : new_size);
delete[] m_buffer;
m_buffer = new_buffer;
m_buffer_size = new_size;
}
uint8_t *BitBuffer::data() const
{
return m_buffer;
}
uint64_t BitStream::read(const uint8_t bits)
uint64_t BitBuffer::read(buffer_pos position, const uint8_t bits) const
{
// Do we have these bits available to read?
if ((m_position + bits).as_bits() > m_buffer_size * 8)
{
std::ostringstream oss;
oss << "Not enough data in buffer to read "
<< static_cast<uint16_t>(bits) << "bits.";
throw runtime_error(oss.str());
}
// Iterate until we've read all of the bits
uint64_t value = 0;
auto unread_bits = bits;
@ -221,14 +235,14 @@ namespace ki
{
// 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 uint8_t bits_available = (8 - 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();
const uint8_t bit_mask = (1 << bit_count) - 1 << 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();
const uint8_t bits_value = (m_buffer[position.get_byte()] & bit_mask) >> position.get_bit();
// Position the value of the bits we just read based on how many bits of the value
// we've already read
@ -239,13 +253,14 @@ namespace ki
unread_bits -= bit_count;
// Move forward the number of bits we just read
seek(tell() + bit_count);
position += bit_count;
}
return value;
}
void BitStream::write(const uint64_t value, const uint8_t bits)
void BitBuffer::write(const uint64_t value,
buffer_pos position, const uint8_t bits)
{
// Iterate until we've written all of the bits
auto unwritten_bits = bits;
@ -253,7 +268,7 @@ namespace ki
{
// 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 uint8_t bits_available = (8 - 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
@ -263,47 +278,149 @@ namespace ki
// 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();
value_byte <<= position.get_bit();
// Write the bits into the byte we're currently at
m_buffer[m_position.get_byte()] |= value_byte;
m_buffer[position.get_byte()] |= value_byte;
unwritten_bits -= bit_count;
// Move forward the number of bits we just wrote
seek(tell() + bit_count);
position += bit_count;
}
}
void BitStream::expand_buffer()
BitBufferSegment::BitBufferSegment(BitBufferBase& buffer,
const buffer_pos from, const std::size_t bitsize)
{
// Work out a new buffer size
const auto minimum_bits = static_cast<uint64_t>(
std::log2(m_position.get_byte())
) + 1;
auto new_size = (2 << minimum_bits) + 2;
if (new_size < m_buffer_size)
new_size = std::numeric_limits<size_t>::max();
m_buffer = &buffer;
// Has the buffer reached maximum size?
if (new_size == m_buffer_size)
throw runtime_error("Buffer cannot be expanded as it has reached maximum size.");
// Is this a valid position to start reading from?
if (from.get_byte() >= m_buffer->size())
throw runtime_error("Cannot create segment of buffer from position outside the original buffer.");
m_from = from;
// Allocate a new buffer, copy everything over, and then delete the old buffer
auto *new_buffer = new uint8_t[new_size] { 0 };
std::memcpy(new_buffer, m_buffer, m_buffer_size);
delete[] m_buffer;
m_buffer = new_buffer;
m_buffer_size = new_size;
// Is the size valid?
if ((m_from + bitsize).as_bytes() > m_buffer->size())
{
std::ostringstream oss;
oss << "Not enough data in original buffer to create segment of size: "
<< bitsize;
throw runtime_error(oss.str());
}
m_bitsize = bitsize;
}
void BitStream::validate_buffer()
std::size_t BitBufferSegment::size() const
{
// Make sure we haven't underflowed
if (m_position.get_byte() < 0)
throw runtime_error("Position of buffer is less than 0!");
return buffer_pos(m_bitsize / 8, m_bitsize % 8).as_bytes();
}
void BitBufferSegment::resize(std::size_t new_size)
{
throw runtime_error("A BitBufferSegment cannot be resized.");
}
uint8_t* BitBufferSegment::data() const
{
return &m_buffer->data()[m_from.get_byte()];
}
BitBufferSegment *BitBufferSegment::segment(
const buffer_pos from, const std::size_t bitsize)
{
return new BitBufferSegment(*m_buffer, m_from + from, bitsize);
}
uint64_t BitBufferSegment::read(
const buffer_pos position, const uint8_t bits) const
{
return m_buffer->read(m_from + position, bits);
}
void BitBufferSegment::write(const uint64_t value,
const buffer_pos position, const uint8_t bits)
{
m_buffer->write(value, m_from + position, bits);
}
BitStream::BitStream(BitBufferBase &buffer)
{
m_buffer = &buffer;
m_position = stream_pos(0, 0);
}
BitStream::~BitStream()
{
m_buffer = nullptr;
}
BitStream::BitStream(const BitStream &that)
{
m_buffer = that.m_buffer;
m_position = that.m_position;
}
BitStream& BitStream::operator=(const BitStream &that)
{
m_buffer = that.m_buffer;
m_position = that.m_position;
return *this;
}
BitBufferBase::buffer_pos BitStream::tell() const
{
return m_position;
}
void BitStream::seek(const stream_pos position, const bool expand)
{
if (expand)
expand_buffer(position);
m_position = position;
}
std::size_t BitStream::capacity() const
{
return m_buffer->size();
}
BitBufferBase &BitStream::buffer() const
{
return *m_buffer;
}
void BitStream::read_copy(uint8_t *dst, const std::size_t bitsize)
{
m_buffer->read_copy(dst, m_position, bitsize);
m_position += bitsize;
}
void BitStream::write_copy(uint8_t *src, const std::size_t bitsize)
{
expand_buffer(m_position + bitsize);
m_buffer->write_copy(src, m_position, bitsize);
m_position += bitsize;
}
void BitStream::expand_buffer(const stream_pos position) const
{
// Expand the buffer if we've overflowed
if (m_position.get_byte() >= m_buffer_size)
expand_buffer();
if (position.as_bytes() >= m_buffer->size())
{
// Work out a new buffer size
const auto minimum_bits = static_cast<uint64_t>(
std::log2(position.get_byte())
) + 1;
auto new_size = (2 << minimum_bits) + 2;
if (new_size < m_buffer->size())
new_size = std::numeric_limits<size_t>::max();
// Has the buffer reached maximum size?
if (new_size == m_buffer->size())
throw runtime_error("Buffer cannot be expanded as it has reached maximum size.");
// Resize the buffer
m_buffer->resize(new_size);
}
}
}

View File

@ -19,8 +19,8 @@
#define KI_TEST_BITSTREAM_U32 0x0A090807
#define KI_TEST_BITSTREAM_U64 0x1211100F0E0D0C0BL
#define KI_TEST_WRITE_BUI(n) bit_stream->write<bui<n>>(KI_TEST_BITSTREAM_BUI##n)
#define KI_TEST_READ_BUI(n) bit_stream->read<bui<n>>() == KI_TEST_BITSTREAM_BUI##n
#define KI_TEST_WRITE_BUI(n) bit_stream.write<bui<n>>(KI_TEST_BITSTREAM_BUI##n)
#define KI_TEST_READ_BUI(n) bit_stream.read<bui<n>>() == KI_TEST_BITSTREAM_BUI##n
using namespace ki;
@ -31,55 +31,50 @@ TEST_CASE("BitStream::stream_pos Functionality", "[bit-stream]")
SECTION("Increment single bit")
{
++position;
if (position.get_byte() != 1 || position.get_bit() != 5)
FAIL();
SUCCEED();
REQUIRE(position.get_byte() == 1);
REQUIRE(position.get_bit() == 5);
}
SECTION("Decrement single bit")
{
--position;
if (position.get_byte() != 1 || position.get_bit() != 3)
FAIL();
SUCCEED();
REQUIRE(position.get_byte() == 1);
REQUIRE(position.get_bit() == 3);
}
SECTION("Increment bits and move to next byte")
{
position += 4;
if (position.get_byte() != 2 || position.get_bit() != 0)
FAIL();
SUCCEED();
REQUIRE(position.get_byte() == 2);
REQUIRE(position.get_bit() == 0);
}
SECTION("Decrement bits and move to previous byte")
{
position -= 5;
if (position.get_byte() != 0 || position.get_bit() != 7)
FAIL();
SUCCEED();
REQUIRE(position.get_byte() == 0);
REQUIRE(position.get_bit() == 7);
}
SECTION("Increment byte")
{
position += BitStream::stream_pos(1, 0);
if (position.get_byte() != 2 || position.get_bit() != 4)
FAIL();
SUCCEED();
REQUIRE(position.get_byte() == 2);
REQUIRE(position.get_bit() == 4);
}
SECTION("Decrement byte")
{
position -= BitStream::stream_pos(1, 0);
if (position.get_byte() != 0 || position.get_bit() != 4)
FAIL();
SUCCEED();
REQUIRE(position.get_byte() == 0);
REQUIRE(position.get_bit() == 4);
}
}
TEST_CASE("BitStream Functionality", "[bit-stream]")
{
auto *bit_stream = new BitStream();
BitBuffer bit_buffer;
auto bit_stream = BitStream(bit_buffer);
SECTION("Writing values with a size less than 8 bits")
{
@ -94,65 +89,60 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
KI_TEST_WRITE_BUI(4);
// Make sure tell is reporting the right position
auto position = bit_stream->tell();
if (position.get_byte() != 4 || position.get_bit() != 0)
FAIL();
auto position = bit_stream.tell();
REQUIRE(position.get_byte() == 4);
REQUIRE(position.get_bit() == 0);
const auto size = position.get_byte();
// Validate what we've got here with a hand-written sample
std::ifstream sample("samples/bitstream1.bin", std::ios::binary);
if (!sample.is_open())
FAIL();
REQUIRE(sample.is_open());
// Load the sample data and compare
auto *sample_data = new char[size + 1]{ 0 };
auto *sample_data = new char[size];
auto *stream_data = bit_buffer.data();
sample.read(sample_data, size);
if (strcmp(sample_data, (char *)bit_stream->data()) != 0)
FAIL();
REQUIRE(memcmp(sample_data, stream_data, size) == 0);
// Free resources
delete[] sample_data;
SUCCEED();
}
SECTION("Writing values with a size greater than 8 bits")
{
// Write some values
bit_stream->write<bool>(KI_TEST_BITSTREAM_BOOL);
bit_stream->write<uint8_t>(KI_TEST_BITSTREAM_U8);
bit_stream->write<uint16_t>(KI_TEST_BITSTREAM_U16);
bit_stream->write<bui<24>>(KI_TEST_BITSTREAM_U24);
bit_stream->write<uint32_t>(KI_TEST_BITSTREAM_U32);
bit_stream->write<uint64_t>(KI_TEST_BITSTREAM_U64);
bit_stream.write<bool>(KI_TEST_BITSTREAM_BOOL);
bit_stream.write<uint8_t>(KI_TEST_BITSTREAM_U8);
bit_stream.write<uint16_t>(KI_TEST_BITSTREAM_U16);
bit_stream.write<bui<24>>(KI_TEST_BITSTREAM_U24);
bit_stream.write<uint32_t>(KI_TEST_BITSTREAM_U32);
bit_stream.write<uint64_t>(KI_TEST_BITSTREAM_U64);
// Make sure tell is reporting the right position
auto position = bit_stream->tell();
if (position.get_byte() != 19 || position.get_bit() != 0)
FAIL();
auto position = bit_stream.tell();
REQUIRE(position.get_byte() == 19);
REQUIRE(position.get_bit() == 0);
const auto size = position.get_byte();
// Validate what we've got here with a hand-written sample
std::ifstream sample("samples/bitstream2.bin", std::ios::binary);
if (!sample.is_open())
FAIL();
REQUIRE(sample.is_open());
// Load the sample data and compare
auto *sample_data = new char[size + 1]{ 0 };
auto *stream_data = bit_buffer.data();
auto *sample_data = new char[size];
sample.read(sample_data, size);
if (strcmp(sample_data, (char *)bit_stream->data()) != 0)
FAIL();
REQUIRE(memcmp(sample_data, stream_data, size) == 0);
// Free resources
delete[] sample_data;
SUCCEED();
}
SECTION("Reading values with a size lower than 8 bits")
{
// Open a previously hand-written sample
std::ifstream sample("samples/bitstream1.bin", std::ios::binary);
if (!sample.is_open())
FAIL();
REQUIRE(sample.is_open());
// Load the sample data into the bit stream
const auto begin = sample.tellg();
@ -160,35 +150,25 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
const auto end = sample.tellg();
const size_t size = end - begin;
sample.seekg(std::ios::beg);
sample.read((char *)bit_stream->data(), size);
auto *stream_data = reinterpret_cast<char *>(bit_buffer.data());
sample.read(stream_data, size);
// Read the values and check they are what we are expecting
if (!KI_TEST_READ_BUI(1))
FAIL();
if (!KI_TEST_READ_BUI(2))
FAIL();
if (!KI_TEST_READ_BUI(3))
FAIL();
if (!KI_TEST_READ_BUI(4))
FAIL();
if (!KI_TEST_READ_BUI(5))
FAIL();
if (!KI_TEST_READ_BUI(6))
FAIL();
if (!KI_TEST_READ_BUI(7))
FAIL();
if (!KI_TEST_READ_BUI(4))
FAIL();
SUCCEED();
REQUIRE(KI_TEST_READ_BUI(1));
REQUIRE(KI_TEST_READ_BUI(2));
REQUIRE(KI_TEST_READ_BUI(3));
REQUIRE(KI_TEST_READ_BUI(4));
REQUIRE(KI_TEST_READ_BUI(5));
REQUIRE(KI_TEST_READ_BUI(6));
REQUIRE(KI_TEST_READ_BUI(7));
REQUIRE(KI_TEST_READ_BUI(4));
}
SECTION("Reading values with a size greater than 8 bits")
{
// Open a previously hand-written sample
std::ifstream sample("samples/bitstream2.bin", std::ios::binary);
if (!sample.is_open())
FAIL();
REQUIRE(sample.is_open());
// Load the sample data into the bit stream
const auto begin = sample.tellg();
@ -196,63 +176,39 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
const auto end = sample.tellg();
const size_t size = end - begin;
sample.seekg(std::ios::beg);
sample.read((char *)bit_stream->data(), size);
auto *stream_data = reinterpret_cast<char *>(bit_buffer.data());
sample.read(stream_data, size);
// Read the values and check they are what we are expecting
if (bit_stream->read<bool>() != KI_TEST_BITSTREAM_BOOL)
FAIL();
if (bit_stream->read<uint8_t>() != KI_TEST_BITSTREAM_U8)
FAIL();
if (bit_stream->read<uint16_t>() != KI_TEST_BITSTREAM_U16)
FAIL();
if (bit_stream->read<bui<24>>() != KI_TEST_BITSTREAM_U24)
FAIL();
if (bit_stream->read<uint32_t>() != KI_TEST_BITSTREAM_U32)
FAIL();
if (bit_stream->read<uint64_t>() != KI_TEST_BITSTREAM_U64)
FAIL();
SUCCEED();
REQUIRE(bit_stream.read<bool>() == KI_TEST_BITSTREAM_BOOL);
REQUIRE(bit_stream.read<uint8_t>() == KI_TEST_BITSTREAM_U8);
REQUIRE(bit_stream.read<uint16_t>() == KI_TEST_BITSTREAM_U16);
REQUIRE(bit_stream.read<bui<24>>() == KI_TEST_BITSTREAM_U24);
REQUIRE(bit_stream.read<uint32_t>() == KI_TEST_BITSTREAM_U32);
REQUIRE(bit_stream.read<uint64_t>() == KI_TEST_BITSTREAM_U64);
}
SECTION("Buffer underflow")
SECTION("Overwriting values")
{
try
{
// Attempt to set the position less than 0
bit_stream->seek(BitStream::stream_pos(-1, 0));
FAIL();
}
catch (std::runtime_error &e)
{
// An exception was thrown, which is intended behaviour
SUCCEED();
}
bit_stream.write<bui<2>>(0b11);
bit_stream.seek(BitStream::stream_pos(0, 0));
}
// Free resources
delete bit_stream;
}
TEST_CASE("BitStream buffer expansion", "[bit-stream]")
{
// Create a very small buffer that we know is going to expand
auto *bit_stream = new BitStream(1);
auto bit_buffer = BitBuffer(1);
auto bit_stream = BitStream(bit_buffer);
// Write a byte, and then check if the capacity grows
bit_stream->write<uint8_t>(0x55, 8);
if (bit_stream->capacity() != 6)
FAIL();
bit_stream.write<uint8_t>(0x55, 8);
REQUIRE(bit_stream.capacity() == 6);
// Go back to the first byte, and make sure what we wrote stuck
// around after the expansion
bit_stream->seek(BitStream::stream_pos(0, 0));
if (bit_stream->read<uint8_t>(8) != 0x55)
FAIL();
// Free resources
delete bit_stream;
SUCCEED();
bit_stream.seek(BitStream::stream_pos(0, 0));
REQUIRE(bit_stream.read<uint8_t>(8) == 0x55);
}
/**