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 class PropertyBase
{ {
public: 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, PropertyBase(PropertyClass &object,
const std::string &name, const Type &type); const std::string &name, const Type &type);
virtual ~PropertyBase() { } virtual ~PropertyBase() {}
PropertyBase(PropertyClass &object,
const PropertyBase &that);
std::string get_name() const; std::string get_name() const;
hash_t get_name_hash() const; hash_t get_name_hash() const;
@ -27,9 +34,10 @@ namespace pclass
virtual Value get_value() const = 0; virtual Value get_value() const = 0;
virtual const PropertyClass *get_object() const = 0; virtual const PropertyClass *get_object() const = 0;
virtual void set_object(PropertyClass *object) = 0;
virtual void write_value_to(BitStreamBase &stream) const = 0; virtual void write_value_to(BitStream &stream) const = 0;
virtual void read_value_from(BitStreamBase &stream) = 0; virtual void read_value_from(BitStream &stream) = 0;
private: private:
std::string m_name; std::string m_name;
@ -44,22 +52,32 @@ namespace pclass
class DynamicPropertyBase : public PropertyBase class DynamicPropertyBase : public PropertyBase
{ {
public: 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, DynamicPropertyBase(PropertyClass &object,
const std::string &name, const Type &type); const std::string &name, const Type &type);
virtual ~DynamicPropertyBase() {} virtual ~DynamicPropertyBase() {}
DynamicPropertyBase(PropertyClass &object,
const DynamicPropertyBase &that);
bool is_dynamic() const override; bool is_dynamic() const override;
virtual std::size_t get_element_count() const = 0; 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; Value get_value() const final override;
const PropertyClass *get_object() const final override; const PropertyClass *get_object() const final override;
void write_value_to(BitStreamBase &stream) const final override; void set_object(PropertyClass *object) final override;
void read_value_from(BitStreamBase &stream) 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 Value get_value(int index) const = 0;
virtual const PropertyClass *get_object(int index) const = 0; virtual const PropertyClass *get_object(int index) const = 0;
virtual void write_value_to(BitStreamBase &stream, int index) const = 0; virtual void set_object(PropertyClass *object, int index) = 0;
virtual void read_value_from(BitStreamBase &stream, 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 ki::pclass::PropertyClass
#define _KI_PCLASS_CONSTRUCTOR(derived) \ #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) \ #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 DERIVED_PCLASS(derived, base) class derived : public base
#define PCLASS(n) DERIVED_PCLASS(n, _KI_PCLASS) #define PCLASS(n) DERIVED_PCLASS(n, _KI_PCLASS)
@ -23,11 +29,22 @@ _KI_PCLASS_CONSTRUCTOR(derived) \
_KI_PCLASS_CONSTRUCTOR(derived) \ _KI_PCLASS_CONSTRUCTOR(derived) \
_KI_PCLASS_CONSTRUCT_BASE(base) _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 TYPE(n) type_system.get_type(n)
#define INIT_PROPERTY(identifier, type) \ #define INIT_PROPERTY(identifier, type) \
, identifier(*this, #identifier, TYPE(type)) , identifier(*this, #identifier, TYPE(type))
#define INIT_PROPERTY_COPY(identifier) \
, identifier(*this, that.##identifier)
#define INIT_PROPERTY_VALUE(identifier, type, value) \ #define INIT_PROPERTY_VALUE(identifier, type, value) \
, identifier(*this, #identifier, TYPE(type), value) , identifier(*this, #identifier, TYPE(type), value)
@ -43,18 +60,28 @@ namespace pclass
*/ */
class PropertyClass class PropertyClass
{ {
friend PropertyBase;
public: public:
explicit PropertyClass(const Type &type, const TypeSystem &type_system); 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; const Type &get_type() const;
PropertyList &get_properties(); PropertyList &get_properties();
const PropertyList &get_properties() const; const PropertyList &get_properties() const;
virtual PropertyClass *copy() const;
virtual void on_created() const {}
protected:
void add_property(PropertyBase &prop);
private: private:
const Type *m_type; const Type *m_type;
PropertyList *m_properties; PropertyList m_properties;
}; };
} }
} }

View File

@ -7,6 +7,7 @@ namespace ki
{ {
namespace pclass namespace pclass
{ {
class PropertyClass;
class PropertyBase; class PropertyBase;
/** /**
@ -14,25 +15,75 @@ namespace pclass
*/ */
class PropertyList class PropertyList
{ {
friend PropertyBase; friend PropertyClass;
public: 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; std::size_t get_property_count() const;
bool has_property(const std::string &name) const; bool has_property(const std::string &name) const;
bool has_property(hash_t hash) const; bool has_property(hash_t hash) const;
PropertyBase &get_property(int index);
const PropertyBase &get_property(int index) const; const PropertyBase &get_property(int index) const;
PropertyBase &get_property(const std::string &name);
const PropertyBase &get_property(const std::string &name) const; const PropertyBase &get_property(const std::string &name) const;
PropertyBase &get_property(hash_t hash);
const PropertyBase &get_property(hash_t hash) const; const PropertyBase &get_property(hash_t hash) const;
iterator begin();
const_iterator begin() const; const_iterator begin() const;
iterator end();
const_iterator end() const; const_iterator end() const;
protected: protected:
void add_property(PropertyBase *prop); void add_property(PropertyBase &prop);
private: private:
std::vector<PropertyBase *> m_properties; std::vector<PropertyBase *> m_properties;

View File

@ -10,6 +10,7 @@ namespace pclass
template <typename ValueT> template <typename ValueT>
class StaticProperty; class StaticProperty;
/// @cond DOXYGEN_SKIP
/** /**
* A helper utility that provides the right implementation of construct() * A helper utility that provides the right implementation of construct()
* and get_object() based on characteristics of type: ValueT. * and get_object() based on characteristics of type: ValueT.
@ -28,6 +29,13 @@ namespace pclass
return ValueT(); 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) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does not derive from PropertyClass, and so, this property is not // 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." "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 * - A pointer; but
* - does not derive from PropertyClass * - does not derive from PropertyClass
* *
* This should construct to a nullptr, and throw an exception * This should:
* when get_object()is called. * - Construct to a nullptr; and
* - Throw an exception when get_object() is called.
*/ */
template <typename ValueT> template <typename ValueT>
struct value_object_helper< struct value_object_helper<
@ -66,6 +84,13 @@ namespace pclass
return nullptr; 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) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does not derive from PropertyClass, and so, this property is not // 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." "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 * - A pointer; and
* - does derive from PropertyClass * - does derive from PropertyClass
* *
* This should construct to a nullptr, and return a pointer to * This should:
* a ValueT instance (as a PropertyClass *) when get_object() is called. * - Construct to a nullptr; and
* - Return a pointer to a ValueT instance (as a PropertyClass *)
* when get_object() is called.
*/ */
template <typename ValueT> template <typename ValueT>
struct value_object_helper< struct value_object_helper<
@ -104,12 +140,26 @@ namespace pclass
return nullptr; 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) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does derive from PropertyClass, and we have a pointer to an instance // ValueT does derive from PropertyClass, and we have a pointer to an instance
// of ValueT, so we can cast down to a PropertyClass pointer. // of ValueT, so we can cast down to a PropertyClass pointer.
return dynamic_cast<const PropertyClass *>(prop.m_value); 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 * - Not a pointer; and
* - does derive from PropertyClass * - does derive from PropertyClass
* *
* This should construct an instance of ValueT by passing the property * This should:
* type and the type's type system, and return a pointer to a ValueT * - Construct an instance of ValueT by passing the property
* instance (as a PropertyClass *) when get_object() is called. * 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> template <typename ValueT>
struct value_object_helper< struct value_object_helper<
@ -143,12 +195,29 @@ namespace pclass
return ValueT(type, type.get_type_system()); 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) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
// ValueT does derive from PropertyClass, and we have an instance of ValueT, // ValueT does derive from PropertyClass, and we have an instance of ValueT,
// so we can cast down to a PropertyClass pointer. // so we can cast down to a PropertyClass pointer.
return dynamic_cast<const PropertyClass *>(&prop.m_value); 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 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); 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)); prop.get_type().read_from(stream, Value(prop.m_value));
} }
}; };
/** /**
* Specialization for when ValueT is a pointer. * Specialization for when ValueT is a pointer.
* *
* Dereference the pointer before creating Value instances. * Dereference the pointer before creating Value instances.
* This is so that the Value stores a pointer to a ValueT instance, * This is so that the Value stores a pointer to a ValueT instance,
* rather than storing a pointer to a pointer. * rather than storing a pointer to a pointer.
*/ */
template <typename ValueT> template <typename ValueT>
struct value_rw_helper< struct value_rw_helper<
ValueT, ValueT,
@ -187,12 +256,12 @@ namespace pclass
>::type >::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); 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)); prop.get_type().read_from(stream, Value(*prop.m_value));
} }
@ -210,21 +279,32 @@ namespace pclass
return value_object_helper<ValueT>::construct(type); 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) static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
{ {
return value_object_helper<ValueT>::get_object(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); 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); value_rw_helper<ValueT>::read(prop, stream);
} }
}; };
/// @endcond
/** /**
* TODO: Documentation * TODO: Documentation
@ -237,6 +317,10 @@ namespace pclass
friend value_rw_helper<ValueT>; friend value_rw_helper<ValueT>;
public: 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, StaticProperty(PropertyClass &object,
const std::string &name, const Type &type) const std::string &name, const Type &type)
: PropertyBase(object, name, type) : PropertyBase(object, name, type)
@ -250,34 +334,19 @@ namespace pclass
m_value = value; m_value = value;
} }
constexpr bool is_dynamic() const override StaticProperty(PropertyClass &object, const StaticProperty<ValueT> &that)
{ : PropertyBase(object, that)
return false; , m_value(value_helper<ValueT>::copy(that))
} {}
constexpr bool is_pointer() const override constexpr bool is_pointer() const override
{ {
return std::is_pointer<ValueT>::value; 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); return false;
}
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;
} }
Value get_value() const override Value get_value() const override
@ -285,12 +354,52 @@ namespace pclass
return m_value; 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; 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; return &m_value;
} }

View File

@ -7,159 +7,307 @@ namespace ki
{ {
namespace pclass namespace pclass
{ {
// Forward declare for our helpers
template <typename ValueT> 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: 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) 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 std::size_t get_element_count() const override
{ {
return this->size(); return this->size();
} }
void set_element_count(const std::size_t size) override
{
this->resize(size);
}
Value get_value(int index) const override Value get_value(int index) const override
{ {
if (index < 0 || index >= this->size()) if (index < 0 || index >= this->size())
throw runtime_error("Index out of bounds."); throw runtime_error("Index out of bounds.");
return this->at(index); 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 const PropertyClass *get_object(const int index) const override
{ {
// We aren't holding an object at all, whoever called this is mistaken. return vector_value_helper<ValueT>::get_object(*this, index);
throw runtime_error(
"Tried calling get_object(index) on a property that does not store an object."
);
} }
};
template <typename ValueT> void set_object(PropertyClass *object, int index) override
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
{ {
if (index < 0 || index >= this->size()) return vector_value_helper<ValueT>::set_object(*this, object, index);
throw runtime_error("Index out of bounds.");
return dynamic_cast<PropertyClass *>(&this->at(index));
} }
};
template <typename ValueT> void write_value_to(BitStream &stream, const int index) const override
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
{ {
if (index < 0 || index >= this->size()) vector_value_helper<ValueT>::write_value_to(*this, stream, index);
throw runtime_error("Index out of bounds."); }
return dynamic_cast<PropertyClass *>(this->at(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/Property.h"
#include "ki/pclass/types/Type.h" #include "ki/pclass/types/Type.h"
#include "ki/pclass/PropertyClass.h" #include "ki/pclass/PropertyClass.h"
#include "ki/util/exception.h"
namespace ki namespace ki
{ {
@ -45,20 +44,20 @@ namespace pclass
return new ClassT(*this, get_type_system()); 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 &object = dynamic_cast<const PropertyClass &>(value.get<ClassT>());
const auto &properties = object.get_properties(); const auto &properties = object.get_properties();
for (auto it = properties.begin(); it != properties.end(); ++it) 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 &object = dynamic_cast<PropertyClass &>(value.get<ClassT>());
auto &properties = object.get_properties(); auto &properties = object.get_properties();
for (auto it = properties.begin(); it != properties.end(); ++it) 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 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 // Reinterpret the reference as a reference to an integer
const uint_type &v = *( const uint_type &v = *(
@ -35,7 +35,7 @@ namespace pclass
typename std::enable_if<std::is_floating_point<ValueT>::value>::type typename std::enable_if<std::is_floating_point<ValueT>::value>::type
> >
{ {
static void read_from(BitStreamBase &stream, ValueT &value) static void read_from(BitStream &stream, ValueT &value)
{ {
// Reinterpret the reference as a reference to an integer // Reinterpret the reference as a reference to an integer
uint_type &v = *( uint_type &v = *(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,181 +3,343 @@
#include <type_traits> #include <type_traits>
#include <sstream> #include <sstream>
#include "ki/util/BitTypes.h" #include "ki/util/BitTypes.h"
#include "exception.h"
#define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000 #define KI_BITBUFFER_DEFAULT_SIZE 0x2000
namespace ki 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: 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); explicit buffer_pos(uint32_t byte = 0, int bit = 0);
stream_pos(const stream_pos &cp); buffer_pos(const buffer_pos &cp);
intmax_t as_bits() const; uint32_t as_bytes() const;
intmax_t get_byte() const; uint32_t as_bits() const;
uint32_t get_byte() const;
uint8_t get_bit() const; uint8_t get_bit() const;
stream_pos operator +(const stream_pos &rhs) const; buffer_pos operator +(const buffer_pos &rhs) const;
stream_pos operator -(const stream_pos &rhs) const; buffer_pos operator -(const buffer_pos &rhs) const;
stream_pos operator +(const int &rhs) const; buffer_pos operator +(const int &rhs) const;
stream_pos operator -(const int &rhs) const; buffer_pos operator -(const int &rhs) const;
stream_pos &operator +=(stream_pos lhs); buffer_pos &operator +=(buffer_pos lhs);
stream_pos &operator -=(stream_pos lhs); buffer_pos &operator -=(buffer_pos lhs);
stream_pos &operator +=(int bits); buffer_pos &operator +=(int bits);
stream_pos &operator -=(int bits); buffer_pos &operator -=(int bits);
stream_pos &operator ++(); buffer_pos &operator ++();
stream_pos &operator --(); buffer_pos &operator --();
stream_pos operator ++(int increment); buffer_pos operator ++(int increment);
stream_pos operator --(int increment); buffer_pos operator --(int increment);
private: private:
intmax_t m_byte; uint32_t m_byte;
uint8_t m_bit; uint8_t m_bit;
void set_bit(int 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. * @returns The size of the buffer in bytes.
* @param position The new position of the stream.
*/ */
virtual void seek(stream_pos position) = 0; virtual std::size_t size() const = 0;
/** /**
* @returns The current size of the internal buffer. * Resize the buffer to the specified size.
*/ * If the current size is larger than the specified new size, then the data is truncated.
virtual std::size_t capacity() const = 0; * @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. * Reads a value from the buffer.
* @param[in] bits The number of bits to read. Defaults to the bitsize of IntegerT. * @tparam ValueT The type of value (must be an integral type).
* @returns The value read from the buffer. * @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.
template < * @returns The n-bit value retrieved from the buffer.
typename IntegerT, * @throws ki::runtime_error Buffer is not large enough to read the specified number of bits at the specified position.
typename = std::enable_if<is_integral<IntegerT>::value> */
> template <typename ValueT>
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value) 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. * Writes a value to the buffer.
* @tparam IntegerT The type of value (must be an integral type). * @tparam ValueT The type of value (must be an integral type).
* @param[in] value The value to write. * @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 < template <typename ValueT>
typename IntegerT, void write(const ValueT value, const buffer_pos position,
typename = std::enable_if<is_integral<IntegerT>::value> const uint8_t bits = bitsizeof<ValueT>::value)
>
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::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. * Copy memory from this buffer into an external buffer.
* @param[in] src The buffer to copy data from. * @param[out] dst The destination buffer to copy data to.
* @param[in] bitsize The number of bits to copy from the src buffer. * @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. * Copy memory from an external buffer into this buffer at the specified position.
* @param[out] dst The destination buffer to copy data to. * @param[in] src The source buffer to copy data from.
* @param[in] bitsize The number of bits to copy into the dst buffer. * @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: protected:
virtual uint64_t read(uint8_t bits) = 0; virtual uint64_t read(buffer_pos position, uint8_t bits) const = 0;
virtual void write(uint64_t value, uint8_t bits) = 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. * A read/write-able stream of bits.
*/ */
class BitStream : public BitStreamBase class BitStream
{ {
public: 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(); virtual ~BitStream();
stream_pos tell() const override; BitStream(const BitStream &that);
void seek(stream_pos position) override; BitStream &operator=(const BitStream &that);
std::size_t capacity() const override;
const uint8_t *data() const override;
/** /**
* @copydoc BitStreamBase::read<IntegerT>(uint8_t) * @returns The stream's current position.
* @throws ki::runtime_error Not enough data available to read the specified number of bits. */
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 < template <
typename IntegerT, typename ValueT,
typename = std::enable_if<is_integral<IntegerT>::value> 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 < template <
typename IntegerT, typename ValueT,
typename = std::enable_if<is_integral<IntegerT>::value> 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; * Copy memory from the BitStream's buffer into an external buffer at the current position.
void write(uint64_t value, uint8_t bits) override; * @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: private:
uint8_t * m_buffer; BitBufferBase *m_buffer;
std::size_t m_buffer_size;
stream_pos m_position; stream_pos m_position;
void expand_buffer(); /**
void validate_buffer(); * 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;
* TODO: Documentation
*/
class BitStreamSection : public BitStreamBase
{
public:
explicit BitStreamSection(BitStreamBase &stream, std::size_t size);
private:
BitStreamBase &m_stream;
stream_pos m_position;
std::size_t m_size;
}; };
} }

