From 3a22c992b51baf5bf116b1b5dddbd635cbeecf4c Mon Sep 17 00:00:00 2001 From: Joshua Scott Date: Tue, 27 Nov 2018 11:51:56 +0000 Subject: [PATCH] etc: Implement deserialization and object copying Also make BitStream tests use REQUIRE instead of SUCCESS and FAIL for better output. --- include/ki/pclass/Property.h | 32 +- include/ki/pclass/PropertyClass.h | 37 +- include/ki/pclass/PropertyList.h | 57 ++- include/ki/pclass/StaticProperty.h | 193 +++++++-- include/ki/pclass/VectorProperty.h | 404 ++++++++++++------ include/ki/pclass/types/ClassType.h | 9 +- .../pclass/types/FloatingPointPrimitiveType.h | 4 +- .../ki/pclass/types/IntegralPrimitiveType.h | 4 +- include/ki/pclass/types/PrimitiveType.h | 8 +- include/ki/pclass/types/StringPrimitiveType.h | 4 +- include/ki/pclass/types/Type.h | 4 +- include/ki/serialization/SerializerBinary.h | 16 +- include/ki/util/BitStream.h | 364 +++++++++++----- src/pclass/ClassType.cpp | 3 + src/pclass/Property.cpp | 32 +- src/pclass/PropertyClass.cpp | 28 +- src/pclass/PropertyList.cpp | 161 ++++++- src/pclass/Type.cpp | 4 +- src/serialization/SerializerBinary.cpp | 303 +++++++++++-- src/util/BitStream.cpp | 327 +++++++++----- test/src/unit-bitstream.cpp | 174 +++----- 21 files changed, 1563 insertions(+), 605 deletions(-) diff --git a/include/ki/pclass/Property.h b/include/ki/pclass/Property.h index 89959fd..cd54a7b 100644 --- a/include/ki/pclass/Property.h +++ b/include/ki/pclass/Property.h @@ -13,9 +13,16 @@ namespace pclass class PropertyBase { public: + // Do not allow copy assignment. Once a property has been constructed, + // it shouldn't be able to change. + PropertyBase & operator=(const PropertyBase &that) = delete; + PropertyBase(PropertyClass &object, const std::string &name, const Type &type); - virtual ~PropertyBase() { } + virtual ~PropertyBase() {} + + PropertyBase(PropertyClass &object, + const PropertyBase &that); std::string get_name() const; hash_t get_name_hash() const; @@ -27,9 +34,10 @@ namespace pclass virtual Value get_value() const = 0; virtual const PropertyClass *get_object() const = 0; + virtual void set_object(PropertyClass *object) = 0; - virtual void write_value_to(BitStreamBase &stream) const = 0; - virtual void read_value_from(BitStreamBase &stream) = 0; + virtual void write_value_to(BitStream &stream) const = 0; + virtual void read_value_from(BitStream &stream) = 0; private: std::string m_name; @@ -44,22 +52,32 @@ namespace pclass class DynamicPropertyBase : public PropertyBase { public: + // Do not allow copy assignment. Once a property has been constructed, + // it shouldn't be able to change. + DynamicPropertyBase & operator=(const DynamicPropertyBase &that) = delete; + DynamicPropertyBase(PropertyClass &object, const std::string &name, const Type &type); virtual ~DynamicPropertyBase() {} + DynamicPropertyBase(PropertyClass &object, + const DynamicPropertyBase &that); + bool is_dynamic() const override; virtual std::size_t get_element_count() const = 0; + virtual void set_element_count(std::size_t size) = 0; Value get_value() const final override; const PropertyClass *get_object() const final override; - void write_value_to(BitStreamBase &stream) const final override; - void read_value_from(BitStreamBase &stream) final override; + void set_object(PropertyClass *object) final override; + void write_value_to(BitStream &stream) const final override; + void read_value_from(BitStream &stream) final override; virtual Value get_value(int index) const = 0; virtual const PropertyClass *get_object(int index) const = 0; - virtual void write_value_to(BitStreamBase &stream, int index) const = 0; - virtual void read_value_from(BitStreamBase &stream, int index) = 0; + virtual void set_object(PropertyClass *object, int index) = 0; + virtual void write_value_to(BitStream &stream, int index) const = 0; + virtual void read_value_from(BitStream &stream, int index) = 0; }; } } diff --git a/include/ki/pclass/PropertyClass.h b/include/ki/pclass/PropertyClass.h index e0ce38c..90c0e86 100644 --- a/include/ki/pclass/PropertyClass.h +++ b/include/ki/pclass/PropertyClass.h @@ -7,10 +7,16 @@ #define _KI_PCLASS ki::pclass::PropertyClass #define _KI_PCLASS_CONSTRUCTOR(derived) \ -explicit derived(const _KI_TYPE &type, const _KI_TYPE_SYSTEM &type_system) + explicit derived(const _KI_TYPE &type, const _KI_TYPE_SYSTEM &type_system) + +#define _KI_PCLASS_COPY_CONSTRUCTOR(derived) \ + derived(const derived &that) #define _KI_PCLASS_CONSTRUCT_BASE(base) \ -: base(type, type_system) + : base(type, type_system) + +#define _KI_PCLASS_COPY_CONSTRUCT_BASE(base) \ + : base(that) #define DERIVED_PCLASS(derived, base) class derived : public base #define PCLASS(n) DERIVED_PCLASS(n, _KI_PCLASS) @@ -23,11 +29,22 @@ _KI_PCLASS_CONSTRUCTOR(derived) \ _KI_PCLASS_CONSTRUCTOR(derived) \ _KI_PCLASS_CONSTRUCT_BASE(base) +#define PCLASS_COPY_CONSTRUCTOR(derived) \ +_KI_PCLASS_COPY_CONSTRUCTOR(derived) \ + _KI_PCLASS_COPY_CONSTRUCT_BASE(_KI_PCLASS) + +#define DERIVED_PCLASS_COPY_CONSTRUCTOR(derived, base) \ +_KI_PCLASS_COPY_CONSTRUCTOR(derived) \ + _KI_PCLASS_COPY_CONSTRUCT_BASE(base) + #define TYPE(n) type_system.get_type(n) #define INIT_PROPERTY(identifier, type) \ , identifier(*this, #identifier, TYPE(type)) +#define INIT_PROPERTY_COPY(identifier) \ + , identifier(*this, that.##identifier) + #define INIT_PROPERTY_VALUE(identifier, type, value) \ , identifier(*this, #identifier, TYPE(type), value) @@ -43,18 +60,28 @@ namespace pclass */ class PropertyClass { + friend PropertyBase; + public: explicit PropertyClass(const Type &type, const TypeSystem &type_system); - virtual ~PropertyClass(); + virtual ~PropertyClass() {} + + PropertyClass(const PropertyClass &that); + PropertyClass &operator=(const PropertyClass &that); const Type &get_type() const; - PropertyList &get_properties(); const PropertyList &get_properties() const; + virtual PropertyClass *copy() const; + virtual void on_created() const {} + + protected: + void add_property(PropertyBase &prop); + private: const Type *m_type; - PropertyList *m_properties; + PropertyList m_properties; }; } } diff --git a/include/ki/pclass/PropertyList.h b/include/ki/pclass/PropertyList.h index 95b518e..3424a48 100644 --- a/include/ki/pclass/PropertyList.h +++ b/include/ki/pclass/PropertyList.h @@ -7,6 +7,7 @@ namespace ki { namespace pclass { + class PropertyClass; class PropertyBase; /** @@ -14,25 +15,75 @@ namespace pclass */ class PropertyList { - friend PropertyBase; + friend PropertyClass; public: - using const_iterator = std::vector::const_iterator; + /** + * TODO: Documentation + */ + class iterator + { + friend PropertyList; + + public: + PropertyBase &operator*() const; + PropertyBase *operator->() const; + iterator &operator++(); + iterator operator++(int); + bool operator==(const iterator &that) const; + bool operator!=(const iterator &that) const; + + private: + PropertyList *m_list; + int m_index; + + explicit iterator(PropertyList &list, const int index = 0); + }; + + /** + * TODO: Documentation + */ + class const_iterator + { + friend PropertyList; + + public: + const PropertyBase &operator*() const; + const PropertyBase *operator->() const; + const_iterator &operator++(); + const_iterator operator++(int); + bool operator==(const const_iterator &that) const; + bool operator!=(const const_iterator &that) const; + + private: + const PropertyList *m_list; + int m_index; + + explicit const_iterator(const PropertyList &list, const int index = 0); + }; std::size_t get_property_count() const; bool has_property(const std::string &name) const; bool has_property(hash_t hash) const; + PropertyBase &get_property(int index); const PropertyBase &get_property(int index) const; + + PropertyBase &get_property(const std::string &name); const PropertyBase &get_property(const std::string &name) const; + + PropertyBase &get_property(hash_t hash); const PropertyBase &get_property(hash_t hash) const; + iterator begin(); const_iterator begin() const; + + iterator end(); const_iterator end() const; protected: - void add_property(PropertyBase *prop); + void add_property(PropertyBase &prop); private: std::vector m_properties; diff --git a/include/ki/pclass/StaticProperty.h b/include/ki/pclass/StaticProperty.h index d12daee..5ee90ea 100644 --- a/include/ki/pclass/StaticProperty.h +++ b/include/ki/pclass/StaticProperty.h @@ -10,6 +10,7 @@ namespace pclass template class StaticProperty; + /// @cond DOXYGEN_SKIP /** * A helper utility that provides the right implementation of construct() * and get_object() based on characteristics of type: ValueT. @@ -28,6 +29,13 @@ namespace pclass return ValueT(); } + static ValueT copy(const StaticProperty &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 &prop) { // ValueT does not derive from PropertyClass, and so, this property is not @@ -36,6 +44,15 @@ namespace pclass "Tried calling get_object() on a property that does not store an object." ); } + + static void set_object(StaticProperty &prop, PropertyClass *object) + { + // ValueT does not derive from PropertyClass, and so, this property is not + // storing an object. + throw runtime_error( + "Tried calling set_object() on a property that does not store an object." + ); + } }; /** @@ -43,8 +60,9 @@ namespace pclass * - A pointer; but * - does not derive from PropertyClass * - * This should construct to a nullptr, and throw an exception - * when get_object()is called. + * This should: + * - Construct to a nullptr; and + * - Throw an exception when get_object() is called. */ template struct value_object_helper< @@ -66,6 +84,13 @@ namespace pclass return nullptr; } + static ValueT copy(const StaticProperty &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 &prop) { // ValueT does not derive from PropertyClass, and so, this property is not @@ -74,6 +99,15 @@ namespace pclass "Tried calling get_object() on a property that does not store an object." ); } + + static void set_object(StaticProperty &prop, PropertyClass *object) + { + // ValueT does not derive from PropertyClass, and so, this property is not + // storing an object. + throw runtime_error( + "Tried calling set_object() on a property that does not store an object." + ); + } }; /** @@ -81,8 +115,10 @@ namespace pclass * - A pointer; and * - does derive from PropertyClass * - * This should construct to a nullptr, and return a pointer to - * a ValueT instance (as a PropertyClass *) when get_object() is called. + * This should: + * - Construct to a nullptr; and + * - Return a pointer to a ValueT instance (as a PropertyClass *) + * when get_object() is called. */ template struct value_object_helper< @@ -104,12 +140,26 @@ namespace pclass return nullptr; } + static ValueT copy(const StaticProperty &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 &prop) { // ValueT does derive from PropertyClass, and we have a pointer to an instance // of ValueT, so we can cast down to a PropertyClass pointer. return dynamic_cast(prop.m_value); } + + static void set_object(StaticProperty &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(object); + } }; /** @@ -117,9 +167,11 @@ namespace pclass * - Not a pointer; and * - does derive from PropertyClass * - * This should construct an instance of ValueT by passing the property - * type and the type's type system, and return a pointer to a ValueT - * instance (as a PropertyClass *) when get_object() is called. + * This should: + * - Construct an instance of ValueT by passing the property + * type and the type's type system; and + * - Return a pointer to a ValueT instance (as a PropertyClass *) + * when get_object() is called. */ template struct value_object_helper< @@ -143,12 +195,29 @@ namespace pclass return ValueT(type, type.get_type_system()); } + static ValueT copy(const StaticProperty &prop) + { + // Derivitives of PropertyClass implement a clone method that returns + // a clone as a pointer. + ValueT *value_ptr = dynamic_cast(prop.m_value.clone()); + ValueT value = *value_ptr; + delete value_ptr; + return value; + } + static const PropertyClass *get_object(const StaticProperty &prop) { // ValueT does derive from PropertyClass, and we have an instance of ValueT, // so we can cast down to a PropertyClass pointer. return dynamic_cast(&prop.m_value); } + + static void set_object(StaticProperty &prop, PropertyClass *object) + { + // ValueT does derive from PropertyClass, but we don't store a pointer, + // so we need to copy the value in. + prop.m_value = *object; + } }; /** @@ -161,24 +230,24 @@ namespace pclass > struct value_rw_helper { - static void write(const StaticProperty &prop, BitStreamBase &stream) + static void write(const StaticProperty &prop, BitStream &stream) { prop.get_type().write_to(stream, prop.m_value); } - static void read(StaticProperty &prop, BitStreamBase &stream) + static void read(StaticProperty &prop, BitStream &stream) { prop.get_type().read_from(stream, Value(prop.m_value)); } }; /** - * Specialization for when ValueT is a pointer. - * - * Dereference the pointer before creating Value instances. - * This is so that the Value stores a pointer to a ValueT instance, - * rather than storing a pointer to a pointer. - */ + * Specialization for when ValueT is a pointer. + * + * Dereference the pointer before creating Value instances. + * This is so that the Value stores a pointer to a ValueT instance, + * rather than storing a pointer to a pointer. + */ template struct value_rw_helper< ValueT, @@ -187,12 +256,12 @@ namespace pclass >::type > { - static void write(const StaticProperty &prop, BitStreamBase &stream) + static void write(const StaticProperty &prop, BitStream &stream) { prop.get_type().write_to(stream, *prop.m_value); } - static void read(StaticProperty &prop, BitStreamBase &stream) + static void read(StaticProperty &prop, BitStream &stream) { prop.get_type().read_from(stream, Value(*prop.m_value)); } @@ -210,21 +279,32 @@ namespace pclass return value_object_helper::construct(type); } + static ValueT copy(const StaticProperty &prop) + { + return value_object_helper::copy(prop); + } + static const PropertyClass *get_object(const StaticProperty &prop) { return value_object_helper::get_object(prop); } - static void write(const StaticProperty &prop, BitStreamBase &stream) + static void set_object(StaticProperty &prop, PropertyClass *object) + { + value_object_helper::set_object(prop, object); + } + + static void write(const StaticProperty &prop, BitStream &stream) { value_rw_helper::write(prop, stream); } - static void read(StaticProperty &prop, BitStreamBase &stream) + static void read(StaticProperty &prop, BitStream &stream) { value_rw_helper::read(prop, stream); } }; + /// @endcond /** * TODO: Documentation @@ -237,6 +317,10 @@ namespace pclass friend value_rw_helper; public: + // Do not allow copy assignment. Once a property has been constructed, + // it shouldn't be able to change. + StaticProperty &operator=(const StaticProperty &that) = delete; + StaticProperty(PropertyClass &object, const std::string &name, const Type &type) : PropertyBase(object, name, type) @@ -250,34 +334,19 @@ namespace pclass m_value = value; } - constexpr bool is_dynamic() const override - { - return false; - } + StaticProperty(PropertyClass &object, const StaticProperty &that) + : PropertyBase(object, that) + , m_value(value_helper::copy(that)) + {} constexpr bool is_pointer() const override { return std::is_pointer::value; } - void write_value_to(BitStreamBase &stream) const override + constexpr bool is_dynamic() const override { - value_helper::write(*this, stream); - } - - void read_value_from(BitStreamBase &stream) override - { - value_helper::read(*this, stream); - } - - const PropertyClass *get_object() const override - { - return value_helper::get_object(*this); - } - - ValueT &get() - { - return m_value; + return false; } Value get_value() const override @@ -285,12 +354,52 @@ namespace pclass return m_value; } - operator ValueT &() const + const PropertyClass *get_object() const override + { + return value_helper::get_object(*this); + } + + void set_object(PropertyClass *object) override + { + return value_helper::set_object(*this, object); + } + + void write_value_to(BitStream &stream) const override + { + value_helper::write(*this, stream); + } + + void read_value_from(BitStream &stream) override + { + value_helper::read(*this, stream); + } + + ValueT &get() { return m_value; } - ValueT *operator&() const + const ValueT &get() const + { + return m_value; + } + + operator ValueT &() + { + return m_value; + } + + operator const ValueT &() const + { + return m_value; + } + + ValueT *operator&() + { + return &m_value; + } + + const ValueT *operator&() const { return &m_value; } diff --git a/include/ki/pclass/VectorProperty.h b/include/ki/pclass/VectorProperty.h index a675991..cbec460 100644 --- a/include/ki/pclass/VectorProperty.h +++ b/include/ki/pclass/VectorProperty.h @@ -7,159 +7,307 @@ namespace ki { namespace pclass { + // Forward declare for our helpers template - class VectorPropertyBase : public std::vector, 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 &prop, const int index) + { + return ValueT(prop.at(index)); + } + + static const PropertyClass *get_object(const VectorProperty &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 &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 + struct vector_value_object_helper< + ValueT, + typename std::enable_if< + std::is_pointer::value + >::type, + typename std::enable_if< + !std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type + > + { + static ValueT copy(VectorProperty &prop, const int index) + { + return prop.at(index); + } + + static const PropertyClass *get_object(const VectorProperty &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 &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 + struct vector_value_object_helper< + ValueT, + typename std::enable_if< + !std::is_pointer::value + >::type, + typename std::enable_if< + std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type + > + { + static ValueT copy(VectorProperty &prop, const int index) + { + ValueT *value_ptr = dynamic_cast(prop.at(index).copy()); + ValueT value = *value_ptr; + delete value_ptr; + return value; + } + + static const PropertyClass *get_object(const VectorProperty &prop, const int index) + { + if (index < 0 || index >= prop.size()) + throw runtime_error("Index out of bounds."); + return dynamic_cast(&prop.at(index)); + } + + static void set_object(VectorProperty &prop, PropertyClass *object, const int index) + { + if (index < 0 || index >= prop.size()) + throw runtime_error("Index out of bounds."); + prop.at(index) = dynamic_cast(*object); + delete object; + } + }; + + /** + * + */ + template + struct vector_value_object_helper< + ValueT, + typename std::enable_if< + std::is_pointer::value + >::type, + typename std::enable_if< + std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type + > + { + static ValueT copy(VectorProperty &prop, const int index) + { + return prop.at(index); + } + + static const PropertyClass *get_object(const VectorProperty &prop, const int index) + { + if (index < 0 || index >= prop.size()) + throw runtime_error("Index out of bounds."); + return dynamic_cast(prop.at(index)); + } + + static void set_object(VectorProperty &prop, PropertyClass *object, const int index) + { + if (index < 0 || index >= prop.size()) + throw runtime_error("Index out of bounds."); + prop.at(index) = dynamic_cast(object); + } + }; + + /** + * + */ + template < + typename ValueT, + typename IsPointerEnable = void + > + struct vector_value_rw_helper + { + static void write_value_to(const VectorProperty &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 &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 + struct vector_value_rw_helper< + ValueT, + typename std::enable_if::value>::type + > + { + static void write_value_to(const VectorProperty &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 &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 + struct vector_value_helper + { + static ValueT copy(VectorProperty &prop, const int index) + { + return vector_value_object_helper::copy(prop, index); + } + + static const PropertyClass *get_object(const VectorProperty &prop, const int index) + { + return vector_value_object_helper::get_object(prop, index); + } + + static void set_object(VectorProperty &prop, PropertyClass *object, const int index) + { + vector_value_object_helper::set_object(prop, object, index); + } + + static void write_value_to(const VectorProperty &prop, BitStream &stream, const int index) + { + vector_value_rw_helper::write_value_to(prop, stream, index); + } + + static void read_value_from(VectorProperty &prop, BitStream &stream, const int index) + { + vector_value_rw_helper::read_value_from(prop, stream, index); + } + }; + /// @endcond + + template + class VectorProperty : public std::vector, public DynamicPropertyBase { public: - VectorPropertyBase(PropertyClass &object, + // Do not allow copy assignment. Once a property has been constructed, + // it shouldn't be able to change. + VectorProperty &operator=(const VectorProperty &that) = delete; + + VectorProperty(PropertyClass &object, const std::string &name, const Type &type) - : DynamicPropertyBase(object, name, type) { } + : DynamicPropertyBase(object, name, type) + {} + + VectorProperty(PropertyClass &object, + const VectorProperty &that) + : DynamicPropertyBase(object, that) + { + // Copy vector values into this vector + for (auto i = 0; i < this->size(); i++) + this->push_back(vector_value_helper::copy(*this, i)); + } + + constexpr bool is_pointer() const override + { + return std::is_pointer::value; + } std::size_t get_element_count() const override { return this->size(); } + void set_element_count(const std::size_t size) override + { + this->resize(size); + } + Value get_value(int index) const override { if (index < 0 || index >= this->size()) throw runtime_error("Index out of bounds."); return this->at(index); } - }; - - template - class VectorPropertyBase2 : public VectorPropertyBase - { - VectorPropertyBase2(PropertyClass &object, - const std::string &name, const Type &type) - : VectorPropertyBase(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 - class VectorPropertyBase2< - ValueT, - typename std::enable_if< - std::is_pointer::value - >::type - > : public VectorPropertyBase - { - public: - VectorPropertyBase2(PropertyClass &object, - const std::string &name, const Type &type) - : VectorPropertyBase(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 - { - public: - VectorProperty(PropertyClass &object, - const std::string &name, const Type &type) - : VectorPropertyBase2(object, name, type) { } const PropertyClass *get_object(const int index) const override { - // We aren't holding an object at all, whoever called this is mistaken. - throw runtime_error( - "Tried calling get_object(index) on a property that does not store an object." - ); + return vector_value_helper::get_object(*this, index); } - }; - template - class VectorProperty< - ValueT, - typename std::enable_if< - std::is_base_of< - PropertyClass, - typename std::remove_pointer::type - >::value - >::type, - typename std::enable_if< - !std::is_pointer::value - >::type - > : public VectorPropertyBase2 - { - public: - VectorProperty(PropertyClass &object, - const std::string &name, const Type &type) - : VectorPropertyBase2(object, name, type) { } - - const PropertyClass *get_object(const int index) const override + void set_object(PropertyClass *object, int index) override { - if (index < 0 || index >= this->size()) - throw runtime_error("Index out of bounds."); - return dynamic_cast(&this->at(index)); + return vector_value_helper::set_object(*this, object, index); } - }; - template - class VectorProperty< - ValueT, - typename std::enable_if< - std::is_base_of< - PropertyClass, - typename std::remove_pointer::type - >::value - >::type, - typename std::enable_if< - std::is_pointer::value - >::type - > : public VectorPropertyBase2 - { - public: - VectorProperty(PropertyClass &object, - const std::string &name, const Type &type) - : VectorPropertyBase2(object, name, type) { } - - const PropertyClass *get_object(const int index) const override + void write_value_to(BitStream &stream, const int index) const override { - if (index < 0 || index >= this->size()) - throw runtime_error("Index out of bounds."); - return dynamic_cast(this->at(index)); + vector_value_helper::write_value_to(*this, stream, index); + } + + void read_value_from(BitStream &stream, const int index) override + { + vector_value_helper::read_value_from(*this, stream, index); } }; } diff --git a/include/ki/pclass/types/ClassType.h b/include/ki/pclass/types/ClassType.h index 8989096..49fc575 100644 --- a/include/ki/pclass/types/ClassType.h +++ b/include/ki/pclass/types/ClassType.h @@ -4,7 +4,6 @@ #include "ki/pclass/Property.h" #include "ki/pclass/types/Type.h" #include "ki/pclass/PropertyClass.h" -#include "ki/util/exception.h" namespace ki { @@ -45,20 +44,20 @@ namespace pclass return new ClassT(*this, get_type_system()); } - void write_to(BitStreamBase &stream, const Value &value) const override + void write_to(BitStream &stream, const Value &value) const override { const auto &object = dynamic_cast(value.get()); const auto &properties = object.get_properties(); for (auto it = properties.begin(); it != properties.end(); ++it) - (*it)->write_value_to(stream); + it->write_value_to(stream); } - void read_from(BitStreamBase &stream, Value &value) const override + void read_from(BitStream &stream, Value &value) const override { auto &object = dynamic_cast(value.get()); auto &properties = object.get_properties(); for (auto it = properties.begin(); it != properties.end(); ++it) - (*it)->read_value_from(stream); + it->read_value_from(stream); } }; } diff --git a/include/ki/pclass/types/FloatingPointPrimitiveType.h b/include/ki/pclass/types/FloatingPointPrimitiveType.h index 26fe484..41fa848 100644 --- a/include/ki/pclass/types/FloatingPointPrimitiveType.h +++ b/include/ki/pclass/types/FloatingPointPrimitiveType.h @@ -12,7 +12,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void write_to(BitStreamBase &stream, const ValueT &value) + static void write_to(BitStream &stream, const ValueT &value) { // Reinterpret the reference as a reference to an integer const uint_type &v = *( @@ -35,7 +35,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void read_from(BitStreamBase &stream, ValueT &value) + static void read_from(BitStream &stream, ValueT &value) { // Reinterpret the reference as a reference to an integer uint_type &v = *( diff --git a/include/ki/pclass/types/IntegralPrimitiveType.h b/include/ki/pclass/types/IntegralPrimitiveType.h index e4c75f6..7fb7302 100644 --- a/include/ki/pclass/types/IntegralPrimitiveType.h +++ b/include/ki/pclass/types/IntegralPrimitiveType.h @@ -12,7 +12,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void write_to(BitStreamBase &stream, const ValueT &value) + static void write_to(BitStream &stream, const ValueT &value) { stream.write(value); } @@ -24,7 +24,7 @@ namespace pclass typename std::enable_if::value>::type > { - static void read_from(BitStreamBase &stream, ValueT &value) + static void read_from(BitStream &stream, ValueT &value) { value = stream.read(); } diff --git a/include/ki/pclass/types/PrimitiveType.h b/include/ki/pclass/types/PrimitiveType.h index 921d620..55d3b88 100644 --- a/include/ki/pclass/types/PrimitiveType.h +++ b/include/ki/pclass/types/PrimitiveType.h @@ -11,7 +11,7 @@ namespace pclass template struct PrimitiveTypeWriter { - static void write_to(BitStreamBase &stream, const ValueT &value) + static void write_to(BitStream &stream, const ValueT &value) { // Provide a compiler error if this is not specialized static_assert( @@ -27,7 +27,7 @@ namespace pclass template struct PrimitiveTypeReader { - static void read_from(BitStreamBase &stream, ValueT &value) + static void read_from(BitStream &stream, ValueT &value) { // Provide a compiler error if this is not specialized static_assert( @@ -50,14 +50,14 @@ namespace pclass m_kind = kind::PRIMITIVE; } - void write_to(BitStreamBase &stream, const Value &value) const override + void write_to(BitStream &stream, const Value &value) const override { if (!value.is()) throw std::runtime_error("Invalid call to Type::write_to -- value type does not match ValueT."); PrimitiveTypeWriter::write_to(stream, value.get()); } - void read_from(BitStreamBase &stream, Value &value) const override + void read_from(BitStream &stream, Value &value) const override { if (!value.is()) throw std::runtime_error("Invalid call to Type::read_from -- value type does not match ValueT."); diff --git a/include/ki/pclass/types/StringPrimitiveType.h b/include/ki/pclass/types/StringPrimitiveType.h index c9e5f4d..4af6870 100644 --- a/include/ki/pclass/types/StringPrimitiveType.h +++ b/include/ki/pclass/types/StringPrimitiveType.h @@ -16,7 +16,7 @@ namespace pclass using type = std::basic_string<_Elem, _Traits, _Alloc>; public: - static void write_to(BitStreamBase &stream, const type &value) + static void write_to(BitStream &stream, const type &value) { // Write the length as an unsigned short stream.write(value.length()); @@ -38,7 +38,7 @@ namespace pclass using type = std::basic_string<_Elem, _Traits, _Alloc>; public: - static void read_from(BitStreamBase &stream, type &value) + static void read_from(BitStream &stream, type &value) { // Read the length and create a new string with the correct capacity auto length = stream.read(); diff --git a/include/ki/pclass/types/Type.h b/include/ki/pclass/types/Type.h index caf1160..eaf3529 100644 --- a/include/ki/pclass/types/Type.h +++ b/include/ki/pclass/types/Type.h @@ -50,8 +50,8 @@ namespace pclass const TypeSystem &get_type_system() const; virtual PropertyClass *instantiate() const; - virtual void write_to(BitStreamBase &stream, const Value &value) const; - virtual void read_from(BitStreamBase &stream, Value &value) const; + virtual void write_to(BitStream &stream, const Value &value) const; + virtual void read_from(BitStream &stream, Value &value) const; protected: kind m_kind; diff --git a/include/ki/serialization/SerializerBinary.h b/include/ki/serialization/SerializerBinary.h index beb2336..21e287c 100644 --- a/include/ki/serialization/SerializerBinary.h +++ b/include/ki/serialization/SerializerBinary.h @@ -45,17 +45,17 @@ namespace serialization bool is_file, flags flags); virtual ~SerializerBinary() {} - void save(const pclass::PropertyClass *object, BitStreamBase &stream); - void load(pclass::PropertyClass *&dest, BitStreamBase &stream); + void save(const pclass::PropertyClass *object, BitStream &stream); + void load(pclass::PropertyClass *&dest, BitStream &stream, std::size_t size); protected: - virtual void presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const; - void save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const; - void save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const; + virtual void presave_object(const pclass::PropertyClass *object, BitStream &stream) const; + void save_object(const pclass::PropertyClass *object, BitStream &stream) const; + void save_property(const pclass::PropertyBase &prop, BitStream &stream) const; - virtual void preload_object(pclass::PropertyClass *&dest, BitStreamBase &stream) const; - void load_object(pclass::PropertyClass *&dest, BitStreamBase &stream) const; - void load_property(pclass::PropertyBase *prop, BitStreamBase &stream) const; + virtual void preload_object(pclass::PropertyClass *&dest, BitStream &stream) const; + void load_object(pclass::PropertyClass *&dest, BitStream &stream) const; + void load_property(pclass::PropertyBase &prop, BitStream &stream) const; private: const pclass::TypeSystem *m_type_system; diff --git a/include/ki/util/BitStream.h b/include/ki/util/BitStream.h index 4af7193..5e88780 100644 --- a/include/ki/util/BitStream.h +++ b/include/ki/util/BitStream.h @@ -3,181 +3,343 @@ #include #include #include "ki/util/BitTypes.h" +#include "exception.h" -#define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000 +#define KI_BITBUFFER_DEFAULT_SIZE 0x2000 namespace ki { + // Forward declaration so that buffers can create segments of themselves + class BitBufferSegment; + /** - * An abstract base class that provides a common interface for - * writing to, reading from, and querying bit streams. + * */ - class BitStreamBase + class BitBufferBase { + friend BitBufferSegment; + public: /** - * Represents a position in a BitStream's buffer. + * */ - struct stream_pos + struct buffer_pos { - explicit stream_pos(intmax_t byte = 0, int bit = 0); - stream_pos(const stream_pos &cp); + explicit buffer_pos(uint32_t byte = 0, int bit = 0); + buffer_pos(const buffer_pos &cp); - intmax_t as_bits() const; - intmax_t get_byte() const; + uint32_t as_bytes() const; + uint32_t as_bits() const; + uint32_t get_byte() const; uint8_t get_bit() const; - - stream_pos operator +(const stream_pos &rhs) const; - stream_pos operator -(const stream_pos &rhs) const; - stream_pos operator +(const int &rhs) const; - stream_pos operator -(const int &rhs) const; - stream_pos &operator +=(stream_pos lhs); - stream_pos &operator -=(stream_pos lhs); - stream_pos &operator +=(int bits); - stream_pos &operator -=(int bits); - stream_pos &operator ++(); - stream_pos &operator --(); - stream_pos operator ++(int increment); - stream_pos operator --(int increment); + + buffer_pos operator +(const buffer_pos &rhs) const; + buffer_pos operator -(const buffer_pos &rhs) const; + buffer_pos operator +(const int &rhs) const; + buffer_pos operator -(const int &rhs) const; + buffer_pos &operator +=(buffer_pos lhs); + buffer_pos &operator -=(buffer_pos lhs); + buffer_pos &operator +=(int bits); + buffer_pos &operator -=(int bits); + buffer_pos &operator ++(); + buffer_pos &operator --(); + buffer_pos operator ++(int increment); + buffer_pos operator --(int increment); private: - intmax_t m_byte; + uint32_t m_byte; uint8_t m_bit; void set_bit(int bit); }; - virtual ~BitStreamBase() {} + virtual ~BitBufferBase() {}; /** - * @returns The stream's current position. + * @returns */ - virtual stream_pos tell() const = 0; + virtual uint8_t *data() const = 0; /** - * Sets the position of the stream. - * @param position The new position of the stream. + * @returns The size of the buffer in bytes. */ - virtual void seek(stream_pos position) = 0; + virtual std::size_t size() const = 0; /** - * @returns The current size of the internal buffer. - */ - virtual std::size_t capacity() const = 0; + * Resize the buffer to the specified size. + * If the current size is larger than the specified new size, then the data is truncated. + * @param[in] new_size The size to resize the buffer to in bytes. + */ + virtual void resize(std::size_t new_size) = 0; /** - * @returns A pointer to the start of the internal buffer. + * @param[in] from The position of the start of the segment. + * @param[in] bitsize The size of the segment in bits. + * @returns A new segment of this buffer. */ - virtual const uint8_t *data() const = 0; + virtual BitBufferSegment *segment(buffer_pos from, std::size_t bitsize); /** - * Reads a value from the buffer given a defined number of bits. - * @param[in] bits The number of bits to read. Defaults to the bitsize of IntegerT. - * @returns The value read from the buffer. - */ - template < - typename IntegerT, - typename = std::enable_if::value> - > - IntegerT read(const uint8_t bits = bitsizeof::value) + * Reads a value from the buffer. + * @tparam ValueT The type of value (must be an integral type). + * @param[in] position Where in the buffer to retrieve the value from. + * @param[in] bits The number of bits the value occupies in the buffer. + * @returns The n-bit value retrieved from the buffer. + * @throws ki::runtime_error Buffer is not large enough to read the specified number of bits at the specified position. + */ + template + ValueT read(const buffer_pos position, + const uint8_t bits = bitsizeof::value) const { - return static_cast(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(bits) << "-bit value at specified position. " + << "(byte=" << position.get_byte() << ", bit=" << static_cast(position.get_bit()) + << ", size=" << size() << ")"; + throw runtime_error(oss.str()); + } + + return static_cast(read(position, bits)); } /** - * Writes a value to the buffer that occupies a defined number of bits. - * @tparam IntegerT The type of value (must be an integral type). + * Writes a value to the buffer. + * @tparam ValueT The type of value (must be an integral type). * @param[in] value The value to write. - * @param[in] bits The number of bits to use. Defaults to the bitsize of IntegerT. + * @param[in] position Where in the buffer to write the value to. + * @param[in] bits The number of bits the value should occupy in the buffer. Defaults to the bitsize of ValueT. + * @throws ki::runtime_error Buffer is not large enough to write the specified number of bits at the specified position. */ - template < - typename IntegerT, - typename = std::enable_if::value> - > - void write(IntegerT value, const uint8_t bits = bitsizeof::value) + template + void write(const ValueT value, const buffer_pos position, + const uint8_t bits = bitsizeof::value) { - write(static_cast(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(bits) << "-bit value at specified position. " + << "(byte=" << position.get_byte() << ", bit=" << static_cast(position.get_bit()) + << ", size=" << size() << ")"; + throw runtime_error(oss.str()); + } + + write(static_cast(value), position, bits); } /** - * Copy memory from an external buffer into the bitstream's buffer from the current position. - * @param[in] src The buffer to copy data from. - * @param[in] bitsize The number of bits to copy from the src buffer. + * Copy memory from this buffer into an external buffer. + * @param[out] dst The destination buffer to copy data to. + * @param[in] position Where in the buffer to start copying data from. + * @param[in] bitsize The number of bits to copy into the destination buffer. */ - void write_copy(uint8_t *src, std::size_t bitsize); + void read_copy(uint8_t *dst, buffer_pos position, std::size_t bitsize) const; /** - * Copy memory from the bitstream's buffer into an external buffer. - * @param[out] dst The destination buffer to copy data to. - * @param[in] bitsize The number of bits to copy into the dst buffer. + * Copy memory from an external buffer into this buffer at the specified position. + * @param[in] src The source buffer to copy data from. + * @param[in] position Where in the buffer to start copying data to. + * @param[in] bitsize The number of bits to copy from the source buffer. */ - void read_copy(uint8_t *dst, std::size_t bitsize); + void write_copy(uint8_t *src, buffer_pos position, std::size_t bitsize); protected: - virtual uint64_t read(uint8_t bits) = 0; - virtual void write(uint64_t value, uint8_t bits) = 0; + virtual uint64_t read(buffer_pos position, uint8_t bits) const = 0; + virtual void write(uint64_t value, buffer_pos position, uint8_t bits) = 0; + }; + + /** + * + */ + class BitBuffer : public BitBufferBase + { + public: + explicit BitBuffer(std::size_t buffer_size = KI_BITBUFFER_DEFAULT_SIZE); + ~BitBuffer(); + + BitBuffer(const BitBuffer &that); + BitBuffer &operator=(const BitBuffer &that); + + /** + * Create a new BitBuffer from an existing buffer. + * @param[in] buffer The buffer to take ownership of. + * @param[in] buffer_size The size of the buffer in bytes. + */ + explicit BitBuffer(uint8_t *buffer, std::size_t buffer_size); + + std::size_t size() const override; + uint8_t* data() const override; + void resize(std::size_t new_size) override; + + /** + * @copydoc BitBufferBase::read(buffer_pos, uint8_t) + */ + template + ValueT read(const buffer_pos position, + const uint8_t bits = bitsizeof::value) const + { + return BitBufferBase::read(position, bits); + } + + /** + * @copydoc BitBufferBase::write(ValueT, buffer_pos, uint8_t) + */ + template + void write(const ValueT value, const buffer_pos position, + const uint8_t bits = bitsizeof::value) + { + return BitBufferBase::write(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(buffer_pos, uint8_t) + */ + template + ValueT read(const buffer_pos position, + const uint8_t bits = bitsizeof::value) const + { + return BitBufferBase::read(position, bits); + } + + /** + * @copydoc BitBufferBase::write(ValueT, buffer_pos, uint8_t) + */ + template + void write(const ValueT value, const buffer_pos position, + const uint8_t bits = bitsizeof::value) + { + return BitBufferBase::write(value, position, bits); + } + + protected: + uint64_t read(buffer_pos position, uint8_t bits) const override; + void write(uint64_t value, buffer_pos position, uint8_t bits) override; + + private: + BitBufferBase *m_buffer; + buffer_pos m_from; + std::size_t m_bitsize; }; /** * A read/write-able stream of bits. */ - class BitStream : public BitStreamBase + class BitStream { public: - explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE); + using stream_pos = BitBuffer::buffer_pos; + + explicit BitStream(BitBufferBase &buffer); virtual ~BitStream(); - stream_pos tell() const override; - void seek(stream_pos position) override; - std::size_t capacity() const override; - const uint8_t *data() const override; + BitStream(const BitStream &that); + BitStream &operator=(const BitStream &that); /** - * @copydoc BitStreamBase::read(uint8_t) - * @throws ki::runtime_error Not enough data available to read the specified number of bits. + * @returns The stream's current position. + */ + stream_pos tell() const; + + /** + * Sets the position of the stream. + * @param[in] position The new position of the stream. + * @param[in] expand Whether or not to expand the buffer. + */ + void seek(stream_pos position, bool expand = true); + + /** + * @returns The current size of the internal buffer. + */ + std::size_t capacity() const; + + /** + * @returns The BitBuffer that the stream is reading/writing to. + */ + BitBufferBase &buffer() const; + + /** + * Reads a value from the buffer. + * @tparam ValueT The type used to store the value. + * @param[in] bits The number of bits to read. Defaults to the bitsize of ValueT. + * @returns The value read from the buffer. */ template < - typename IntegerT, - typename = std::enable_if::value> + typename ValueT, + typename = std::enable_if::value> > - IntegerT read(const uint8_t bits = bitsizeof::value) + ValueT read(const uint8_t bits = bitsizeof::value) { - return BitStreamBase::read(bits); + ValueT value = m_buffer->read(m_position, bits); + m_position += bits; + return value; } + /** + * Writes a value to the buffer. + * If the buffer is not big enough to write the value, then the buffer is resized. + * @tparam ValueT The type of value (must be an integral type). + * @param[in] value The value to write. + * @param[in] bits The number of bits to use. Defaults to the bitsize of ValueT. + */ template < - typename IntegerT, - typename = std::enable_if::value> + typename ValueT, + typename = std::enable_if::value> > - void write(IntegerT value, const uint8_t bits = bitsizeof::value) + void write(ValueT value, const uint8_t bits = bitsizeof::value) { - BitStreamBase::write(value, bits); + expand_buffer(m_position + bits); + m_buffer->write(value, m_position, bits); + m_position += bits; } - protected: - uint64_t read(uint8_t bits) override; - void write(uint64_t value, uint8_t bits) override; + /** + * Copy memory from the BitStream's buffer into an external buffer at the current position. + * @param[out] dst The destination buffer to copy data to. + * @param[in] bitsize The number of bits to copy into the destination buffer. + */ + void read_copy(uint8_t *dst, std::size_t bitsize); + + /** + * Copy memory from an external buffer into the BitStream's buffer from the current position. + * @param[in] src The source buffer to copy data from. + * @param[in] bitsize The number of bits to copy from the source buffer. + */ + void write_copy(uint8_t *src, std::size_t bitsize); private: - uint8_t * m_buffer; - std::size_t m_buffer_size; + BitBufferBase *m_buffer; stream_pos m_position; - void expand_buffer(); - void validate_buffer(); - }; - - /** - * TODO: Documentation - */ - class BitStreamSection : public BitStreamBase - { - public: - explicit BitStreamSection(BitStreamBase &stream, std::size_t size); - - private: - BitStreamBase &m_stream; - stream_pos m_position; - std::size_t m_size; + /** + * Expand the buffer such that a position becomes valid. + * @param[in] position The minimum position that should be accessible after expanding. + */ + void expand_buffer(stream_pos position) const; }; } diff --git a/src/pclass/ClassType.cpp b/src/pclass/ClassType.cpp index 7d910fa..34958c1 100644 --- a/src/pclass/ClassType.cpp +++ b/src/pclass/ClassType.cpp @@ -1,5 +1,6 @@ #include "ki/pclass/types/ClassType.h" #include "ki/pclass/TypeSystem.h" +#include "ki/util/exception.h" namespace ki { @@ -23,6 +24,8 @@ namespace pclass if (!m_base_class) throw runtime_error("base_class must inherit ClassTypeBase!"); } + else + m_base_class = nullptr; } bool ClassTypeBase::inherits(const Type &type) const diff --git a/src/pclass/Property.cpp b/src/pclass/Property.cpp index 9759867..a8e5688 100644 --- a/src/pclass/Property.cpp +++ b/src/pclass/Property.cpp @@ -19,7 +19,19 @@ namespace pclass m_type = &type; // Add this property to the object's property list - object.get_properties().add_property(this); + object.add_property(*this); + } + + PropertyBase::PropertyBase(PropertyClass &object, + const PropertyBase &that) + { + m_name = that.m_name; + m_name_hash = that.m_name_hash; + m_full_hash = that.m_full_hash; + m_type = that.m_type; + + // Add this property to the object's property list + object.add_property(*this); } std::string PropertyBase::get_name() const @@ -54,7 +66,13 @@ namespace pclass DynamicPropertyBase::DynamicPropertyBase(PropertyClass &object, const std::string& name, const Type& type) - : PropertyBase(object, name, type) { } + : PropertyBase(object, name, type) + {} + + DynamicPropertyBase::DynamicPropertyBase(PropertyClass &object, + const DynamicPropertyBase &that) + : PropertyBase(object, that) + {} bool DynamicPropertyBase::is_dynamic() const { @@ -73,13 +91,19 @@ namespace pclass throw runtime_error("Called get_object() on a dynamic property. Use get_object(index) instead."); } - void DynamicPropertyBase::write_value_to(BitStreamBase &stream) const + void DynamicPropertyBase::set_object(PropertyClass *object) + { + // The caller must specify an index + throw runtime_error("Called set_object() on a dynamic property. Use set_object(index) instead."); + } + + void DynamicPropertyBase::write_value_to(BitStream &stream) const { // The caller must specify an index throw runtime_error("Called write_value_to() on a dynamic property. Use write_value_to(index) instead."); } - void DynamicPropertyBase::read_value_from(BitStreamBase &stream) + void DynamicPropertyBase::read_value_from(BitStream &stream) { // The caller must specify an index throw runtime_error("Called read_value_from() on a dynamic property. Use read_value_from(index) instead."); diff --git a/src/pclass/PropertyClass.cpp b/src/pclass/PropertyClass.cpp index 5f45534..fb432e2 100644 --- a/src/pclass/PropertyClass.cpp +++ b/src/pclass/PropertyClass.cpp @@ -7,13 +7,19 @@ namespace pclass PropertyClass::PropertyClass(const Type &type, const TypeSystem &type_system) { m_type = &type; - m_properties = new PropertyList(); } - PropertyClass::~PropertyClass() + PropertyClass::PropertyClass(const PropertyClass &that) { - // Delete the list of properties - delete m_properties; + m_type = that.m_type; + m_properties = PropertyList(); + } + + PropertyClass &PropertyClass::operator=(const PropertyClass &that) + { + m_type = that.m_type; + m_properties = PropertyList(); + return *this; } const Type& PropertyClass::get_type() const @@ -23,12 +29,22 @@ namespace pclass PropertyList &PropertyClass::get_properties() { - return *m_properties; + return m_properties; } const PropertyList& PropertyClass::get_properties() const { - return *m_properties; + return m_properties; + } + + PropertyClass *PropertyClass::copy() const + { + return new PropertyClass(*this); + } + + void PropertyClass::add_property(PropertyBase &prop) + { + m_properties.add_property(prop); } } } diff --git a/src/pclass/PropertyList.cpp b/src/pclass/PropertyList.cpp index 736b32b..2201c71 100644 --- a/src/pclass/PropertyList.cpp +++ b/src/pclass/PropertyList.cpp @@ -8,12 +8,92 @@ namespace ki { namespace pclass { + PropertyBase &PropertyList::iterator::operator*() const + { + return m_list->get_property(m_index); + } + + PropertyBase *PropertyList::iterator::operator->() const + { + return &operator*(); + } + + PropertyList::iterator &PropertyList::iterator::operator++() + { + m_index++; + return *this; + } + + PropertyList::iterator PropertyList::iterator::operator++(int) + { + const auto copy(*this); + operator++(); + return copy; + } + + bool PropertyList::iterator::operator==(const iterator &that) const + { + return this->m_list == that.m_list && + this->m_index == that.m_index; + } + + bool PropertyList::iterator::operator!=(const iterator &that) const + { + return !(*this == that); + } + + PropertyList::iterator::iterator(PropertyList &list, const int index) + { + m_list = &list; + m_index = index; + } + + const PropertyBase &PropertyList::const_iterator::operator*() const + { + return m_list->get_property(m_index); + } + + const PropertyBase *PropertyList::const_iterator::operator->() const + { + return &operator*(); + } + + PropertyList::const_iterator &PropertyList::const_iterator::operator++() + { + m_index++; + return *this; + } + + PropertyList::const_iterator PropertyList::const_iterator::operator++(int) + { + const auto copy(*this); + operator++(); + return copy; + } + + bool PropertyList::const_iterator::operator==(const const_iterator &that) const + { + return this->m_list == that.m_list && + this->m_index == that.m_index; + } + + bool PropertyList::const_iterator::operator!=(const const_iterator &that) const + { + return !(*this == that); + } + + PropertyList::const_iterator::const_iterator(const PropertyList &list, const int index) + { + m_list = &list; + m_index = index; + } + std::size_t PropertyList::get_property_count() const { return m_properties.size(); } - bool PropertyList::has_property(const std::string& name) const + bool PropertyList::has_property(const std::string &name) const { return m_property_name_lookup.find(name) != m_property_name_lookup.end(); @@ -25,7 +105,7 @@ namespace pclass != m_property_hash_lookup.end(); } - const PropertyBase& PropertyList::get_property(const int index) const + PropertyBase &PropertyList::get_property(const int index) { if (index >= 0 && index < m_properties.size()) return *m_properties[index]; @@ -36,7 +116,7 @@ namespace pclass throw runtime_error(oss.str()); } - const PropertyBase& PropertyList::get_property(const std::string& name) const + PropertyBase& PropertyList::get_property(const std::string& name) { const auto it = m_property_name_lookup.find(name); if (it != m_property_name_lookup.end()) @@ -47,7 +127,7 @@ namespace pclass throw runtime_error(oss.str()); } - const PropertyBase& PropertyList::get_property(const hash_t hash) const + PropertyBase& PropertyList::get_property(const hash_t hash) { const auto it = m_property_hash_lookup.find(hash); if (it != m_property_hash_lookup.end()) @@ -60,48 +140,87 @@ namespace pclass throw runtime_error(oss.str()); } + const PropertyBase &PropertyList::get_property(const int index) const + { + if (index >= 0 && index < m_properties.size()) + return *m_properties[index]; + + std::ostringstream oss; + oss << "Property index out of range. (index=" << index + << ", size=" << m_properties.size() << ")"; + throw runtime_error(oss.str()); + } + + const PropertyBase& PropertyList::get_property(const std::string &name) const + { + const auto it = m_property_name_lookup.find(name); + if (it != m_property_name_lookup.end()) + return *it->second; + + std::ostringstream oss; + oss << "Could not find property with name: '" << name << "'."; + throw runtime_error(oss.str()); + } + + const PropertyBase &PropertyList::get_property(const hash_t hash) const + { + const auto it = m_property_hash_lookup.find(hash); + if (it != m_property_hash_lookup.end()) + return *it->second; + + std::ostringstream oss; + oss << "Could not find property with hash: 0x" + << std::hex << std::setw(8) << std::setfill('0') + << std::uppercase << hash << "."; + throw runtime_error(oss.str()); + } + + PropertyList::iterator PropertyList::begin() + { + return iterator(*this); + } + PropertyList::const_iterator PropertyList::begin() const { - return m_properties.cbegin(); + return const_iterator(*this); + } + + PropertyList::iterator PropertyList::end() + { + return iterator(*this, m_properties.size()); } PropertyList::const_iterator PropertyList::end() const { - return m_properties.cend(); + return const_iterator(*this, m_properties.size()); } - void PropertyList::add_property(PropertyBase *prop) + void PropertyList::add_property(PropertyBase &prop) { // Make sure a property with the same name as another isn't being added - if (has_property(prop->get_name())) + if (has_property(prop.get_name())) { - // This pointer is going to be lost, so delete it now - delete prop; - std::ostringstream oss; oss << "A property has already been added with name: '" - << prop->get_name() << "'."; + << prop.get_name() << "'."; throw runtime_error(oss.str()); } // Check for hash collisions - if (has_property(prop->get_full_hash())) + if (has_property(prop.get_full_hash())) { - const auto &other = get_property(prop->get_full_hash()); - - // This pointer is going to be lost, so delete it now - delete prop; + const auto &other = get_property(prop.get_full_hash()); std::ostringstream oss; - oss << "Cannot add property '" << prop->get_name() << "'. " + oss << "Cannot add property '" << prop.get_name() << "'. " << "Hash collision with property '" << other.get_name() << "'."; throw runtime_error(oss.str()); } // Add the property to lookups - m_properties.push_back(prop); - m_property_name_lookup[prop->get_name()] = prop; - m_property_hash_lookup[prop->get_full_hash()] = prop; + m_properties.push_back(&prop); + m_property_name_lookup[prop.get_name()] = ∝ + m_property_hash_lookup[prop.get_full_hash()] = ∝ } } } diff --git a/src/pclass/Type.cpp b/src/pclass/Type.cpp index e9d096e..56560ca 100644 --- a/src/pclass/Type.cpp +++ b/src/pclass/Type.cpp @@ -38,14 +38,14 @@ namespace pclass return m_type_system; } - void Type::write_to(BitStreamBase &stream, const Value& value) const + void Type::write_to(BitStream &stream, const Value& value) const { std::ostringstream oss; oss << "Type '" << m_name << "' does not implement Type::write_to."; throw runtime_error(oss.str()); } - void Type::read_from(BitStreamBase &stream, Value& value) const + void Type::read_from(BitStream &stream, Value& value) const { std::ostringstream oss; oss << "Type '" << m_name << "' does not implement Type::read_from."; diff --git a/src/serialization/SerializerBinary.cpp b/src/serialization/SerializerBinary.cpp index b37e80c..52ec7f8 100644 --- a/src/serialization/SerializerBinary.cpp +++ b/src/serialization/SerializerBinary.cpp @@ -15,22 +15,23 @@ namespace serialization m_root_object = nullptr; } - void SerializerBinary::save(const pclass::PropertyClass* object, BitStreamBase &stream) + void SerializerBinary::save(const pclass::PropertyClass* object, BitStream &stream) { // Write the serializer flags if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS)) stream.write(static_cast(m_flags)); - // Remember where we started writing data, so we can compress later - // if necessary. - const auto start_pos = stream.tell(); - // If the contents of the stream are going to be compressed, - // reserve space to put the length + // reserve space to put the length and compression toggle. + const auto compression_header_pos = stream.tell(); if (FLAG_IS_SET(m_flags, flags::COMPRESSED)) + { + stream.write(false); stream.write(0); + } // Write the object to the stream + const auto start_pos = stream.tell(); m_root_object = object; save_object(object, stream); @@ -39,17 +40,17 @@ namespace serialization { // Remember where the ending of the data is const auto end_pos = stream.tell(); - const auto size_bits = (end_pos - (start_pos + bitsizeof::value)).as_bits(); - const auto size_bytes = (size_bits / 8) + (size_bits % 8 > 0 ? 1 : 0); + const auto size_bits = (end_pos - start_pos).as_bits(); + const auto size_bytes = (end_pos - start_pos).as_bytes(); // Make a copy of the uncompressed data - auto *uncompressed = new uint8_t[size_bytes]; + auto *uncompressed = new uint8_t[size_bytes]{0}; stream.read_copy(uncompressed, size_bits); // Setup compression static const std::size_t bufsize = 1024; - auto *temp_buffer = new uint8_t[bufsize]; - std::vector compressed(size_bytes); + auto *temp_buffer = new uint8_t[bufsize]{0}; + std::vector compressed; z_stream z; z.zalloc = nullptr; z.zfree = nullptr; @@ -93,23 +94,26 @@ namespace serialization temp_buffer, temp_buffer + bufsize - z.avail_out); deflateEnd(&z); - // Move the contents of the compressed buffer if the compressed - // buffer is smaller - stream.seek(start_pos); - stream.write(size_bytes); - if (compressed.size() < size_bytes) - stream.write_copy(compressed.data(), compressed.size() * 8); - else - // Go back to the end of the data - stream.seek(end_pos); - // Cleanup temporary buffers delete[] uncompressed; delete[] temp_buffer; + + // Write the compression header + const auto use_compression = compressed.size() < size_bytes; + stream.seek(compression_header_pos); + stream.write(use_compression); + stream.write(size_bytes); + + // Write the compressed data + if (use_compression) + stream.write_copy(compressed.data(), compressed.size() * 8); + else + // Go back to the end of the original data + stream.seek(end_pos); } } - void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const + void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStream &stream) const { // If we have an object, write the type hash, otherwise, write NULL (0). if (object) @@ -118,7 +122,7 @@ namespace serialization stream.write(NULL); } - void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const + void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStream &stream) const { // Write any object headers presave_object(object, stream); @@ -154,9 +158,13 @@ namespace serialization stream.write(size_bits); stream.seek(end_pos); } + + // Re-align the stream so that our position lies on a byte + // TODO: Look into how/when the serialization re-aligns as this may not be the right place + stream.seek(BitStream::stream_pos(stream.tell().as_bytes(), 0)); } - void SerializerBinary::save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const + void SerializerBinary::save_property(const pclass::PropertyBase &prop, BitStream &stream) const { // Remember where we started writing the property data const auto start_pos = stream.tell(); @@ -166,52 +174,52 @@ namespace serialization { // Reserve space to put the size of the property stream.write(0); - stream.write(prop->get_full_hash()); + stream.write(prop.get_full_hash()); } // Is the property dynamic? (holding more than one value) - auto &property_type = prop->get_type(); - if (prop->is_dynamic()) + auto &property_type = prop.get_type(); + if (prop.is_dynamic()) { // Cast the property to a DynamicPropertyBase - const auto *dynamic_property = - dynamic_cast(prop); + const auto &dynamic_property = + dynamic_cast(prop); // Write the number of elements - stream.write(dynamic_property->get_element_count()); + stream.write(dynamic_property.get_element_count()); // Iterate through the elements - for (auto i = 0; i < dynamic_property->get_element_count(); i++) + for (auto i = 0; i < dynamic_property.get_element_count(); i++) { // Is this a collection of pointers? - if (prop->is_pointer()) + if (prop.is_pointer()) { // Is the property a collection of pointers to other objects? if (property_type.get_kind() == pclass::Type::kind::CLASS) // Write the value as a nested object - save_object(dynamic_property->get_object(i), stream); + save_object(dynamic_property.get_object(i), stream); else // Write the value as normal (let the property deal with dereferencing) - dynamic_property->write_value_to(stream, i); + dynamic_property.write_value_to(stream, i); } else // If the value isn't a pointer, and it's not dynamic, just write it as a value - dynamic_property->write_value_to(stream, i); + dynamic_property.write_value_to(stream, i); } } - else if (prop->is_pointer()) + else if (prop.is_pointer()) { // Does this property hold a pointer to another object? if (property_type.get_kind() == pclass::Type::kind::CLASS) // Write the value as a nested object - save_object(prop->get_object(), stream); + save_object(prop.get_object(), stream); else // Write the value as normal (let the property deal with dereferencing) - prop->write_value_to(stream); + prop.write_value_to(stream); } else // If the value isn't a pointer, and it's not dynamic, just write it as a value - prop->write_value_to(stream); + prop.write_value_to(stream); // Finish writing the property header by writing the length if (m_is_file) @@ -227,32 +235,233 @@ namespace serialization } } - void SerializerBinary::load(pclass::PropertyClass*& dest, BitStreamBase &stream) + void SerializerBinary::load(pclass::PropertyClass *&dest, + BitStream &stream, const std::size_t size) { + // Create a new stream that reads a segment of the stream given to us + BitBufferBase *buffer = stream.buffer().segment(stream.tell(), size * 8); + auto segment_stream = BitStream(*buffer); + stream.seek(stream.tell() + size * 8, false); + // Read the serializer flags if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS)) - m_flags = static_cast(stream.read()); + m_flags = static_cast(segment_stream.read()); // Decompress the contents of the stream if (FLAG_IS_SET(m_flags, flags::COMPRESSED)) { - + // Read the compression header + const auto use_compression = segment_stream.read(); + const auto uncompressed_size = segment_stream.read(); + + // 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(); + 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() - bitsizeof::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() - bitsizeof::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(); + 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(prop); + + // How many elements are there in this dynamic property? + const auto element_count = stream.read(); + 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( + 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( + 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); } } } diff --git a/src/util/BitStream.cpp b/src/util/BitStream.cpp index 7e08c49..fcab661 100644 --- a/src/util/BitStream.cpp +++ b/src/util/BitStream.cpp @@ -6,34 +6,39 @@ namespace ki { - BitStream::stream_pos::stream_pos(const intmax_t byte, const int bit) + BitBufferBase::buffer_pos::buffer_pos(const uint32_t byte, const int bit) { m_byte = byte; set_bit(bit); } - BitStream::stream_pos::stream_pos(const stream_pos& cp) + BitBufferBase::buffer_pos::buffer_pos(const buffer_pos& cp) { m_byte = cp.m_byte; set_bit(cp.m_bit); } - intmax_t BitStream::stream_pos::as_bits() const + uint32_t BitBufferBase::buffer_pos::as_bytes() const + { + return m_byte + (m_bit > 0 ? 1 : 0); + } + + uint32_t BitBufferBase::buffer_pos::as_bits() const { return (m_byte * 8) + m_bit; } - intmax_t BitStream::stream_pos::get_byte() const + uint32_t BitBufferBase::buffer_pos::get_byte() const { return m_byte; } - uint8_t BitStream::stream_pos::get_bit() const + uint8_t BitBufferBase::buffer_pos::get_bit() const { return m_bit; } - void BitStream::stream_pos::set_bit(int bit) + void BitBufferBase::buffer_pos::set_bit(int bit) { if (bit < 0) { @@ -50,170 +55,179 @@ namespace ki m_bit = bit; } - BitStream::stream_pos BitStream::stream_pos::operator+( - const stream_pos &rhs) const + BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator+( + const buffer_pos &rhs) const { - return stream_pos( + return buffer_pos( m_byte + rhs.m_byte, m_bit + rhs.m_bit ); } - BitStream::stream_pos BitStream::stream_pos::operator-( - const stream_pos &rhs) const + BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator-( + const buffer_pos &rhs) const { - return stream_pos( + return buffer_pos( m_byte - rhs.m_byte, m_bit - rhs.m_bit ); } - BitStream::stream_pos BitStream::stream_pos::operator+( + BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator+( const int& rhs) const { - return stream_pos( + return buffer_pos( m_byte, m_bit + rhs ); } - BitStream::stream_pos BitStream::stream_pos::operator-( + BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator-( const int& rhs) const { - return stream_pos( + return buffer_pos( m_byte, m_bit - rhs ); } - BitStream::stream_pos& BitStream::stream_pos::operator+=( - const stream_pos rhs) + BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator+=( + const buffer_pos rhs) { m_byte += rhs.m_byte; set_bit(m_bit + rhs.m_bit); return *this; } - BitStream::stream_pos& BitStream::stream_pos::operator-=( - const stream_pos rhs) + BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator-=( + const buffer_pos rhs) { m_byte -= rhs.m_byte; set_bit(m_bit - rhs.m_bit); return *this; } - BitStream::stream_pos& BitStream::stream_pos::operator+=(const int bits) + BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator+=(const int bits) { set_bit(m_bit + bits); return *this; } - BitStream::stream_pos& BitStream::stream_pos::operator-=(const int bits) + BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator-=(const int bits) { set_bit(m_bit - bits); return *this; } - BitStream::stream_pos& BitStream::stream_pos::operator++() + BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator++() { set_bit(m_bit + 1); return *this; } - BitStream::stream_pos& BitStream::stream_pos::operator--() + BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator--() { set_bit(m_bit - 1); return *this; } - BitStream::stream_pos BitStream::stream_pos::operator++(int increment) + BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator++(int increment) { auto copy(*this); ++(*this); return copy; } - BitStream::stream_pos BitStream::stream_pos::operator--(int increment) + BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator--(int increment) { auto copy(*this); --(*this); return copy; } - void BitStreamBase::write_copy(uint8_t *src, const std::size_t bitsize) + BitBufferSegment *BitBufferBase::segment( + const buffer_pos from, const std::size_t bitsize) { - // Copy all whole bytes - const auto bytes = bitsize / 8; - auto written_bytes = 0; - while (written_bytes < bytes) - { - write(src[written_bytes]); - written_bytes++; - } - - // Copy left over bits - const auto bits = bitsize % 8; - if (bits > 0) - write(src[bytes + 1], bits); + return new BitBufferSegment(*this, from, bitsize); } - void BitStreamBase::read_copy(uint8_t *dst, const std::size_t bitsize) + void BitBufferBase::write_copy(uint8_t *src, + buffer_pos position, const std::size_t bitsize) { - // Copy all whole bytes - const auto bytes = bitsize / 8; - auto read_bytes = 0; - while (read_bytes < bytes) + auto bits_left = bitsize; + while (bits_left > 0) { - dst[read_bytes] = read(); - read_bytes++; + const auto bits = bits_left >= 8 ? 8 : bits_left; + write(*(src++), position, bits); + bits_left -= bits; + position += bits; } - - // Copy left over bits - const auto bits = bitsize % 8; - if (bits > 0) - dst[bytes + 1] = read(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(position, bits); + bits_left -= bits; + position += bits; + } + } + + BitBuffer::BitBuffer(const size_t buffer_size) + { + m_buffer = new uint8_t[buffer_size]{0}; m_buffer_size = buffer_size; - m_position = stream_pos(0, 0); } - BitStream::~BitStream() + BitBuffer::~BitBuffer() { delete[] m_buffer; + m_buffer = nullptr; + m_buffer_size = 0; } - BitStream::stream_pos BitStream::tell() const + BitBuffer::BitBuffer(const BitBuffer& that) { - return m_position; + m_buffer_size = that.m_buffer_size; + m_buffer = new uint8_t[m_buffer_size]{0}; + std::memcpy(m_buffer, that.m_buffer, m_buffer_size); } - void BitStream::seek(const stream_pos position) + BitBuffer &BitBuffer::operator=(const BitBuffer &that) { - m_position = position; - validate_buffer(); + m_buffer_size = that.m_buffer_size; + m_buffer = new uint8_t[m_buffer_size]{0}; + std::memcpy(m_buffer, that.m_buffer, m_buffer_size); + return (*this); } - std::size_t BitStream::capacity() const + BitBuffer::BitBuffer(uint8_t *buffer, const std::size_t buffer_size) + { + m_buffer = buffer; + m_buffer_size = buffer_size; + } + + std::size_t BitBuffer::size() const { return m_buffer_size; } - const uint8_t *BitStream::data() const + void BitBuffer::resize(const std::size_t new_size) + { + auto *new_buffer = new uint8_t[new_size]{0}; + std::memcpy(new_buffer, m_buffer, new_size >= m_buffer_size ? m_buffer_size : new_size); + delete[] m_buffer; + m_buffer = new_buffer; + m_buffer_size = new_size; + } + + uint8_t *BitBuffer::data() const { return m_buffer; } - uint64_t BitStream::read(const uint8_t bits) + uint64_t BitBuffer::read(buffer_pos position, const uint8_t bits) const { - // Do we have these bits available to read? - if ((m_position + bits).as_bits() > m_buffer_size * 8) - { - std::ostringstream oss; - oss << "Not enough data in buffer to read " - << static_cast(bits) << "bits."; - throw runtime_error(oss.str()); - } - // Iterate until we've read all of the bits uint64_t value = 0; auto unread_bits = bits; @@ -221,14 +235,14 @@ namespace ki { // Calculate how many bits to read from the current byte based on how many bits // are left and how many bits we still need to read - const uint8_t bits_available = (8 - m_position.get_bit()); + const uint8_t bits_available = (8 - position.get_bit()); const auto bit_count = unread_bits < bits_available ? unread_bits : bits_available; // Find the bit-mask based on how many bits are being read - const uint8_t bit_mask = (1 << bit_count) - 1 << m_position.get_bit(); + const uint8_t bit_mask = (1 << bit_count) - 1 << position.get_bit(); // Read the bits from the current byte and position them on the least-signficant bit - const uint8_t bits_value = (m_buffer[m_position.get_byte()] & bit_mask) >> m_position.get_bit(); + const uint8_t bits_value = (m_buffer[position.get_byte()] & bit_mask) >> position.get_bit(); // Position the value of the bits we just read based on how many bits of the value // we've already read @@ -239,13 +253,14 @@ namespace ki unread_bits -= bit_count; // Move forward the number of bits we just read - seek(tell() + bit_count); + position += bit_count; } return value; } - void BitStream::write(const uint64_t value, const uint8_t bits) + void BitBuffer::write(const uint64_t value, + buffer_pos position, const uint8_t bits) { // Iterate until we've written all of the bits auto unwritten_bits = bits; @@ -253,7 +268,7 @@ namespace ki { // Calculate how many bits to write based on how many bits are left in the current byte // and how many bits from the value we still need to write - const uint8_t bits_available = (8 - m_position.get_bit()); + const uint8_t bits_available = (8 - position.get_bit()); const auto bit_count = unwritten_bits < bits_available ? unwritten_bits : bits_available; // Find the bit-mask based on how many bits are being written, and how many bits we've @@ -263,47 +278,149 @@ namespace ki // Get the bits from the value and position them at the current bit position uint8_t value_byte = ((value & bit_mask) >> written_bits) & 0xFF; - value_byte <<= m_position.get_bit(); + value_byte <<= position.get_bit(); // Write the bits into the byte we're currently at - m_buffer[m_position.get_byte()] |= value_byte; + m_buffer[position.get_byte()] |= value_byte; unwritten_bits -= bit_count; // Move forward the number of bits we just wrote - seek(tell() + bit_count); + position += bit_count; } } - void BitStream::expand_buffer() + BitBufferSegment::BitBufferSegment(BitBufferBase& buffer, + const buffer_pos from, const std::size_t bitsize) { - // Work out a new buffer size - const auto minimum_bits = static_cast( - 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::max(); + m_buffer = &buffer; - // Has the buffer reached maximum size? - if (new_size == m_buffer_size) - throw runtime_error("Buffer cannot be expanded as it has reached maximum size."); + // Is this a valid position to start reading from? + if (from.get_byte() >= m_buffer->size()) + throw runtime_error("Cannot create segment of buffer from position outside the original buffer."); + m_from = from; - // Allocate a new buffer, copy everything over, and then delete the old buffer - auto *new_buffer = new uint8_t[new_size] { 0 }; - std::memcpy(new_buffer, m_buffer, m_buffer_size); - delete[] m_buffer; - m_buffer = new_buffer; - m_buffer_size = new_size; + // Is the size valid? + if ((m_from + bitsize).as_bytes() > m_buffer->size()) + { + std::ostringstream oss; + oss << "Not enough data in original buffer to create segment of size: " + << bitsize; + throw runtime_error(oss.str()); + } + m_bitsize = bitsize; } - void BitStream::validate_buffer() + std::size_t BitBufferSegment::size() const { - // Make sure we haven't underflowed - if (m_position.get_byte() < 0) - throw runtime_error("Position of buffer is less than 0!"); + return buffer_pos(m_bitsize / 8, m_bitsize % 8).as_bytes(); + } + void BitBufferSegment::resize(std::size_t new_size) + { + throw runtime_error("A BitBufferSegment cannot be resized."); + } + + uint8_t* BitBufferSegment::data() const + { + return &m_buffer->data()[m_from.get_byte()]; + } + + BitBufferSegment *BitBufferSegment::segment( + const buffer_pos from, const std::size_t bitsize) + { + return new BitBufferSegment(*m_buffer, m_from + from, bitsize); + } + + uint64_t BitBufferSegment::read( + const buffer_pos position, const uint8_t bits) const + { + return m_buffer->read(m_from + position, bits); + } + + void BitBufferSegment::write(const uint64_t value, + const buffer_pos position, const uint8_t bits) + { + m_buffer->write(value, m_from + position, bits); + } + + BitStream::BitStream(BitBufferBase &buffer) + { + m_buffer = &buffer; + m_position = stream_pos(0, 0); + } + + BitStream::~BitStream() + { + m_buffer = nullptr; + } + + BitStream::BitStream(const BitStream &that) + { + m_buffer = that.m_buffer; + m_position = that.m_position; + } + + BitStream& BitStream::operator=(const BitStream &that) + { + m_buffer = that.m_buffer; + m_position = that.m_position; + return *this; + } + + BitBufferBase::buffer_pos BitStream::tell() const + { + return m_position; + } + + void BitStream::seek(const stream_pos position, const bool expand) + { + if (expand) + expand_buffer(position); + m_position = position; + } + + std::size_t BitStream::capacity() const + { + return m_buffer->size(); + } + + BitBufferBase &BitStream::buffer() const + { + return *m_buffer; + } + + void BitStream::read_copy(uint8_t *dst, const std::size_t bitsize) + { + m_buffer->read_copy(dst, m_position, bitsize); + m_position += bitsize; + } + + void BitStream::write_copy(uint8_t *src, const std::size_t bitsize) + { + expand_buffer(m_position + bitsize); + m_buffer->write_copy(src, m_position, bitsize); + m_position += bitsize; + } + + void BitStream::expand_buffer(const stream_pos position) const + { // Expand the buffer if we've overflowed - if (m_position.get_byte() >= m_buffer_size) - expand_buffer(); + if (position.as_bytes() >= m_buffer->size()) + { + // Work out a new buffer size + const auto minimum_bits = static_cast( + std::log2(position.get_byte()) + ) + 1; + auto new_size = (2 << minimum_bits) + 2; + if (new_size < m_buffer->size()) + new_size = std::numeric_limits::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); + } } } diff --git a/test/src/unit-bitstream.cpp b/test/src/unit-bitstream.cpp index 371a69b..5e61221 100644 --- a/test/src/unit-bitstream.cpp +++ b/test/src/unit-bitstream.cpp @@ -19,8 +19,8 @@ #define KI_TEST_BITSTREAM_U32 0x0A090807 #define KI_TEST_BITSTREAM_U64 0x1211100F0E0D0C0BL -#define KI_TEST_WRITE_BUI(n) bit_stream->write>(KI_TEST_BITSTREAM_BUI##n) -#define KI_TEST_READ_BUI(n) bit_stream->read>() == KI_TEST_BITSTREAM_BUI##n +#define KI_TEST_WRITE_BUI(n) bit_stream.write>(KI_TEST_BITSTREAM_BUI##n) +#define KI_TEST_READ_BUI(n) bit_stream.read>() == KI_TEST_BITSTREAM_BUI##n using namespace ki; @@ -31,55 +31,50 @@ TEST_CASE("BitStream::stream_pos Functionality", "[bit-stream]") SECTION("Increment single bit") { ++position; - if (position.get_byte() != 1 || position.get_bit() != 5) - FAIL(); - SUCCEED(); + REQUIRE(position.get_byte() == 1); + REQUIRE(position.get_bit() == 5); } SECTION("Decrement single bit") { --position; - if (position.get_byte() != 1 || position.get_bit() != 3) - FAIL(); - SUCCEED(); + REQUIRE(position.get_byte() == 1); + REQUIRE(position.get_bit() == 3); } SECTION("Increment bits and move to next byte") { position += 4; - if (position.get_byte() != 2 || position.get_bit() != 0) - FAIL(); - SUCCEED(); + REQUIRE(position.get_byte() == 2); + REQUIRE(position.get_bit() == 0); } SECTION("Decrement bits and move to previous byte") { position -= 5; - if (position.get_byte() != 0 || position.get_bit() != 7) - FAIL(); - SUCCEED(); + REQUIRE(position.get_byte() == 0); + REQUIRE(position.get_bit() == 7); } SECTION("Increment byte") { position += BitStream::stream_pos(1, 0); - if (position.get_byte() != 2 || position.get_bit() != 4) - FAIL(); - SUCCEED(); + REQUIRE(position.get_byte() == 2); + REQUIRE(position.get_bit() == 4); } SECTION("Decrement byte") { position -= BitStream::stream_pos(1, 0); - if (position.get_byte() != 0 || position.get_bit() != 4) - FAIL(); - SUCCEED(); + REQUIRE(position.get_byte() == 0); + REQUIRE(position.get_bit() == 4); } } TEST_CASE("BitStream Functionality", "[bit-stream]") { - auto *bit_stream = new BitStream(); + BitBuffer bit_buffer; + auto bit_stream = BitStream(bit_buffer); SECTION("Writing values with a size less than 8 bits") { @@ -94,65 +89,60 @@ TEST_CASE("BitStream Functionality", "[bit-stream]") KI_TEST_WRITE_BUI(4); // Make sure tell is reporting the right position - auto position = bit_stream->tell(); - if (position.get_byte() != 4 || position.get_bit() != 0) - FAIL(); + auto position = bit_stream.tell(); + REQUIRE(position.get_byte() == 4); + REQUIRE(position.get_bit() == 0); const auto size = position.get_byte(); // Validate what we've got here with a hand-written sample std::ifstream sample("samples/bitstream1.bin", std::ios::binary); - if (!sample.is_open()) - FAIL(); + REQUIRE(sample.is_open()); // Load the sample data and compare - auto *sample_data = new char[size + 1]{ 0 }; + auto *sample_data = new char[size]; + auto *stream_data = bit_buffer.data(); sample.read(sample_data, size); - if (strcmp(sample_data, (char *)bit_stream->data()) != 0) - FAIL(); + REQUIRE(memcmp(sample_data, stream_data, size) == 0); // Free resources delete[] sample_data; - SUCCEED(); } SECTION("Writing values with a size greater than 8 bits") { // Write some values - bit_stream->write(KI_TEST_BITSTREAM_BOOL); - bit_stream->write(KI_TEST_BITSTREAM_U8); - bit_stream->write(KI_TEST_BITSTREAM_U16); - bit_stream->write>(KI_TEST_BITSTREAM_U24); - bit_stream->write(KI_TEST_BITSTREAM_U32); - bit_stream->write(KI_TEST_BITSTREAM_U64); + bit_stream.write(KI_TEST_BITSTREAM_BOOL); + bit_stream.write(KI_TEST_BITSTREAM_U8); + bit_stream.write(KI_TEST_BITSTREAM_U16); + bit_stream.write>(KI_TEST_BITSTREAM_U24); + bit_stream.write(KI_TEST_BITSTREAM_U32); + bit_stream.write(KI_TEST_BITSTREAM_U64); // Make sure tell is reporting the right position - auto position = bit_stream->tell(); - if (position.get_byte() != 19 || position.get_bit() != 0) - FAIL(); + auto position = bit_stream.tell(); + REQUIRE(position.get_byte() == 19); + REQUIRE(position.get_bit() == 0); const auto size = position.get_byte(); // Validate what we've got here with a hand-written sample std::ifstream sample("samples/bitstream2.bin", std::ios::binary); - if (!sample.is_open()) - FAIL(); + REQUIRE(sample.is_open()); // Load the sample data and compare - auto *sample_data = new char[size + 1]{ 0 }; + auto *stream_data = bit_buffer.data(); + auto *sample_data = new char[size]; sample.read(sample_data, size); - if (strcmp(sample_data, (char *)bit_stream->data()) != 0) - FAIL(); + REQUIRE(memcmp(sample_data, stream_data, size) == 0); // Free resources delete[] sample_data; - SUCCEED(); } SECTION("Reading values with a size lower than 8 bits") { // Open a previously hand-written sample std::ifstream sample("samples/bitstream1.bin", std::ios::binary); - if (!sample.is_open()) - FAIL(); + REQUIRE(sample.is_open()); // Load the sample data into the bit stream const auto begin = sample.tellg(); @@ -160,35 +150,25 @@ TEST_CASE("BitStream Functionality", "[bit-stream]") const auto end = sample.tellg(); const size_t size = end - begin; sample.seekg(std::ios::beg); - sample.read((char *)bit_stream->data(), size); + auto *stream_data = reinterpret_cast(bit_buffer.data()); + sample.read(stream_data, size); // Read the values and check they are what we are expecting - if (!KI_TEST_READ_BUI(1)) - FAIL(); - if (!KI_TEST_READ_BUI(2)) - FAIL(); - if (!KI_TEST_READ_BUI(3)) - FAIL(); - if (!KI_TEST_READ_BUI(4)) - FAIL(); - if (!KI_TEST_READ_BUI(5)) - FAIL(); - if (!KI_TEST_READ_BUI(6)) - FAIL(); - if (!KI_TEST_READ_BUI(7)) - FAIL(); - if (!KI_TEST_READ_BUI(4)) - FAIL(); - - SUCCEED(); + REQUIRE(KI_TEST_READ_BUI(1)); + REQUIRE(KI_TEST_READ_BUI(2)); + REQUIRE(KI_TEST_READ_BUI(3)); + REQUIRE(KI_TEST_READ_BUI(4)); + REQUIRE(KI_TEST_READ_BUI(5)); + REQUIRE(KI_TEST_READ_BUI(6)); + REQUIRE(KI_TEST_READ_BUI(7)); + REQUIRE(KI_TEST_READ_BUI(4)); } SECTION("Reading values with a size greater than 8 bits") { // Open a previously hand-written sample std::ifstream sample("samples/bitstream2.bin", std::ios::binary); - if (!sample.is_open()) - FAIL(); + REQUIRE(sample.is_open()); // Load the sample data into the bit stream const auto begin = sample.tellg(); @@ -196,63 +176,39 @@ TEST_CASE("BitStream Functionality", "[bit-stream]") const auto end = sample.tellg(); const size_t size = end - begin; sample.seekg(std::ios::beg); - sample.read((char *)bit_stream->data(), size); + auto *stream_data = reinterpret_cast(bit_buffer.data()); + sample.read(stream_data, size); // Read the values and check they are what we are expecting - if (bit_stream->read() != KI_TEST_BITSTREAM_BOOL) - FAIL(); - if (bit_stream->read() != KI_TEST_BITSTREAM_U8) - FAIL(); - if (bit_stream->read() != KI_TEST_BITSTREAM_U16) - FAIL(); - if (bit_stream->read>() != KI_TEST_BITSTREAM_U24) - FAIL(); - if (bit_stream->read() != KI_TEST_BITSTREAM_U32) - FAIL(); - if (bit_stream->read() != KI_TEST_BITSTREAM_U64) - FAIL(); - - SUCCEED(); + REQUIRE(bit_stream.read() == KI_TEST_BITSTREAM_BOOL); + REQUIRE(bit_stream.read() == KI_TEST_BITSTREAM_U8); + REQUIRE(bit_stream.read() == KI_TEST_BITSTREAM_U16); + REQUIRE(bit_stream.read>() == KI_TEST_BITSTREAM_U24); + REQUIRE(bit_stream.read() == KI_TEST_BITSTREAM_U32); + REQUIRE(bit_stream.read() == KI_TEST_BITSTREAM_U64); } - SECTION("Buffer underflow") + SECTION("Overwriting values") { - try - { - // Attempt to set the position less than 0 - bit_stream->seek(BitStream::stream_pos(-1, 0)); - FAIL(); - } - catch (std::runtime_error &e) - { - // An exception was thrown, which is intended behaviour - SUCCEED(); - } + bit_stream.write>(0b11); + bit_stream.seek(BitStream::stream_pos(0, 0)); } - - // Free resources - delete bit_stream; } TEST_CASE("BitStream buffer expansion", "[bit-stream]") { // Create a very small buffer that we know is going to expand - auto *bit_stream = new BitStream(1); + auto bit_buffer = BitBuffer(1); + auto bit_stream = BitStream(bit_buffer); // Write a byte, and then check if the capacity grows - bit_stream->write(0x55, 8); - if (bit_stream->capacity() != 6) - FAIL(); + bit_stream.write(0x55, 8); + REQUIRE(bit_stream.capacity() == 6); // Go back to the first byte, and make sure what we wrote stuck // around after the expansion - bit_stream->seek(BitStream::stream_pos(0, 0)); - if (bit_stream->read(8) != 0x55) - FAIL(); - - // Free resources - delete bit_stream; - SUCCEED(); + bit_stream.seek(BitStream::stream_pos(0, 0)); + REQUIRE(bit_stream.read(8) == 0x55); } /**