View File

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

View File

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

View File

@ -7,13 +7,19 @@ namespace pclass
PropertyClass::PropertyClass(const Type &type, const TypeSystem &type_system) PropertyClass::PropertyClass(const Type &type, const TypeSystem &type_system)
{ {
m_type = &type; m_type = &type;
m_properties = new PropertyList();
} }
PropertyClass::~PropertyClass() PropertyClass::PropertyClass(const PropertyClass &that)
{ {
// Delete the list of properties m_type = that.m_type;
delete m_properties; 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 const Type& PropertyClass::get_type() const
@ -23,12 +29,22 @@ namespace pclass
PropertyList &PropertyClass::get_properties() PropertyList &PropertyClass::get_properties()
{ {
return *m_properties; return m_properties;
} }
const PropertyList& PropertyClass::get_properties() const 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 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 std::size_t PropertyList::get_property_count() const
{ {
return m_properties.size(); 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) return m_property_name_lookup.find(name)
!= m_property_name_lookup.end(); != m_property_name_lookup.end();
@ -25,7 +105,7 @@ namespace pclass
!= m_property_hash_lookup.end(); != 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()) if (index >= 0 && index < m_properties.size())
return *m_properties[index]; return *m_properties[index];
@ -36,7 +116,7 @@ namespace pclass
throw runtime_error(oss.str()); 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); const auto it = m_property_name_lookup.find(name);
if (it != m_property_name_lookup.end()) if (it != m_property_name_lookup.end())
@ -47,7 +127,7 @@ namespace pclass
throw runtime_error(oss.str()); 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); const auto it = m_property_hash_lookup.find(hash);
if (it != m_property_hash_lookup.end()) if (it != m_property_hash_lookup.end())
@ -60,48 +140,87 @@ namespace pclass
throw runtime_error(oss.str()); 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 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 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 // 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; std::ostringstream oss;
oss << "A property has already been added with name: '" oss << "A property has already been added with name: '"
<< prop->get_name() << "'."; << prop.get_name() << "'.";
throw runtime_error(oss.str()); throw runtime_error(oss.str());
} }
// Check for hash collisions // 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()); const auto &other = get_property(prop.get_full_hash());
// This pointer is going to be lost, so delete it now
delete prop;
std::ostringstream oss; std::ostringstream oss;
oss << "Cannot add property '" << prop->get_name() << "'. " oss << "Cannot add property '" << prop.get_name() << "'. "
<< "Hash collision with property '" << other.get_name() << "'."; << "Hash collision with property '" << other.get_name() << "'.";
throw runtime_error(oss.str()); throw runtime_error(oss.str());
} }
// Add the property to lookups // Add the property to lookups
m_properties.push_back(prop); m_properties.push_back(&prop);
m_property_name_lookup[prop->get_name()] = prop; m_property_name_lookup[prop.get_name()] = &prop;
m_property_hash_lookup[prop->get_full_hash()] = prop; m_property_hash_lookup[prop.get_full_hash()] = &prop;
} }
} }
} }

View File

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

View File

@ -15,22 +15,23 @@ namespace serialization
m_root_object = nullptr; 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 // Write the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS)) if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS))
stream.write<uint32_t>(static_cast<uint32_t>(m_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, // 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)) if (FLAG_IS_SET(m_flags, flags::COMPRESSED))
{
stream.write<bool>(false);
stream.write<uint32_t>(0); stream.write<uint32_t>(0);
}
// Write the object to the stream // Write the object to the stream
const auto start_pos = stream.tell();
m_root_object = object; m_root_object = object;
save_object(object, stream); save_object(object, stream);
@ -39,17 +40,17 @@ namespace serialization
{ {
// Remember where the ending of the data is // Remember where the ending of the data is
const auto end_pos = stream.tell(); const auto end_pos = stream.tell();
const auto size_bits = (end_pos - (start_pos + bitsizeof<uint32_t>::value)).as_bits(); const auto size_bits = (end_pos - start_pos).as_bits();
const auto size_bytes = (size_bits / 8) + (size_bits % 8 > 0 ? 1 : 0); const auto size_bytes = (end_pos - start_pos).as_bytes();
// Make a copy of the uncompressed data // 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); stream.read_copy(uncompressed, size_bits);
// Setup compression // Setup compression
static const std::size_t bufsize = 1024; static const std::size_t bufsize = 1024;
auto *temp_buffer = new uint8_t[bufsize]; auto *temp_buffer = new uint8_t[bufsize]{0};
std::vector<uint8_t> compressed(size_bytes); std::vector<uint8_t> compressed;
z_stream z; z_stream z;
z.zalloc = nullptr; z.zalloc = nullptr;
z.zfree = nullptr; z.zfree = nullptr;
@ -93,23 +94,26 @@ namespace serialization
temp_buffer, temp_buffer + bufsize - z.avail_out); temp_buffer, temp_buffer + bufsize - z.avail_out);
deflateEnd(&z); 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 // Cleanup temporary buffers
delete[] uncompressed; delete[] uncompressed;
delete[] temp_buffer; 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 we have an object, write the type hash, otherwise, write NULL (0).
if (object) if (object)
@ -118,7 +122,7 @@ namespace serialization
stream.write<uint32_t>(NULL); 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 // Write any object headers
presave_object(object, stream); presave_object(object, stream);
@ -154,9 +158,13 @@ namespace serialization
stream.write(size_bits); stream.write(size_bits);
stream.seek(end_pos); 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 // Remember where we started writing the property data
const auto start_pos = stream.tell(); const auto start_pos = stream.tell();
@ -166,52 +174,52 @@ namespace serialization
{ {
// Reserve space to put the size of the property // Reserve space to put the size of the property
stream.write<uint32_t>(0); 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) // Is the property dynamic? (holding more than one value)
auto &property_type = prop->get_type(); auto &property_type = prop.get_type();
if (prop->is_dynamic()) if (prop.is_dynamic())
{ {
// Cast the property to a DynamicPropertyBase // Cast the property to a DynamicPropertyBase
const auto *dynamic_property = const auto &dynamic_property =
dynamic_cast<const pclass::DynamicPropertyBase *>(prop); dynamic_cast<const pclass::DynamicPropertyBase &>(prop);
// Write the number of elements // 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 // 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? // Is this a collection of pointers?
if (prop->is_pointer()) if (prop.is_pointer())
{ {
// Is the property a collection of pointers to other objects? // Is the property a collection of pointers to other objects?
if (property_type.get_kind() == pclass::Type::kind::CLASS) if (property_type.get_kind() == pclass::Type::kind::CLASS)
// Write the value as a nested object // Write the value as a nested object
save_object(dynamic_property->get_object(i), stream); save_object(dynamic_property.get_object(i), stream);
else else
// Write the value as normal (let the property deal with dereferencing) // 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 else
// If the value isn't a pointer, and it's not dynamic, just write it as a value // 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? // Does this property hold a pointer to another object?
if (property_type.get_kind() == pclass::Type::kind::CLASS) if (property_type.get_kind() == pclass::Type::kind::CLASS)
// Write the value as a nested object // Write the value as a nested object
save_object(prop->get_object(), stream); save_object(prop.get_object(), stream);
else else
// Write the value as normal (let the property deal with dereferencing) // Write the value as normal (let the property deal with dereferencing)
prop->write_value_to(stream); prop.write_value_to(stream);
} }
else else
// If the value isn't a pointer, and it's not dynamic, just write it as a value // 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 // Finish writing the property header by writing the length
if (m_is_file) 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 // Read the serializer flags
if (FLAG_IS_SET(m_flags, flags::WRITE_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 // Decompress the contents of the stream
if (FLAG_IS_SET(m_flags, flags::COMPRESSED)) 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 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; m_byte = byte;
set_bit(bit); 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; m_byte = cp.m_byte;
set_bit(cp.m_bit); 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; 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; return m_byte;
} }
uint8_t BitStream::stream_pos::get_bit() const uint8_t BitBufferBase::buffer_pos::get_bit() const
{ {
return m_bit; return m_bit;
} }
void BitStream::stream_pos::set_bit(int bit) void BitBufferBase::buffer_pos::set_bit(int bit)
{ {
if (bit < 0) if (bit < 0)
{ {
@ -50,170 +55,179 @@ namespace ki
m_bit = bit; m_bit = bit;
} }
BitStream::stream_pos BitStream::stream_pos::operator+( BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator+(
const stream_pos &rhs) const const buffer_pos &rhs) const
{ {
return stream_pos( return buffer_pos(
m_byte + rhs.m_byte, m_bit + rhs.m_bit 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 stream_pos &rhs) const const buffer_pos &rhs) const
{ {
return stream_pos( return buffer_pos(
m_byte - rhs.m_byte, m_bit - rhs.m_bit 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 const int& rhs) const
{ {
return stream_pos( return buffer_pos(
m_byte, m_bit + rhs m_byte, m_bit + rhs
); );
} }
BitStream::stream_pos BitStream::stream_pos::operator-( BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator-(
const int& rhs) const const int& rhs) const
{ {
return stream_pos( return buffer_pos(
m_byte, m_bit - rhs m_byte, m_bit - rhs
); );
} }
BitStream::stream_pos& BitStream::stream_pos::operator+=( BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator+=(
const stream_pos rhs) const buffer_pos rhs)
{ {
m_byte += rhs.m_byte; m_byte += rhs.m_byte;
set_bit(m_bit + rhs.m_bit); set_bit(m_bit + rhs.m_bit);
return *this; return *this;
} }
BitStream::stream_pos& BitStream::stream_pos::operator-=( BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator-=(
const stream_pos rhs) const buffer_pos rhs)
{ {
m_byte -= rhs.m_byte; m_byte -= rhs.m_byte;
set_bit(m_bit - rhs.m_bit); set_bit(m_bit - rhs.m_bit);
return *this; 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); set_bit(m_bit + bits);
return *this; 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); set_bit(m_bit - bits);
return *this; return *this;
} }
BitStream::stream_pos& BitStream::stream_pos::operator++() BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator++()
{ {
set_bit(m_bit + 1); set_bit(m_bit + 1);
return *this; return *this;
} }
BitStream::stream_pos& BitStream::stream_pos::operator--() BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator--()
{ {
set_bit(m_bit - 1); set_bit(m_bit - 1);
return *this; return *this;
} }
BitStream::stream_pos BitStream::stream_pos::operator++(int increment) BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator++(int increment)
{ {
auto copy(*this); auto copy(*this);
++(*this); ++(*this);
return copy; return copy;
} }
BitStream::stream_pos BitStream::stream_pos::operator--(int increment) BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator--(int increment)
{ {
auto copy(*this); auto copy(*this);
--(*this); --(*this);
return copy; 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 return new BitBufferSegment(*this, from, bitsize);
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) void BitBufferBase::write_copy(uint8_t *src,
buffer_pos position, const std::size_t bitsize)
{ {
// Copy all whole bytes auto bits_left = bitsize;
const auto bytes = bitsize / 8; while (bits_left > 0)
auto read_bytes = 0;
while (read_bytes < bytes)
{ {
dst[read_bytes] = read<uint8_t>(); const auto bits = bits_left >= 8 ? 8 : bits_left;
read_bytes++; 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_buffer_size = buffer_size;
m_position = stream_pos(0, 0);
} }
BitStream::~BitStream() BitBuffer::~BitBuffer()
{ {
delete[] m_buffer; 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; m_buffer_size = that.m_buffer_size;
validate_buffer(); 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; 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; 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 // Iterate until we've read all of the bits
uint64_t value = 0; uint64_t value = 0;
auto unread_bits = bits; 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 // 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 // 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; const auto bit_count = unread_bits < bits_available ? unread_bits : bits_available;
// Find the bit-mask based on how many bits are being read // 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 // 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 // Position the value of the bits we just read based on how many bits of the value
// we've already read // we've already read
@ -239,13 +253,14 @@ namespace ki
unread_bits -= bit_count; unread_bits -= bit_count;
// Move forward the number of bits we just read // Move forward the number of bits we just read
seek(tell() + bit_count); position += bit_count;
} }
return value; 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 // Iterate until we've written all of the bits
auto unwritten_bits = 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 // 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 // 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; 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 // 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 // Get the bits from the value and position them at the current bit position
uint8_t value_byte = ((value & bit_mask) >> written_bits) & 0xFF; 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 // 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; unwritten_bits -= bit_count;
// Move forward the number of bits we just wrote // 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 m_buffer = &buffer;
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();
// Has the buffer reached maximum size? // Is this a valid position to start reading from?
if (new_size == m_buffer_size) if (from.get_byte() >= m_buffer->size())
throw runtime_error("Buffer cannot be expanded as it has reached maximum 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 // Is the size valid?
auto *new_buffer = new uint8_t[new_size] { 0 }; if ((m_from + bitsize).as_bytes() > m_buffer->size())
std::memcpy(new_buffer, m_buffer, m_buffer_size); {
delete[] m_buffer; std::ostringstream oss;
m_buffer = new_buffer; oss << "Not enough data in original buffer to create segment of size: "
m_buffer_size = new_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 return buffer_pos(m_bitsize / 8, m_bitsize % 8).as_bytes();
if (m_position.get_byte() < 0) }
throw runtime_error("Position of buffer is less than 0!");
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 // Expand the buffer if we've overflowed
if (m_position.get_byte() >= m_buffer_size) if (position.as_bytes() >= m_buffer->size())
expand_buffer(); {
// 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_U32 0x0A090807
#define KI_TEST_BITSTREAM_U64 0x1211100F0E0D0C0BL #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_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_READ_BUI(n) bit_stream.read<bui<n>>() == KI_TEST_BITSTREAM_BUI##n
using namespace ki; using namespace ki;
@ -31,55 +31,50 @@ TEST_CASE("BitStream::stream_pos Functionality", "[bit-stream]")
SECTION("Increment single bit") SECTION("Increment single bit")
{ {
++position; ++position;
if (position.get_byte() != 1 || position.get_bit() != 5) REQUIRE(position.get_byte() == 1);
FAIL(); REQUIRE(position.get_bit() == 5);
SUCCEED();
} }
SECTION("Decrement single bit") SECTION("Decrement single bit")
{ {
--position; --position;
if (position.get_byte() != 1 || position.get_bit() != 3) REQUIRE(position.get_byte() == 1);
FAIL(); REQUIRE(position.get_bit() == 3);
SUCCEED();
} }
SECTION("Increment bits and move to next byte") SECTION("Increment bits and move to next byte")
{ {
position += 4; position += 4;
if (position.get_byte() != 2 || position.get_bit() != 0) REQUIRE(position.get_byte() == 2);
FAIL(); REQUIRE(position.get_bit() == 0);
SUCCEED();
} }
SECTION("Decrement bits and move to previous byte") SECTION("Decrement bits and move to previous byte")
{ {
position -= 5; position -= 5;
if (position.get_byte() != 0 || position.get_bit() != 7) REQUIRE(position.get_byte() == 0);
FAIL(); REQUIRE(position.get_bit() == 7);
SUCCEED();
} }
SECTION("Increment byte") SECTION("Increment byte")
{ {
position += BitStream::stream_pos(1, 0); position += BitStream::stream_pos(1, 0);
if (position.get_byte() != 2 || position.get_bit() != 4) REQUIRE(position.get_byte() == 2);
FAIL(); REQUIRE(position.get_bit() == 4);
SUCCEED();
} }
SECTION("Decrement byte") SECTION("Decrement byte")
{ {
position -= BitStream::stream_pos(1, 0); position -= BitStream::stream_pos(1, 0);
if (position.get_byte() != 0 || position.get_bit() != 4) REQUIRE(position.get_byte() == 0);
FAIL(); REQUIRE(position.get_bit() == 4);
SUCCEED();
} }
} }
TEST_CASE("BitStream Functionality", "[bit-stream]") 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") 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); KI_TEST_WRITE_BUI(4);
// Make sure tell is reporting the right position // Make sure tell is reporting the right position
auto position = bit_stream->tell(); auto position = bit_stream.tell();
if (position.get_byte() != 4 || position.get_bit() != 0) REQUIRE(position.get_byte() == 4);
FAIL(); REQUIRE(position.get_bit() == 0);
const auto size = position.get_byte(); const auto size = position.get_byte();
// Validate what we've got here with a hand-written sample // Validate what we've got here with a hand-written sample
std::ifstream sample("samples/bitstream1.bin", std::ios::binary); std::ifstream sample("samples/bitstream1.bin", std::ios::binary);
if (!sample.is_open()) REQUIRE(sample.is_open());
FAIL();
// Load the sample data and compare // 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); sample.read(sample_data, size);
if (strcmp(sample_data, (char *)bit_stream->data()) != 0) REQUIRE(memcmp(sample_data, stream_data, size) == 0);
FAIL();
// Free resources // Free resources
delete[] sample_data; delete[] sample_data;
SUCCEED();
} }
SECTION("Writing values with a size greater than 8 bits") SECTION("Writing values with a size greater than 8 bits")
{ {
// Write some values // Write some values
bit_stream->write<bool>(KI_TEST_BITSTREAM_BOOL); bit_stream.write<bool>(KI_TEST_BITSTREAM_BOOL);
bit_stream->write<uint8_t>(KI_TEST_BITSTREAM_U8); bit_stream.write<uint8_t>(KI_TEST_BITSTREAM_U8);
bit_stream->write<uint16_t>(KI_TEST_BITSTREAM_U16); bit_stream.write<uint16_t>(KI_TEST_BITSTREAM_U16);
bit_stream->write<bui<24>>(KI_TEST_BITSTREAM_U24); bit_stream.write<bui<24>>(KI_TEST_BITSTREAM_U24);
bit_stream->write<uint32_t>(KI_TEST_BITSTREAM_U32); bit_stream.write<uint32_t>(KI_TEST_BITSTREAM_U32);
bit_stream->write<uint64_t>(KI_TEST_BITSTREAM_U64); bit_stream.write<uint64_t>(KI_TEST_BITSTREAM_U64);
// Make sure tell is reporting the right position // Make sure tell is reporting the right position
auto position = bit_stream->tell(); auto position = bit_stream.tell();
if (position.get_byte() != 19 || position.get_bit() != 0) REQUIRE(position.get_byte() == 19);
FAIL(); REQUIRE(position.get_bit() == 0);
const auto size = position.get_byte(); const auto size = position.get_byte();
// Validate what we've got here with a hand-written sample // Validate what we've got here with a hand-written sample
std::ifstream sample("samples/bitstream2.bin", std::ios::binary); std::ifstream sample("samples/bitstream2.bin", std::ios::binary);
if (!sample.is_open()) REQUIRE(sample.is_open());
FAIL();
// Load the sample data and compare // 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); sample.read(sample_data, size);
if (strcmp(sample_data, (char *)bit_stream->data()) != 0) REQUIRE(memcmp(sample_data, stream_data, size) == 0);
FAIL();
// Free resources // Free resources
delete[] sample_data; delete[] sample_data;
SUCCEED();
} }
SECTION("Reading values with a size lower than 8 bits") SECTION("Reading values with a size lower than 8 bits")
{ {
// Open a previously hand-written sample // Open a previously hand-written sample
std::ifstream sample("samples/bitstream1.bin", std::ios::binary); std::ifstream sample("samples/bitstream1.bin", std::ios::binary);
if (!sample.is_open()) REQUIRE(sample.is_open());
FAIL();
// Load the sample data into the bit stream // Load the sample data into the bit stream
const auto begin = sample.tellg(); const auto begin = sample.tellg();
@ -160,35 +150,25 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
const auto end = sample.tellg(); const auto end = sample.tellg();
const size_t size = end - begin; const size_t size = end - begin;
sample.seekg(std::ios::beg); 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 // Read the values and check they are what we are expecting
if (!KI_TEST_READ_BUI(1)) REQUIRE(KI_TEST_READ_BUI(1));
FAIL(); REQUIRE(KI_TEST_READ_BUI(2));
if (!KI_TEST_READ_BUI(2)) REQUIRE(KI_TEST_READ_BUI(3));
FAIL(); REQUIRE(KI_TEST_READ_BUI(4));
if (!KI_TEST_READ_BUI(3)) REQUIRE(KI_TEST_READ_BUI(5));
FAIL(); REQUIRE(KI_TEST_READ_BUI(6));
if (!KI_TEST_READ_BUI(4)) REQUIRE(KI_TEST_READ_BUI(7));
FAIL(); REQUIRE(KI_TEST_READ_BUI(4));
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();
} }
SECTION("Reading values with a size greater than 8 bits") SECTION("Reading values with a size greater than 8 bits")
{ {
// Open a previously hand-written sample // Open a previously hand-written sample
std::ifstream sample("samples/bitstream2.bin", std::ios::binary); std::ifstream sample("samples/bitstream2.bin", std::ios::binary);
if (!sample.is_open()) REQUIRE(sample.is_open());
FAIL();
// Load the sample data into the bit stream // Load the sample data into the bit stream
const auto begin = sample.tellg(); const auto begin = sample.tellg();
@ -196,63 +176,39 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
const auto end = sample.tellg(); const auto end = sample.tellg();
const size_t size = end - begin; const size_t size = end - begin;
sample.seekg(std::ios::beg); 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 // Read the values and check they are what we are expecting
if (bit_stream->read<bool>() != KI_TEST_BITSTREAM_BOOL) REQUIRE(bit_stream.read<bool>() == KI_TEST_BITSTREAM_BOOL);
FAIL(); REQUIRE(bit_stream.read<uint8_t>() == KI_TEST_BITSTREAM_U8);
if (bit_stream->read<uint8_t>() != KI_TEST_BITSTREAM_U8) REQUIRE(bit_stream.read<uint16_t>() == KI_TEST_BITSTREAM_U16);
FAIL(); REQUIRE(bit_stream.read<bui<24>>() == KI_TEST_BITSTREAM_U24);
if (bit_stream->read<uint16_t>() != KI_TEST_BITSTREAM_U16) REQUIRE(bit_stream.read<uint32_t>() == KI_TEST_BITSTREAM_U32);
FAIL(); REQUIRE(bit_stream.read<uint64_t>() == KI_TEST_BITSTREAM_U64);
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();
} }
SECTION("Buffer underflow") SECTION("Overwriting values")
{ {
try bit_stream.write<bui<2>>(0b11);
{ bit_stream.seek(BitStream::stream_pos(0, 0));
// 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();
}
} }
// Free resources
delete bit_stream;
} }
TEST_CASE("BitStream buffer expansion", "[bit-stream]") TEST_CASE("BitStream buffer expansion", "[bit-stream]")
{ {
// Create a very small buffer that we know is going to expand // 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 // Write a byte, and then check if the capacity grows
bit_stream->write<uint8_t>(0x55, 8); bit_stream.write<uint8_t>(0x55, 8);
if (bit_stream->capacity() != 6) REQUIRE(bit_stream.capacity() == 6);
FAIL();
// Go back to the first byte, and make sure what we wrote stuck // Go back to the first byte, and make sure what we wrote stuck
// around after the expansion // around after the expansion
bit_stream->seek(BitStream::stream_pos(0, 0)); bit_stream.seek(BitStream::stream_pos(0, 0));
if (bit_stream->read<uint8_t>(8) != 0x55) REQUIRE(bit_stream.read<uint8_t>(8) == 0x55);
FAIL();
// Free resources
delete bit_stream;
SUCCEED();
} }
/** /**