mirror of https://github.com/SeanOMik/libki.git
etc: Implement deserialization and object copying
Also make BitStream tests use REQUIRE instead of SUCCESS and FAIL for better output.
This commit is contained in:
parent
ad448befe6
commit
3a22c992b5
|
@ -13,10 +13,17 @@ 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() {}
|
||||
|
||||
PropertyBase(PropertyClass &object,
|
||||
const PropertyBase &that);
|
||||
|
||||
std::string get_name() const;
|
||||
hash_t get_name_hash() const;
|
||||
hash_t get_full_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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,15 @@
|
|||
#define _KI_PCLASS_CONSTRUCTOR(derived) \
|
||||
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)
|
||||
|
||||
#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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace ki
|
|||
{
|
||||
namespace pclass
|
||||
{
|
||||
class PropertyClass;
|
||||
class PropertyBase;
|
||||
|
||||
/**
|
||||
|
@ -14,25 +15,75 @@ namespace pclass
|
|||
*/
|
||||
class PropertyList
|
||||
{
|
||||
friend PropertyBase;
|
||||
friend PropertyClass;
|
||||
|
||||
public:
|
||||
using const_iterator = std::vector<PropertyBase *>::const_iterator;
|
||||
/**
|
||||
* TODO: Documentation
|
||||
*/
|
||||
class iterator
|
||||
{
|
||||
friend PropertyList;
|
||||
|
||||
public:
|
||||
PropertyBase &operator*() const;
|
||||
PropertyBase *operator->() const;
|
||||
iterator &operator++();
|
||||
iterator operator++(int);
|
||||
bool operator==(const iterator &that) const;
|
||||
bool operator!=(const iterator &that) const;
|
||||
|
||||
private:
|
||||
PropertyList *m_list;
|
||||
int m_index;
|
||||
|
||||
explicit iterator(PropertyList &list, const int index = 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Documentation
|
||||
*/
|
||||
class const_iterator
|
||||
{
|
||||
friend PropertyList;
|
||||
|
||||
public:
|
||||
const PropertyBase &operator*() const;
|
||||
const PropertyBase *operator->() const;
|
||||
const_iterator &operator++();
|
||||
const_iterator operator++(int);
|
||||
bool operator==(const const_iterator &that) const;
|
||||
bool operator!=(const const_iterator &that) const;
|
||||
|
||||
private:
|
||||
const PropertyList *m_list;
|
||||
int m_index;
|
||||
|
||||
explicit const_iterator(const PropertyList &list, const int index = 0);
|
||||
};
|
||||
|
||||
std::size_t get_property_count() const;
|
||||
|
||||
bool has_property(const std::string &name) const;
|
||||
bool has_property(hash_t hash) const;
|
||||
|
||||
PropertyBase &get_property(int index);
|
||||
const PropertyBase &get_property(int index) const;
|
||||
|
||||
PropertyBase &get_property(const std::string &name);
|
||||
const PropertyBase &get_property(const std::string &name) const;
|
||||
|
||||
PropertyBase &get_property(hash_t hash);
|
||||
const PropertyBase &get_property(hash_t hash) const;
|
||||
|
||||
iterator begin();
|
||||
const_iterator begin() const;
|
||||
|
||||
iterator end();
|
||||
const_iterator end() const;
|
||||
|
||||
protected:
|
||||
void add_property(PropertyBase *prop);
|
||||
void add_property(PropertyBase &prop);
|
||||
|
||||
private:
|
||||
std::vector<PropertyBase *> m_properties;
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace pclass
|
|||
template <typename ValueT>
|
||||
class StaticProperty;
|
||||
|
||||
/// @cond DOXYGEN_SKIP
|
||||
/**
|
||||
* A helper utility that provides the right implementation of construct()
|
||||
* and get_object() based on characteristics of type: ValueT.
|
||||
|
@ -28,6 +29,13 @@ namespace pclass
|
|||
return ValueT();
|
||||
}
|
||||
|
||||
static ValueT copy(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// In cases where ValueT is not a pointer, and does not derive from PropertyClass,
|
||||
// just call the copy constructor.
|
||||
return ValueT(prop.m_value);
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
|
@ -36,6 +44,15 @@ namespace pclass
|
|||
"Tried calling get_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
|
||||
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
// storing an object.
|
||||
throw runtime_error(
|
||||
"Tried calling set_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -43,8 +60,9 @@ namespace pclass
|
|||
* - A pointer; but
|
||||
* - does not derive from PropertyClass
|
||||
*
|
||||
* This should construct to a nullptr, and throw an exception
|
||||
* when get_object()is called.
|
||||
* This should:
|
||||
* - Construct to a nullptr; and
|
||||
* - Throw an exception when get_object() is called.
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct value_object_helper<
|
||||
|
@ -66,6 +84,13 @@ namespace pclass
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static ValueT copy(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// The copy constructor for all pointers is to copy the pointer
|
||||
// without creating a new copy of the object it's pointing too.
|
||||
return prop.m_value;
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
|
@ -74,6 +99,15 @@ namespace pclass
|
|||
"Tried calling get_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
|
||||
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
// storing an object.
|
||||
throw runtime_error(
|
||||
"Tried calling set_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -81,8 +115,10 @@ namespace pclass
|
|||
* - A pointer; and
|
||||
* - does derive from PropertyClass
|
||||
*
|
||||
* This should construct to a nullptr, and return a pointer to
|
||||
* a ValueT instance (as a PropertyClass *) when get_object() is called.
|
||||
* This should:
|
||||
* - Construct to a nullptr; and
|
||||
* - Return a pointer to a ValueT instance (as a PropertyClass *)
|
||||
* when get_object() is called.
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct value_object_helper<
|
||||
|
@ -104,12 +140,26 @@ namespace pclass
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static ValueT copy(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// The copy constructor for all pointers is to copy the pointer
|
||||
// without creating a new copy of the object it's pointing too.
|
||||
return prop.m_value;
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// ValueT does derive from PropertyClass, and we have a pointer to an instance
|
||||
// of ValueT, so we can cast down to a PropertyClass pointer.
|
||||
return dynamic_cast<const PropertyClass *>(prop.m_value);
|
||||
}
|
||||
|
||||
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
|
||||
{
|
||||
// ValueT does derive from PropertyClass, and we have a pointer to an instance
|
||||
// of PropertyClass, so cast the pointer up to a ValueT.
|
||||
prop.m_value = dynamic_cast<ValueT>(object);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -117,9 +167,11 @@ namespace pclass
|
|||
* - Not a pointer; and
|
||||
* - does derive from PropertyClass
|
||||
*
|
||||
* This should construct an instance of ValueT by passing the property
|
||||
* type and the type's type system, and return a pointer to a ValueT
|
||||
* instance (as a PropertyClass *) when get_object() is called.
|
||||
* This should:
|
||||
* - Construct an instance of ValueT by passing the property
|
||||
* type and the type's type system; and
|
||||
* - Return a pointer to a ValueT instance (as a PropertyClass *)
|
||||
* when get_object() is called.
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct value_object_helper<
|
||||
|
@ -143,12 +195,29 @@ namespace pclass
|
|||
return ValueT(type, type.get_type_system());
|
||||
}
|
||||
|
||||
static ValueT copy(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// Derivitives of PropertyClass implement a clone method that returns
|
||||
// a clone as a pointer.
|
||||
ValueT *value_ptr = dynamic_cast<ValueT *>(prop.m_value.clone());
|
||||
ValueT value = *value_ptr;
|
||||
delete value_ptr;
|
||||
return value;
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
// ValueT does derive from PropertyClass, and we have an instance of ValueT,
|
||||
// so we can cast down to a PropertyClass pointer.
|
||||
return dynamic_cast<const PropertyClass *>(&prop.m_value);
|
||||
}
|
||||
|
||||
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
|
||||
{
|
||||
// ValueT does derive from PropertyClass, but we don't store a pointer,
|
||||
// so we need to copy the value in.
|
||||
prop.m_value = *object;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -161,12 +230,12 @@ namespace pclass
|
|||
>
|
||||
struct value_rw_helper
|
||||
{
|
||||
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
|
||||
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
|
||||
{
|
||||
prop.get_type().write_to(stream, prop.m_value);
|
||||
}
|
||||
|
||||
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
|
||||
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
|
||||
{
|
||||
prop.get_type().read_from(stream, Value(prop.m_value));
|
||||
}
|
||||
|
@ -187,12 +256,12 @@ namespace pclass
|
|||
>::type
|
||||
>
|
||||
{
|
||||
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
|
||||
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
|
||||
{
|
||||
prop.get_type().write_to(stream, *prop.m_value);
|
||||
}
|
||||
|
||||
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
|
||||
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
|
||||
{
|
||||
prop.get_type().read_from(stream, Value(*prop.m_value));
|
||||
}
|
||||
|
@ -210,21 +279,32 @@ namespace pclass
|
|||
return value_object_helper<ValueT>::construct(type);
|
||||
}
|
||||
|
||||
static ValueT copy(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
return value_object_helper<ValueT>::copy(prop);
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const StaticProperty<ValueT> &prop)
|
||||
{
|
||||
return value_object_helper<ValueT>::get_object(prop);
|
||||
}
|
||||
|
||||
static void write(const StaticProperty<ValueT> &prop, BitStreamBase &stream)
|
||||
static void set_object(StaticProperty<ValueT> &prop, PropertyClass *object)
|
||||
{
|
||||
value_object_helper<ValueT>::set_object(prop, object);
|
||||
}
|
||||
|
||||
static void write(const StaticProperty<ValueT> &prop, BitStream &stream)
|
||||
{
|
||||
value_rw_helper<ValueT>::write(prop, stream);
|
||||
}
|
||||
|
||||
static void read(StaticProperty<ValueT> &prop, BitStreamBase &stream)
|
||||
static void read(StaticProperty<ValueT> &prop, BitStream &stream)
|
||||
{
|
||||
value_rw_helper<ValueT>::read(prop, stream);
|
||||
}
|
||||
};
|
||||
/// @endcond
|
||||
|
||||
/**
|
||||
* TODO: Documentation
|
||||
|
@ -237,6 +317,10 @@ namespace pclass
|
|||
friend value_rw_helper<ValueT>;
|
||||
|
||||
public:
|
||||
// Do not allow copy assignment. Once a property has been constructed,
|
||||
// it shouldn't be able to change.
|
||||
StaticProperty<ValueT> &operator=(const StaticProperty<ValueT> &that) = delete;
|
||||
|
||||
StaticProperty(PropertyClass &object,
|
||||
const std::string &name, const Type &type)
|
||||
: PropertyBase(object, name, type)
|
||||
|
@ -250,34 +334,19 @@ namespace pclass
|
|||
m_value = value;
|
||||
}
|
||||
|
||||
constexpr bool is_dynamic() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
StaticProperty(PropertyClass &object, const StaticProperty<ValueT> &that)
|
||||
: PropertyBase(object, that)
|
||||
, m_value(value_helper<ValueT>::copy(that))
|
||||
{}
|
||||
|
||||
constexpr bool is_pointer() const override
|
||||
{
|
||||
return std::is_pointer<ValueT>::value;
|
||||
}
|
||||
|
||||
void write_value_to(BitStreamBase &stream) const override
|
||||
constexpr bool is_dynamic() const override
|
||||
{
|
||||
value_helper<ValueT>::write(*this, stream);
|
||||
}
|
||||
|
||||
void read_value_from(BitStreamBase &stream) override
|
||||
{
|
||||
value_helper<ValueT>::read(*this, stream);
|
||||
}
|
||||
|
||||
const PropertyClass *get_object() const override
|
||||
{
|
||||
return value_helper<ValueT>::get_object(*this);
|
||||
}
|
||||
|
||||
ValueT &get()
|
||||
{
|
||||
return m_value;
|
||||
return false;
|
||||
}
|
||||
|
||||
Value get_value() const override
|
||||
|
@ -285,12 +354,52 @@ namespace pclass
|
|||
return m_value;
|
||||
}
|
||||
|
||||
operator ValueT &() const
|
||||
const PropertyClass *get_object() const override
|
||||
{
|
||||
return value_helper<ValueT>::get_object(*this);
|
||||
}
|
||||
|
||||
void set_object(PropertyClass *object) override
|
||||
{
|
||||
return value_helper<ValueT>::set_object(*this, object);
|
||||
}
|
||||
|
||||
void write_value_to(BitStream &stream) const override
|
||||
{
|
||||
value_helper<ValueT>::write(*this, stream);
|
||||
}
|
||||
|
||||
void read_value_from(BitStream &stream) override
|
||||
{
|
||||
value_helper<ValueT>::read(*this, stream);
|
||||
}
|
||||
|
||||
ValueT &get()
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
ValueT *operator&() const
|
||||
const ValueT &get() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
operator ValueT &()
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
operator const ValueT &() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
ValueT *operator&()
|
||||
{
|
||||
return &m_value;
|
||||
}
|
||||
|
||||
const ValueT *operator&() const
|
||||
{
|
||||
return &m_value;
|
||||
}
|
||||
|
|
|
@ -7,159 +7,307 @@ namespace ki
|
|||
{
|
||||
namespace pclass
|
||||
{
|
||||
// Forward declare for our helpers
|
||||
template <typename ValueT>
|
||||
class VectorPropertyBase : public std::vector<ValueT>, public DynamicPropertyBase
|
||||
class VectorProperty;
|
||||
|
||||
/// @cond DOXYGEN_SKIP
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename ValueT,
|
||||
typename IsPointerEnable = void,
|
||||
typename IsBaseEnable = void
|
||||
>
|
||||
struct vector_value_object_helper
|
||||
{
|
||||
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
return ValueT(prop.at(index));
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
// storing an object.
|
||||
throw runtime_error(
|
||||
"Tried calling get_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
|
||||
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
// storing an object.
|
||||
throw runtime_error(
|
||||
"Tried calling set_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct vector_value_object_helper<
|
||||
ValueT,
|
||||
typename std::enable_if<
|
||||
std::is_pointer<ValueT>::value
|
||||
>::type,
|
||||
typename std::enable_if<
|
||||
!std::is_base_of<
|
||||
PropertyClass,
|
||||
typename std::remove_pointer<ValueT>::type
|
||||
>::value
|
||||
>::type
|
||||
>
|
||||
{
|
||||
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
return prop.at(index);
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
// storing an object.
|
||||
throw runtime_error(
|
||||
"Tried calling get_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
|
||||
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
|
||||
{
|
||||
// ValueT does not derive from PropertyClass, and so, this property is not
|
||||
// storing an object.
|
||||
throw runtime_error(
|
||||
"Tried calling set_object() on a property that does not store an object."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct vector_value_object_helper<
|
||||
ValueT,
|
||||
typename std::enable_if<
|
||||
!std::is_pointer<ValueT>::value
|
||||
>::type,
|
||||
typename std::enable_if<
|
||||
std::is_base_of<
|
||||
PropertyClass,
|
||||
typename std::remove_pointer<ValueT>::type
|
||||
>::value
|
||||
>::type
|
||||
>
|
||||
{
|
||||
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
ValueT *value_ptr = dynamic_cast<ValueT *>(prop.at(index).copy());
|
||||
ValueT value = *value_ptr;
|
||||
delete value_ptr;
|
||||
return value;
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
return dynamic_cast<PropertyClass *>(&prop.at(index));
|
||||
}
|
||||
|
||||
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
prop.at(index) = dynamic_cast<ValueT>(*object);
|
||||
delete object;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct vector_value_object_helper<
|
||||
ValueT,
|
||||
typename std::enable_if<
|
||||
std::is_pointer<ValueT>::value
|
||||
>::type,
|
||||
typename std::enable_if<
|
||||
std::is_base_of<
|
||||
PropertyClass,
|
||||
typename std::remove_pointer<ValueT>::type
|
||||
>::value
|
||||
>::type
|
||||
>
|
||||
{
|
||||
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
return prop.at(index);
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
return dynamic_cast<PropertyClass *>(prop.at(index));
|
||||
}
|
||||
|
||||
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
prop.at(index) = dynamic_cast<ValueT>(object);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <
|
||||
typename ValueT,
|
||||
typename IsPointerEnable = void
|
||||
>
|
||||
struct vector_value_rw_helper
|
||||
{
|
||||
static void write_value_to(const VectorProperty<ValueT> &prop, BitStream &stream, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
prop.get_type().write_to(stream, prop.at(index));
|
||||
}
|
||||
|
||||
static void read_value_from(VectorProperty<ValueT> &prop, BitStream &stream, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
prop.get_type().read_from(stream, Value(prop.at(index)));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct vector_value_rw_helper<
|
||||
ValueT,
|
||||
typename std::enable_if<std::is_pointer<ValueT>::value>::type
|
||||
>
|
||||
{
|
||||
static void write_value_to(const VectorProperty<ValueT> &prop, BitStream &stream, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
prop.get_type().write_to(stream, *prop.at(index));
|
||||
}
|
||||
|
||||
static void read_value_from(VectorProperty<ValueT> &prop, BitStream &stream, const int index)
|
||||
{
|
||||
if (index < 0 || index >= prop.size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
prop.get_type().read_from(stream, Value(*prop.at(index)));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <typename ValueT>
|
||||
struct vector_value_helper
|
||||
{
|
||||
static ValueT copy(VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
return vector_value_object_helper<ValueT>::copy(prop, index);
|
||||
}
|
||||
|
||||
static const PropertyClass *get_object(const VectorProperty<ValueT> &prop, const int index)
|
||||
{
|
||||
return vector_value_object_helper<ValueT>::get_object(prop, index);
|
||||
}
|
||||
|
||||
static void set_object(VectorProperty<ValueT> &prop, PropertyClass *object, const int index)
|
||||
{
|
||||
vector_value_object_helper<ValueT>::set_object(prop, object, index);
|
||||
}
|
||||
|
||||
static void write_value_to(const VectorProperty<ValueT> &prop, BitStream &stream, const int index)
|
||||
{
|
||||
vector_value_rw_helper<ValueT>::write_value_to(prop, stream, index);
|
||||
}
|
||||
|
||||
static void read_value_from(VectorProperty<ValueT> &prop, BitStream &stream, const int index)
|
||||
{
|
||||
vector_value_rw_helper<ValueT>::read_value_from(prop, stream, index);
|
||||
}
|
||||
};
|
||||
/// @endcond
|
||||
|
||||
template <typename ValueT>
|
||||
class VectorProperty : public std::vector<ValueT>, public DynamicPropertyBase
|
||||
{
|
||||
public:
|
||||
VectorPropertyBase(PropertyClass &object,
|
||||
// Do not allow copy assignment. Once a property has been constructed,
|
||||
// it shouldn't be able to change.
|
||||
VectorProperty<ValueT> &operator=(const VectorProperty<ValueT> &that) = delete;
|
||||
|
||||
VectorProperty(PropertyClass &object,
|
||||
const std::string &name, const Type &type)
|
||||
: DynamicPropertyBase(object, name, type) { }
|
||||
: DynamicPropertyBase(object, name, type)
|
||||
{}
|
||||
|
||||
VectorProperty(PropertyClass &object,
|
||||
const VectorProperty<ValueT> &that)
|
||||
: DynamicPropertyBase(object, that)
|
||||
{
|
||||
// Copy vector values into this vector
|
||||
for (auto i = 0; i < this->size(); i++)
|
||||
this->push_back(vector_value_helper<ValueT>::copy(*this, i));
|
||||
}
|
||||
|
||||
constexpr bool is_pointer() const override
|
||||
{
|
||||
return std::is_pointer<ValueT>::value;
|
||||
}
|
||||
|
||||
std::size_t get_element_count() const override
|
||||
{
|
||||
return this->size();
|
||||
}
|
||||
|
||||
void set_element_count(const std::size_t size) override
|
||||
{
|
||||
this->resize(size);
|
||||
}
|
||||
|
||||
Value get_value(int index) const override
|
||||
{
|
||||
if (index < 0 || index >= this->size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
return this->at(index);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ValueT, typename Enable = void>
|
||||
class VectorPropertyBase2 : public VectorPropertyBase<ValueT>
|
||||
{
|
||||
VectorPropertyBase2(PropertyClass &object,
|
||||
const std::string &name, const Type &type)
|
||||
: VectorPropertyBase<ValueT>(object, name, type) { }
|
||||
|
||||
constexpr bool is_pointer() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void write_value_to(BitStreamBase &stream, const int index) const override
|
||||
{
|
||||
if (index < 0 || index >= this->size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
this->get_type().write_to(stream, this->at(index));
|
||||
}
|
||||
|
||||
void read_value_from(BitStreamBase &stream, const int index) override
|
||||
{
|
||||
if (index < 0 || index >= this->size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
this->get_type().read_from(stream, this->at(index));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ValueT>
|
||||
class VectorPropertyBase2<
|
||||
ValueT,
|
||||
typename std::enable_if<
|
||||
std::is_pointer<ValueT>::value
|
||||
>::type
|
||||
> : public VectorPropertyBase<ValueT>
|
||||
{
|
||||
public:
|
||||
VectorPropertyBase2(PropertyClass &object,
|
||||
const std::string &name, const Type &type)
|
||||
: VectorPropertyBase<ValueT>(object, name, type) { }
|
||||
|
||||
constexpr bool is_pointer() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void write_value_to(BitStreamBase &stream, const int index) const override
|
||||
{
|
||||
if (index < 0 || index >= this->size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
this->get_type().write_to(stream, *this->at(index));
|
||||
}
|
||||
|
||||
void read_value_from(BitStreamBase &stream, const int index) override
|
||||
{
|
||||
if (index < 0 || index >= this->size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
this->get_type().read_from(stream, Value(*this->at(index)));
|
||||
}
|
||||
};
|
||||
|
||||
template <
|
||||
typename ValueT,
|
||||
typename _IsBaseEnable = void,
|
||||
typename _IsPointerEnable = void
|
||||
>
|
||||
class VectorProperty : public VectorPropertyBase2<ValueT>
|
||||
{
|
||||
public:
|
||||
VectorProperty(PropertyClass &object,
|
||||
const std::string &name, const Type &type)
|
||||
: VectorPropertyBase2<ValueT>(object, name, type) { }
|
||||
|
||||
const PropertyClass *get_object(const int index) const override
|
||||
{
|
||||
// We aren't holding an object at all, whoever called this is mistaken.
|
||||
throw runtime_error(
|
||||
"Tried calling get_object(index) on a property that does not store an object."
|
||||
);
|
||||
return vector_value_helper<ValueT>::get_object(*this, index);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ValueT>
|
||||
class VectorProperty<
|
||||
ValueT,
|
||||
typename std::enable_if<
|
||||
std::is_base_of<
|
||||
PropertyClass,
|
||||
typename std::remove_pointer<ValueT>::type
|
||||
>::value
|
||||
>::type,
|
||||
typename std::enable_if<
|
||||
!std::is_pointer<ValueT>::value
|
||||
>::type
|
||||
> : public VectorPropertyBase2<ValueT>
|
||||
void set_object(PropertyClass *object, int index) override
|
||||
{
|
||||
public:
|
||||
VectorProperty(PropertyClass &object,
|
||||
const std::string &name, const Type &type)
|
||||
: VectorPropertyBase2<ValueT>(object, name, type) { }
|
||||
|
||||
const PropertyClass *get_object(const int index) const override
|
||||
{
|
||||
if (index < 0 || index >= this->size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
return dynamic_cast<PropertyClass *>(&this->at(index));
|
||||
return vector_value_helper<ValueT>::set_object(*this, object, index);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ValueT>
|
||||
class VectorProperty<
|
||||
ValueT,
|
||||
typename std::enable_if<
|
||||
std::is_base_of<
|
||||
PropertyClass,
|
||||
typename std::remove_pointer<ValueT>::type
|
||||
>::value
|
||||
>::type,
|
||||
typename std::enable_if<
|
||||
std::is_pointer<ValueT>::value
|
||||
>::type
|
||||
> : public VectorPropertyBase2<ValueT>
|
||||
void write_value_to(BitStream &stream, const int index) const override
|
||||
{
|
||||
public:
|
||||
VectorProperty(PropertyClass &object,
|
||||
const std::string &name, const Type &type)
|
||||
: VectorPropertyBase2<ValueT>(object, name, type) { }
|
||||
vector_value_helper<ValueT>::write_value_to(*this, stream, index);
|
||||
}
|
||||
|
||||
const PropertyClass *get_object(const int index) const override
|
||||
void read_value_from(BitStream &stream, const int index) override
|
||||
{
|
||||
if (index < 0 || index >= this->size())
|
||||
throw runtime_error("Index out of bounds.");
|
||||
return dynamic_cast<PropertyClass *>(this->at(index));
|
||||
vector_value_helper<ValueT>::read_value_from(*this, stream, index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "ki/pclass/Property.h"
|
||||
#include "ki/pclass/types/Type.h"
|
||||
#include "ki/pclass/PropertyClass.h"
|
||||
#include "ki/util/exception.h"
|
||||
|
||||
namespace ki
|
||||
{
|
||||
|
@ -45,20 +44,20 @@ namespace pclass
|
|||
return new ClassT(*this, get_type_system());
|
||||
}
|
||||
|
||||
void write_to(BitStreamBase &stream, const Value &value) const override
|
||||
void write_to(BitStream &stream, const Value &value) const override
|
||||
{
|
||||
const auto &object = dynamic_cast<const PropertyClass &>(value.get<ClassT>());
|
||||
const auto &properties = object.get_properties();
|
||||
for (auto it = properties.begin(); it != properties.end(); ++it)
|
||||
(*it)->write_value_to(stream);
|
||||
it->write_value_to(stream);
|
||||
}
|
||||
|
||||
void read_from(BitStreamBase &stream, Value &value) const override
|
||||
void read_from(BitStream &stream, Value &value) const override
|
||||
{
|
||||
auto &object = dynamic_cast<PropertyClass &>(value.get<ClassT>());
|
||||
auto &properties = object.get_properties();
|
||||
for (auto it = properties.begin(); it != properties.end(); ++it)
|
||||
(*it)->read_value_from(stream);
|
||||
it->read_value_from(stream);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace pclass
|
|||
typename std::enable_if<std::is_floating_point<ValueT>::value>::type
|
||||
>
|
||||
{
|
||||
static void write_to(BitStreamBase &stream, const ValueT &value)
|
||||
static void write_to(BitStream &stream, const ValueT &value)
|
||||
{
|
||||
// Reinterpret the reference as a reference to an integer
|
||||
const uint_type &v = *(
|
||||
|
@ -35,7 +35,7 @@ namespace pclass
|
|||
typename std::enable_if<std::is_floating_point<ValueT>::value>::type
|
||||
>
|
||||
{
|
||||
static void read_from(BitStreamBase &stream, ValueT &value)
|
||||
static void read_from(BitStream &stream, ValueT &value)
|
||||
{
|
||||
// Reinterpret the reference as a reference to an integer
|
||||
uint_type &v = *(
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace pclass
|
|||
typename std::enable_if<is_integral<ValueT>::value>::type
|
||||
>
|
||||
{
|
||||
static void write_to(BitStreamBase &stream, const ValueT &value)
|
||||
static void write_to(BitStream &stream, const ValueT &value)
|
||||
{
|
||||
stream.write<ValueT>(value);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace pclass
|
|||
typename std::enable_if<is_integral<ValueT>::value>::type
|
||||
>
|
||||
{
|
||||
static void read_from(BitStreamBase &stream, ValueT &value)
|
||||
static void read_from(BitStream &stream, ValueT &value)
|
||||
{
|
||||
value = stream.read<ValueT>();
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace pclass
|
|||
template <typename ValueT, typename Enable = void>
|
||||
struct PrimitiveTypeWriter
|
||||
{
|
||||
static void write_to(BitStreamBase &stream, const ValueT &value)
|
||||
static void write_to(BitStream &stream, const ValueT &value)
|
||||
{
|
||||
// Provide a compiler error if this is not specialized
|
||||
static_assert(
|
||||
|
@ -27,7 +27,7 @@ namespace pclass
|
|||
template <typename ValueT, typename Enable = void>
|
||||
struct PrimitiveTypeReader
|
||||
{
|
||||
static void read_from(BitStreamBase &stream, ValueT &value)
|
||||
static void read_from(BitStream &stream, ValueT &value)
|
||||
{
|
||||
// Provide a compiler error if this is not specialized
|
||||
static_assert(
|
||||
|
@ -50,14 +50,14 @@ namespace pclass
|
|||
m_kind = kind::PRIMITIVE;
|
||||
}
|
||||
|
||||
void write_to(BitStreamBase &stream, const Value &value) const override
|
||||
void write_to(BitStream &stream, const Value &value) const override
|
||||
{
|
||||
if (!value.is<ValueT>())
|
||||
throw std::runtime_error("Invalid call to Type::write_to -- value type does not match ValueT.");
|
||||
PrimitiveTypeWriter<ValueT>::write_to(stream, value.get<ValueT>());
|
||||
}
|
||||
|
||||
void read_from(BitStreamBase &stream, Value &value) const override
|
||||
void read_from(BitStream &stream, Value &value) const override
|
||||
{
|
||||
if (!value.is<ValueT>())
|
||||
throw std::runtime_error("Invalid call to Type::read_from -- value type does not match ValueT.");
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace pclass
|
|||
using type = std::basic_string<_Elem, _Traits, _Alloc>;
|
||||
|
||||
public:
|
||||
static void write_to(BitStreamBase &stream, const type &value)
|
||||
static void write_to(BitStream &stream, const type &value)
|
||||
{
|
||||
// Write the length as an unsigned short
|
||||
stream.write<uint16_t>(value.length());
|
||||
|
@ -38,7 +38,7 @@ namespace pclass
|
|||
using type = std::basic_string<_Elem, _Traits, _Alloc>;
|
||||
|
||||
public:
|
||||
static void read_from(BitStreamBase &stream, type &value)
|
||||
static void read_from(BitStream &stream, type &value)
|
||||
{
|
||||
// Read the length and create a new string with the correct capacity
|
||||
auto length = stream.read<uint16_t>();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -3,181 +3,343 @@
|
|||
#include <type_traits>
|
||||
#include <sstream>
|
||||
#include "ki/util/BitTypes.h"
|
||||
#include "exception.h"
|
||||
|
||||
#define KI_BITSTREAM_DEFAULT_BUFFER_SIZE 0x2000
|
||||
#define KI_BITBUFFER_DEFAULT_SIZE 0x2000
|
||||
|
||||
namespace ki
|
||||
{
|
||||
// Forward declaration so that buffers can create segments of themselves
|
||||
class BitBufferSegment;
|
||||
|
||||
/**
|
||||
* An abstract base class that provides a common interface for
|
||||
* writing to, reading from, and querying bit streams.
|
||||
*
|
||||
*/
|
||||
class BitStreamBase
|
||||
class BitBufferBase
|
||||
{
|
||||
friend BitBufferSegment;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Represents a position in a BitStream's buffer.
|
||||
*
|
||||
*/
|
||||
struct stream_pos
|
||||
struct buffer_pos
|
||||
{
|
||||
explicit stream_pos(intmax_t byte = 0, int bit = 0);
|
||||
stream_pos(const stream_pos &cp);
|
||||
explicit buffer_pos(uint32_t byte = 0, int bit = 0);
|
||||
buffer_pos(const buffer_pos &cp);
|
||||
|
||||
intmax_t as_bits() const;
|
||||
intmax_t get_byte() const;
|
||||
uint32_t as_bytes() const;
|
||||
uint32_t as_bits() const;
|
||||
uint32_t get_byte() const;
|
||||
uint8_t get_bit() const;
|
||||
|
||||
stream_pos operator +(const stream_pos &rhs) const;
|
||||
stream_pos operator -(const stream_pos &rhs) const;
|
||||
stream_pos operator +(const int &rhs) const;
|
||||
stream_pos operator -(const int &rhs) const;
|
||||
stream_pos &operator +=(stream_pos lhs);
|
||||
stream_pos &operator -=(stream_pos lhs);
|
||||
stream_pos &operator +=(int bits);
|
||||
stream_pos &operator -=(int bits);
|
||||
stream_pos &operator ++();
|
||||
stream_pos &operator --();
|
||||
stream_pos operator ++(int increment);
|
||||
stream_pos operator --(int increment);
|
||||
buffer_pos operator +(const buffer_pos &rhs) const;
|
||||
buffer_pos operator -(const buffer_pos &rhs) const;
|
||||
buffer_pos operator +(const int &rhs) const;
|
||||
buffer_pos operator -(const int &rhs) const;
|
||||
buffer_pos &operator +=(buffer_pos lhs);
|
||||
buffer_pos &operator -=(buffer_pos lhs);
|
||||
buffer_pos &operator +=(int bits);
|
||||
buffer_pos &operator -=(int bits);
|
||||
buffer_pos &operator ++();
|
||||
buffer_pos &operator --();
|
||||
buffer_pos operator ++(int increment);
|
||||
buffer_pos operator --(int increment);
|
||||
|
||||
private:
|
||||
intmax_t m_byte;
|
||||
uint32_t m_byte;
|
||||
uint8_t m_bit;
|
||||
|
||||
void set_bit(int bit);
|
||||
};
|
||||
|
||||
virtual ~BitStreamBase() {}
|
||||
virtual ~BitBufferBase() {};
|
||||
|
||||
/**
|
||||
* @returns The stream's current position.
|
||||
* @returns
|
||||
*/
|
||||
virtual stream_pos tell() const = 0;
|
||||
virtual uint8_t *data() const = 0;
|
||||
|
||||
/**
|
||||
* Sets the position of the stream.
|
||||
* @param position The new position of the stream.
|
||||
* @returns The size of the buffer in bytes.
|
||||
*/
|
||||
virtual void seek(stream_pos position) = 0;
|
||||
virtual std::size_t size() const = 0;
|
||||
|
||||
/**
|
||||
* @returns The current size of the internal buffer.
|
||||
* 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 std::size_t capacity() const = 0;
|
||||
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.
|
||||
* Reads a value from the buffer.
|
||||
* @tparam ValueT The type of value (must be an integral type).
|
||||
* @param[in] position Where in the buffer to retrieve the value from.
|
||||
* @param[in] bits The number of bits the value occupies in the buffer.
|
||||
* @returns The n-bit value retrieved from the buffer.
|
||||
* @throws ki::runtime_error Buffer is not large enough to read the specified number of bits at the specified position.
|
||||
*/
|
||||
template <
|
||||
typename IntegerT,
|
||||
typename = std::enable_if<is_integral<IntegerT>::value>
|
||||
>
|
||||
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value)
|
||||
template <typename ValueT>
|
||||
ValueT read(const buffer_pos position,
|
||||
const uint8_t bits = bitsizeof<ValueT>::value) const
|
||||
{
|
||||
return static_cast<IntegerT>(read(bits));
|
||||
// Check for buffer overflow
|
||||
if ((position + bits).as_bytes() > size())
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Buffer is not large enough to read a "
|
||||
<< static_cast<uint16_t>(bits) << "-bit value at specified position. "
|
||||
<< "(byte=" << position.get_byte() << ", bit=" << static_cast<uint16_t>(position.get_bit())
|
||||
<< ", size=" << size() << ")";
|
||||
throw runtime_error(oss.str());
|
||||
}
|
||||
|
||||
return static_cast<ValueT>(read(position, bits));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a value to the buffer that occupies a defined number of bits.
|
||||
* @tparam IntegerT The type of value (must be an integral type).
|
||||
* Writes a value to the buffer.
|
||||
* @tparam ValueT The type of value (must be an integral type).
|
||||
* @param[in] value The value to write.
|
||||
* @param[in] bits The number of bits to use. Defaults to the bitsize of IntegerT.
|
||||
* @param[in] position Where in the buffer to write the value to.
|
||||
* @param[in] bits The number of bits the value should occupy in the buffer. Defaults to the bitsize of ValueT.
|
||||
* @throws ki::runtime_error Buffer is not large enough to write the specified number of bits at the specified position.
|
||||
*/
|
||||
template <
|
||||
typename IntegerT,
|
||||
typename = std::enable_if<is_integral<IntegerT>::value>
|
||||
>
|
||||
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value)
|
||||
template <typename ValueT>
|
||||
void write(const ValueT value, const buffer_pos position,
|
||||
const uint8_t bits = bitsizeof<ValueT>::value)
|
||||
{
|
||||
write(static_cast<uint64_t>(value), bits);
|
||||
// Check for buffer overflow
|
||||
if ((position + bits).as_bytes() > size())
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Buffer is not large enough to write a "
|
||||
<< static_cast<uint16_t>(bits) << "-bit value at specified position. "
|
||||
<< "(byte=" << position.get_byte() << ", bit=" << static_cast<uint16_t>(position.get_bit())
|
||||
<< ", size=" << size() << ")";
|
||||
throw runtime_error(oss.str());
|
||||
}
|
||||
|
||||
write(static_cast<uint64_t>(value), position, bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy memory from an external buffer into the bitstream's buffer from the current position.
|
||||
* @param[in] src The buffer to copy data from.
|
||||
* @param[in] bitsize The number of bits to copy from the src buffer.
|
||||
* Copy memory from this buffer into an external buffer.
|
||||
* @param[out] dst The destination buffer to copy data to.
|
||||
* @param[in] position Where in the buffer to start copying data from.
|
||||
* @param[in] bitsize The number of bits to copy into the destination buffer.
|
||||
*/
|
||||
void write_copy(uint8_t *src, std::size_t bitsize);
|
||||
void read_copy(uint8_t *dst, buffer_pos position, std::size_t bitsize) const;
|
||||
|
||||
/**
|
||||
* Copy memory from the bitstream's buffer into an external buffer.
|
||||
* @param[out] dst The destination buffer to copy data to.
|
||||
* @param[in] bitsize The number of bits to copy into the dst buffer.
|
||||
* Copy memory from an external buffer into this buffer at the specified position.
|
||||
* @param[in] src The source buffer to copy data from.
|
||||
* @param[in] position Where in the buffer to start copying data to.
|
||||
* @param[in] bitsize The number of bits to copy from the source buffer.
|
||||
*/
|
||||
void read_copy(uint8_t *dst, std::size_t bitsize);
|
||||
void write_copy(uint8_t *src, buffer_pos position, std::size_t bitsize);
|
||||
|
||||
protected:
|
||||
virtual uint64_t read(uint8_t bits) = 0;
|
||||
virtual void write(uint64_t value, uint8_t bits) = 0;
|
||||
virtual uint64_t read(buffer_pos position, uint8_t bits) const = 0;
|
||||
virtual void write(uint64_t value, buffer_pos position, uint8_t bits) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class BitBuffer : public BitBufferBase
|
||||
{
|
||||
public:
|
||||
explicit BitBuffer(std::size_t buffer_size = KI_BITBUFFER_DEFAULT_SIZE);
|
||||
~BitBuffer();
|
||||
|
||||
BitBuffer(const BitBuffer &that);
|
||||
BitBuffer &operator=(const BitBuffer &that);
|
||||
|
||||
/**
|
||||
* Create a new BitBuffer from an existing buffer.
|
||||
* @param[in] buffer The buffer to take ownership of.
|
||||
* @param[in] buffer_size The size of the buffer in bytes.
|
||||
*/
|
||||
explicit BitBuffer(uint8_t *buffer, std::size_t buffer_size);
|
||||
|
||||
std::size_t size() const override;
|
||||
uint8_t* data() const override;
|
||||
void resize(std::size_t new_size) override;
|
||||
|
||||
/**
|
||||
* @copydoc BitBufferBase::read<ValueT>(buffer_pos, uint8_t)
|
||||
*/
|
||||
template <typename ValueT>
|
||||
ValueT read(const buffer_pos position,
|
||||
const uint8_t bits = bitsizeof<ValueT>::value) const
|
||||
{
|
||||
return BitBufferBase::read<ValueT>(position, bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc BitBufferBase::write<ValueT>(ValueT, buffer_pos, uint8_t)
|
||||
*/
|
||||
template <typename ValueT>
|
||||
void write(const ValueT value, const buffer_pos position,
|
||||
const uint8_t bits = bitsizeof<ValueT>::value)
|
||||
{
|
||||
return BitBufferBase::write<ValueT>(value, position, bits);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t read(buffer_pos position, uint8_t bits) const override;
|
||||
void write(uint64_t value, buffer_pos position, uint8_t bits) override;
|
||||
|
||||
private:
|
||||
uint8_t *m_buffer;
|
||||
std::size_t m_buffer_size;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class BitBufferSegment : public BitBufferBase
|
||||
{
|
||||
public:
|
||||
BitBufferSegment(BitBufferBase &buffer, buffer_pos from, std::size_t bitsize);
|
||||
|
||||
std::size_t size() const override;
|
||||
void resize(std::size_t new_size) override;
|
||||
uint8_t* data() const override;
|
||||
BitBufferSegment *segment(buffer_pos from, std::size_t bitsize) override;
|
||||
|
||||
/**
|
||||
* @copydoc BitBufferBase::read<ValueT>(buffer_pos, uint8_t)
|
||||
*/
|
||||
template <typename ValueT>
|
||||
ValueT read(const buffer_pos position,
|
||||
const uint8_t bits = bitsizeof<ValueT>::value) const
|
||||
{
|
||||
return BitBufferBase::read<ValueT>(position, bits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc BitBufferBase::write<ValueT>(ValueT, buffer_pos, uint8_t)
|
||||
*/
|
||||
template <typename ValueT>
|
||||
void write(const ValueT value, const buffer_pos position,
|
||||
const uint8_t bits = bitsizeof<ValueT>::value)
|
||||
{
|
||||
return BitBufferBase::write<ValueT>(value, position, bits);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t read(buffer_pos position, uint8_t bits) const override;
|
||||
void write(uint64_t value, buffer_pos position, uint8_t bits) override;
|
||||
|
||||
private:
|
||||
BitBufferBase *m_buffer;
|
||||
buffer_pos m_from;
|
||||
std::size_t m_bitsize;
|
||||
};
|
||||
|
||||
/**
|
||||
* A read/write-able stream of bits.
|
||||
*/
|
||||
class BitStream : public BitStreamBase
|
||||
class BitStream
|
||||
{
|
||||
public:
|
||||
explicit BitStream(std::size_t buffer_size = KI_BITSTREAM_DEFAULT_BUFFER_SIZE);
|
||||
using stream_pos = BitBuffer::buffer_pos;
|
||||
|
||||
explicit BitStream(BitBufferBase &buffer);
|
||||
virtual ~BitStream();
|
||||
|
||||
stream_pos tell() const override;
|
||||
void seek(stream_pos position) override;
|
||||
std::size_t capacity() const override;
|
||||
const uint8_t *data() const override;
|
||||
BitStream(const BitStream &that);
|
||||
BitStream &operator=(const BitStream &that);
|
||||
|
||||
/**
|
||||
* @copydoc BitStreamBase::read<IntegerT>(uint8_t)
|
||||
* @throws ki::runtime_error Not enough data available to read the specified number of bits.
|
||||
* @returns The stream's current position.
|
||||
*/
|
||||
template <
|
||||
typename IntegerT,
|
||||
typename = std::enable_if<is_integral<IntegerT>::value>
|
||||
>
|
||||
IntegerT read(const uint8_t bits = bitsizeof<IntegerT>::value)
|
||||
{
|
||||
return BitStreamBase::read<IntegerT>(bits);
|
||||
}
|
||||
|
||||
template <
|
||||
typename IntegerT,
|
||||
typename = std::enable_if<is_integral<IntegerT>::value>
|
||||
>
|
||||
void write(IntegerT value, const uint8_t bits = bitsizeof<IntegerT>::value)
|
||||
{
|
||||
BitStreamBase::write<IntegerT>(value, bits);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t read(uint8_t bits) override;
|
||||
void write(uint64_t value, uint8_t bits) override;
|
||||
|
||||
private:
|
||||
uint8_t * m_buffer;
|
||||
std::size_t m_buffer_size;
|
||||
stream_pos m_position;
|
||||
|
||||
void expand_buffer();
|
||||
void validate_buffer();
|
||||
};
|
||||
stream_pos tell() const;
|
||||
|
||||
/**
|
||||
* TODO: Documentation
|
||||
* 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.
|
||||
*/
|
||||
class BitStreamSection : public BitStreamBase
|
||||
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 ValueT,
|
||||
typename = std::enable_if<is_integral<ValueT>::value>
|
||||
>
|
||||
ValueT read(const uint8_t bits = bitsizeof<ValueT>::value)
|
||||
{
|
||||
public:
|
||||
explicit BitStreamSection(BitStreamBase &stream, std::size_t size);
|
||||
ValueT value = m_buffer->read<ValueT>(m_position, bits);
|
||||
m_position += bits;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a value to the buffer.
|
||||
* If the buffer is not big enough to write the value, then the buffer is resized.
|
||||
* @tparam ValueT The type of value (must be an integral type).
|
||||
* @param[in] value The value to write.
|
||||
* @param[in] bits The number of bits to use. Defaults to the bitsize of ValueT.
|
||||
*/
|
||||
template <
|
||||
typename ValueT,
|
||||
typename = std::enable_if<is_integral<ValueT>::value>
|
||||
>
|
||||
void write(ValueT value, const uint8_t bits = bitsizeof<ValueT>::value)
|
||||
{
|
||||
expand_buffer(m_position + bits);
|
||||
m_buffer->write<ValueT>(value, m_position, bits);
|
||||
m_position += bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
BitStreamBase &m_stream;
|
||||
BitBufferBase *m_buffer;
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,86 @@ 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();
|
||||
|
@ -25,6 +105,41 @@ namespace pclass
|
|||
!= m_property_hash_lookup.end();
|
||||
}
|
||||
|
||||
PropertyBase &PropertyList::get_property(const int index)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
PropertyBase& PropertyList::get_property(const std::string& name)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
PropertyBase& PropertyList::get_property(const hash_t hash)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
const PropertyBase &PropertyList::get_property(const int index) const
|
||||
{
|
||||
if (index >= 0 && index < m_properties.size())
|
||||
|
@ -60,48 +175,52 @@ namespace pclass
|
|||
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()] = ∝
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.";
|
||||
|
|
|
@ -15,22 +15,23 @@ namespace serialization
|
|||
m_root_object = nullptr;
|
||||
}
|
||||
|
||||
void SerializerBinary::save(const pclass::PropertyClass* object, BitStreamBase &stream)
|
||||
void SerializerBinary::save(const pclass::PropertyClass* object, BitStream &stream)
|
||||
{
|
||||
// Write the serializer flags
|
||||
if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS))
|
||||
stream.write<uint32_t>(static_cast<uint32_t>(m_flags));
|
||||
|
||||
// Remember where we started writing data, so we can compress later
|
||||
// if necessary.
|
||||
const auto start_pos = stream.tell();
|
||||
|
||||
// If the contents of the stream are going to be compressed,
|
||||
// reserve space to put the length
|
||||
// reserve space to put the length and compression toggle.
|
||||
const auto compression_header_pos = stream.tell();
|
||||
if (FLAG_IS_SET(m_flags, flags::COMPRESSED))
|
||||
{
|
||||
stream.write<bool>(false);
|
||||
stream.write<uint32_t>(0);
|
||||
}
|
||||
|
||||
// Write the object to the stream
|
||||
const auto start_pos = stream.tell();
|
||||
m_root_object = object;
|
||||
save_object(object, stream);
|
||||
|
||||
|
@ -39,17 +40,17 @@ namespace serialization
|
|||
{
|
||||
// Remember where the ending of the data is
|
||||
const auto end_pos = stream.tell();
|
||||
const auto size_bits = (end_pos - (start_pos + bitsizeof<uint32_t>::value)).as_bits();
|
||||
const auto size_bytes = (size_bits / 8) + (size_bits % 8 > 0 ? 1 : 0);
|
||||
const auto size_bits = (end_pos - start_pos).as_bits();
|
||||
const auto size_bytes = (end_pos - start_pos).as_bytes();
|
||||
|
||||
// Make a copy of the uncompressed data
|
||||
auto *uncompressed = new uint8_t[size_bytes];
|
||||
auto *uncompressed = new uint8_t[size_bytes]{0};
|
||||
stream.read_copy(uncompressed, size_bits);
|
||||
|
||||
// Setup compression
|
||||
static const std::size_t bufsize = 1024;
|
||||
auto *temp_buffer = new uint8_t[bufsize];
|
||||
std::vector<uint8_t> compressed(size_bytes);
|
||||
auto *temp_buffer = new uint8_t[bufsize]{0};
|
||||
std::vector<uint8_t> compressed;
|
||||
z_stream z;
|
||||
z.zalloc = nullptr;
|
||||
z.zfree = nullptr;
|
||||
|
@ -93,23 +94,26 @@ namespace serialization
|
|||
temp_buffer, temp_buffer + bufsize - z.avail_out);
|
||||
deflateEnd(&z);
|
||||
|
||||
// Move the contents of the compressed buffer if the compressed
|
||||
// buffer is smaller
|
||||
stream.seek(start_pos);
|
||||
stream.write<uint32_t>(size_bytes);
|
||||
if (compressed.size() < size_bytes)
|
||||
stream.write_copy(compressed.data(), compressed.size() * 8);
|
||||
else
|
||||
// Go back to the end of the data
|
||||
stream.seek(end_pos);
|
||||
|
||||
// Cleanup temporary buffers
|
||||
delete[] uncompressed;
|
||||
delete[] temp_buffer;
|
||||
|
||||
// Write the compression header
|
||||
const auto use_compression = compressed.size() < size_bytes;
|
||||
stream.seek(compression_header_pos);
|
||||
stream.write<bool>(use_compression);
|
||||
stream.write<uint32_t>(size_bytes);
|
||||
|
||||
// Write the compressed data
|
||||
if (use_compression)
|
||||
stream.write_copy(compressed.data(), compressed.size() * 8);
|
||||
else
|
||||
// Go back to the end of the original data
|
||||
stream.seek(end_pos);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStreamBase &stream) const
|
||||
void SerializerBinary::presave_object(const pclass::PropertyClass *object, BitStream &stream) const
|
||||
{
|
||||
// If we have an object, write the type hash, otherwise, write NULL (0).
|
||||
if (object)
|
||||
|
@ -118,7 +122,7 @@ namespace serialization
|
|||
stream.write<uint32_t>(NULL);
|
||||
}
|
||||
|
||||
void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStreamBase &stream) const
|
||||
void SerializerBinary::save_object(const pclass::PropertyClass *object, BitStream &stream) const
|
||||
{
|
||||
// Write any object headers
|
||||
presave_object(object, stream);
|
||||
|
@ -154,9 +158,13 @@ namespace serialization
|
|||
stream.write(size_bits);
|
||||
stream.seek(end_pos);
|
||||
}
|
||||
|
||||
// Re-align the stream so that our position lies on a byte
|
||||
// TODO: Look into how/when the serialization re-aligns as this may not be the right place
|
||||
stream.seek(BitStream::stream_pos(stream.tell().as_bytes(), 0));
|
||||
}
|
||||
|
||||
void SerializerBinary::save_property(const pclass::PropertyBase *prop, BitStreamBase &stream) const
|
||||
void SerializerBinary::save_property(const pclass::PropertyBase &prop, BitStream &stream) const
|
||||
{
|
||||
// Remember where we started writing the property data
|
||||
const auto start_pos = stream.tell();
|
||||
|
@ -166,52 +174,52 @@ namespace serialization
|
|||
{
|
||||
// Reserve space to put the size of the property
|
||||
stream.write<uint32_t>(0);
|
||||
stream.write<uint32_t>(prop->get_full_hash());
|
||||
stream.write<uint32_t>(prop.get_full_hash());
|
||||
}
|
||||
|
||||
// Is the property dynamic? (holding more than one value)
|
||||
auto &property_type = prop->get_type();
|
||||
if (prop->is_dynamic())
|
||||
auto &property_type = prop.get_type();
|
||||
if (prop.is_dynamic())
|
||||
{
|
||||
// Cast the property to a DynamicPropertyBase
|
||||
const auto *dynamic_property =
|
||||
dynamic_cast<const pclass::DynamicPropertyBase *>(prop);
|
||||
const auto &dynamic_property =
|
||||
dynamic_cast<const pclass::DynamicPropertyBase &>(prop);
|
||||
|
||||
// Write the number of elements
|
||||
stream.write<uint16_t>(dynamic_property->get_element_count());
|
||||
stream.write<uint32_t>(dynamic_property.get_element_count());
|
||||
|
||||
// Iterate through the elements
|
||||
for (auto i = 0; i < dynamic_property->get_element_count(); i++)
|
||||
for (auto i = 0; i < dynamic_property.get_element_count(); i++)
|
||||
{
|
||||
// Is this a collection of pointers?
|
||||
if (prop->is_pointer())
|
||||
if (prop.is_pointer())
|
||||
{
|
||||
// Is the property a collection of pointers to other objects?
|
||||
if (property_type.get_kind() == pclass::Type::kind::CLASS)
|
||||
// Write the value as a nested object
|
||||
save_object(dynamic_property->get_object(i), stream);
|
||||
save_object(dynamic_property.get_object(i), stream);
|
||||
else
|
||||
// Write the value as normal (let the property deal with dereferencing)
|
||||
dynamic_property->write_value_to(stream, i);
|
||||
dynamic_property.write_value_to(stream, i);
|
||||
}
|
||||
else
|
||||
// If the value isn't a pointer, and it's not dynamic, just write it as a value
|
||||
dynamic_property->write_value_to(stream, i);
|
||||
dynamic_property.write_value_to(stream, i);
|
||||
}
|
||||
}
|
||||
else if (prop->is_pointer())
|
||||
else if (prop.is_pointer())
|
||||
{
|
||||
// Does this property hold a pointer to another object?
|
||||
if (property_type.get_kind() == pclass::Type::kind::CLASS)
|
||||
// Write the value as a nested object
|
||||
save_object(prop->get_object(), stream);
|
||||
save_object(prop.get_object(), stream);
|
||||
else
|
||||
// Write the value as normal (let the property deal with dereferencing)
|
||||
prop->write_value_to(stream);
|
||||
prop.write_value_to(stream);
|
||||
}
|
||||
else
|
||||
// If the value isn't a pointer, and it's not dynamic, just write it as a value
|
||||
prop->write_value_to(stream);
|
||||
prop.write_value_to(stream);
|
||||
|
||||
// Finish writing the property header by writing the length
|
||||
if (m_is_file)
|
||||
|
@ -227,32 +235,233 @@ namespace serialization
|
|||
}
|
||||
}
|
||||
|
||||
void SerializerBinary::load(pclass::PropertyClass*& dest, BitStreamBase &stream)
|
||||
void SerializerBinary::load(pclass::PropertyClass *&dest,
|
||||
BitStream &stream, const std::size_t size)
|
||||
{
|
||||
// Create a new stream that reads a segment of the stream given to us
|
||||
BitBufferBase *buffer = stream.buffer().segment(stream.tell(), size * 8);
|
||||
auto segment_stream = BitStream(*buffer);
|
||||
stream.seek(stream.tell() + size * 8, false);
|
||||
|
||||
// Read the serializer flags
|
||||
if (FLAG_IS_SET(m_flags, flags::WRITE_SERIALIZER_FLAGS))
|
||||
m_flags = static_cast<flags>(stream.read<uint32_t>());
|
||||
m_flags = static_cast<flags>(segment_stream.read<uint32_t>());
|
||||
|
||||
// Decompress the contents of the stream
|
||||
if (FLAG_IS_SET(m_flags, flags::COMPRESSED))
|
||||
{
|
||||
// Read the compression header
|
||||
const auto use_compression = segment_stream.read<bool>();
|
||||
const auto uncompressed_size = segment_stream.read<uint32_t>();
|
||||
|
||||
}
|
||||
}
|
||||
// Work out how much data is available after the compression header
|
||||
const auto start_pos = segment_stream.tell();
|
||||
const auto end_pos = BitStream::stream_pos(buffer->size(), 0);
|
||||
const auto data_available_bytes = (end_pos - start_pos).as_bytes();
|
||||
|
||||
void SerializerBinary::preload_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const
|
||||
// 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;
|
||||
}
|
||||
|
||||
void SerializerBinary::load_object(pclass::PropertyClass*& dest, BitStreamBase &stream) const
|
||||
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;
|
||||
}
|
||||
|
||||
void SerializerBinary::load_property(pclass::PropertyBase* prop, BitStreamBase &stream) const
|
||||
// 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, BitStream &stream) const
|
||||
{
|
||||
const auto type_hash = stream.read<pclass::hash_t>();
|
||||
if (type_hash != 0)
|
||||
{
|
||||
// Instantiate the type
|
||||
const auto &type = m_type_system->get_type(type_hash);
|
||||
dest = type.instantiate();
|
||||
}
|
||||
else
|
||||
// Instantiate to a nullptr
|
||||
dest = nullptr;
|
||||
}
|
||||
|
||||
void SerializerBinary::load_object(pclass::PropertyClass *&dest, BitStream &stream) const
|
||||
{
|
||||
// Read the object header
|
||||
preload_object(dest, stream);
|
||||
|
||||
// Did we get an object or null?
|
||||
if (!dest)
|
||||
return;
|
||||
auto &properties = dest->get_properties();
|
||||
|
||||
if (m_is_file)
|
||||
{
|
||||
// Read the object's size, and create a new BitBufferSegment to
|
||||
// ensure that data is only read from inside this region.
|
||||
const auto object_size =
|
||||
stream.read<uint32_t>() - bitsizeof<uint32_t>::value;
|
||||
auto *object_buffer = stream.buffer().segment(
|
||||
stream.tell(), object_size
|
||||
);
|
||||
stream.seek(stream.tell() + object_size, false);
|
||||
auto object_stream = BitStream(*object_buffer);
|
||||
|
||||
// Instead of loading properties sequentially, the file format specifies
|
||||
// the hash of a property before writing its value, so we just need to
|
||||
// iterate for how ever many properties the object has declared.
|
||||
for (std::size_t i = 0;
|
||||
i < properties.get_property_count(); i++)
|
||||
{
|
||||
// Read the property's size, and create a new BitBufferSegment to
|
||||
// ensure that data is only read from inside this region.
|
||||
const auto property_size =
|
||||
object_stream.read<uint32_t>() - bitsizeof<uint32_t>::value;
|
||||
auto *property_buffer = object_buffer->segment(
|
||||
object_stream.tell(), property_size
|
||||
);
|
||||
auto property_stream = BitStream(*property_buffer);
|
||||
object_stream.seek(object_stream.tell() + property_size, false);
|
||||
|
||||
// Get the property to load based on it's hash, and then load
|
||||
// it's value.
|
||||
const auto property_hash = property_stream.read<uint32_t>();
|
||||
auto &prop = properties.get_property(property_hash);
|
||||
load_property(prop, property_stream);
|
||||
delete property_buffer;
|
||||
}
|
||||
delete object_buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load properties in the order that the object's class declared them
|
||||
for (auto it = properties.begin();
|
||||
it != properties.end(); ++it)
|
||||
load_property(*it, stream);
|
||||
}
|
||||
|
||||
// All properties on this object have been set now, so call the
|
||||
// created handler on the object
|
||||
dest->on_created();
|
||||
|
||||
// Re-align the stream so that our position lies on a byte
|
||||
stream.seek(BitStream::stream_pos(stream.tell().as_bytes(), 0), false);
|
||||
}
|
||||
|
||||
void SerializerBinary::load_property(pclass::PropertyBase &prop, BitStream &stream) const
|
||||
{
|
||||
auto &property_type = prop.get_type();
|
||||
if (prop.is_dynamic())
|
||||
{
|
||||
auto &dynamic_property =
|
||||
dynamic_cast<pclass::DynamicPropertyBase &>(prop);
|
||||
|
||||
// How many elements are there in this dynamic property?
|
||||
const auto element_count = stream.read<uint32_t>();
|
||||
dynamic_property.set_element_count(element_count);
|
||||
|
||||
// Load each of the elements
|
||||
for (uint16_t i = 0; i < element_count; i++)
|
||||
{
|
||||
// Is this a collection of pointers?
|
||||
if (dynamic_property.is_pointer())
|
||||
{
|
||||
// Is the property a collection of pointers to other objects?
|
||||
if (property_type.get_kind() == pclass::Type::kind::CLASS)
|
||||
{
|
||||
// Read the object as a nested object
|
||||
pclass::PropertyClass *object = nullptr;
|
||||
load_object(object, stream);
|
||||
if (object)
|
||||
{
|
||||
// Does the nested object inherit from the property's type?
|
||||
const auto &object_type =
|
||||
dynamic_cast<const pclass::ClassTypeBase &>(
|
||||
object->get_type()
|
||||
);
|
||||
if (!object_type.inherits(dynamic_property.get_type()))
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Type '" << object_type.get_name() << "' does not derive from "
|
||||
<< "property type '" << prop.get_type().get_name() << "'.";
|
||||
throw runtime_error(oss.str());
|
||||
}
|
||||
}
|
||||
dynamic_property.set_object(object, i);
|
||||
}
|
||||
else
|
||||
// Read the value as normal (let the property deal with dereferencing)
|
||||
dynamic_property.read_value_from(stream, i);
|
||||
}
|
||||
else
|
||||
// If the value isn't a pointer, and it's not dynamic, just read it as a value
|
||||
dynamic_property.read_value_from(stream, i);
|
||||
}
|
||||
}
|
||||
else if (prop.is_pointer())
|
||||
{
|
||||
// Does this property hold a pointer to another object?
|
||||
if (property_type.get_kind() == pclass::Type::kind::CLASS)
|
||||
{
|
||||
// Read the object as a nested object
|
||||
pclass::PropertyClass *object = nullptr;
|
||||
load_object(object, stream);
|
||||
if (object)
|
||||
{
|
||||
// Does the nested object inherit from the property's type?
|
||||
const auto &object_type =
|
||||
dynamic_cast<const pclass::ClassTypeBase &>(
|
||||
object->get_type()
|
||||
);
|
||||
if (!object_type.inherits(prop.get_type()))
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Type '" << object_type.get_name() << "' does not derive from "
|
||||
<< "property type '" << prop.get_type().get_name() << "'.";
|
||||
throw runtime_error(oss.str());
|
||||
}
|
||||
}
|
||||
prop.set_object(object);
|
||||
}
|
||||
else
|
||||
// Read the value as normal (let the property deal with dereferencing)
|
||||
prop.read_value_from(stream);
|
||||
}
|
||||
else
|
||||
// If the value isn't a pointer, and it's not dynamic, just read it as a value
|
||||
prop.read_value_from(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,34 +6,39 @@
|
|||
|
||||
namespace ki
|
||||
{
|
||||
BitStream::stream_pos::stream_pos(const intmax_t byte, const int bit)
|
||||
BitBufferBase::buffer_pos::buffer_pos(const uint32_t byte, const int bit)
|
||||
{
|
||||
m_byte = byte;
|
||||
set_bit(bit);
|
||||
}
|
||||
|
||||
BitStream::stream_pos::stream_pos(const stream_pos& cp)
|
||||
BitBufferBase::buffer_pos::buffer_pos(const buffer_pos& cp)
|
||||
{
|
||||
m_byte = cp.m_byte;
|
||||
set_bit(cp.m_bit);
|
||||
}
|
||||
|
||||
intmax_t BitStream::stream_pos::as_bits() const
|
||||
uint32_t BitBufferBase::buffer_pos::as_bytes() const
|
||||
{
|
||||
return m_byte + (m_bit > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
uint32_t BitBufferBase::buffer_pos::as_bits() const
|
||||
{
|
||||
return (m_byte * 8) + m_bit;
|
||||
}
|
||||
|
||||
intmax_t BitStream::stream_pos::get_byte() const
|
||||
uint32_t BitBufferBase::buffer_pos::get_byte() const
|
||||
{
|
||||
return m_byte;
|
||||
}
|
||||
|
||||
uint8_t BitStream::stream_pos::get_bit() const
|
||||
uint8_t BitBufferBase::buffer_pos::get_bit() const
|
||||
{
|
||||
return m_bit;
|
||||
}
|
||||
|
||||
void BitStream::stream_pos::set_bit(int bit)
|
||||
void BitBufferBase::buffer_pos::set_bit(int bit)
|
||||
{
|
||||
if (bit < 0)
|
||||
{
|
||||
|
@ -50,170 +55,179 @@ namespace ki
|
|||
m_bit = bit;
|
||||
}
|
||||
|
||||
BitStream::stream_pos BitStream::stream_pos::operator+(
|
||||
const stream_pos &rhs) const
|
||||
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator+(
|
||||
const buffer_pos &rhs) const
|
||||
{
|
||||
return stream_pos(
|
||||
return buffer_pos(
|
||||
m_byte + rhs.m_byte, m_bit + rhs.m_bit
|
||||
);
|
||||
}
|
||||
|
||||
BitStream::stream_pos BitStream::stream_pos::operator-(
|
||||
const stream_pos &rhs) const
|
||||
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator-(
|
||||
const buffer_pos &rhs) const
|
||||
{
|
||||
return stream_pos(
|
||||
return buffer_pos(
|
||||
m_byte - rhs.m_byte, m_bit - rhs.m_bit
|
||||
);
|
||||
}
|
||||
|
||||
BitStream::stream_pos BitStream::stream_pos::operator+(
|
||||
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator+(
|
||||
const int& rhs) const
|
||||
{
|
||||
return stream_pos(
|
||||
return buffer_pos(
|
||||
m_byte, m_bit + rhs
|
||||
);
|
||||
}
|
||||
|
||||
BitStream::stream_pos BitStream::stream_pos::operator-(
|
||||
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator-(
|
||||
const int& rhs) const
|
||||
{
|
||||
return stream_pos(
|
||||
return buffer_pos(
|
||||
m_byte, m_bit - rhs
|
||||
);
|
||||
}
|
||||
|
||||
BitStream::stream_pos& BitStream::stream_pos::operator+=(
|
||||
const stream_pos rhs)
|
||||
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator+=(
|
||||
const buffer_pos rhs)
|
||||
{
|
||||
m_byte += rhs.m_byte;
|
||||
set_bit(m_bit + rhs.m_bit);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitStream::stream_pos& BitStream::stream_pos::operator-=(
|
||||
const stream_pos rhs)
|
||||
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator-=(
|
||||
const buffer_pos rhs)
|
||||
{
|
||||
m_byte -= rhs.m_byte;
|
||||
set_bit(m_bit - rhs.m_bit);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitStream::stream_pos& BitStream::stream_pos::operator+=(const int bits)
|
||||
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator+=(const int bits)
|
||||
{
|
||||
set_bit(m_bit + bits);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitStream::stream_pos& BitStream::stream_pos::operator-=(const int bits)
|
||||
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator-=(const int bits)
|
||||
{
|
||||
set_bit(m_bit - bits);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitStream::stream_pos& BitStream::stream_pos::operator++()
|
||||
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator++()
|
||||
{
|
||||
set_bit(m_bit + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitStream::stream_pos& BitStream::stream_pos::operator--()
|
||||
BitBufferBase::buffer_pos& BitBufferBase::buffer_pos::operator--()
|
||||
{
|
||||
set_bit(m_bit - 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitStream::stream_pos BitStream::stream_pos::operator++(int increment)
|
||||
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator++(int increment)
|
||||
{
|
||||
auto copy(*this);
|
||||
++(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
BitStream::stream_pos BitStream::stream_pos::operator--(int increment)
|
||||
BitBufferBase::buffer_pos BitBufferBase::buffer_pos::operator--(int increment)
|
||||
{
|
||||
auto copy(*this);
|
||||
--(*this);
|
||||
return copy;
|
||||
}
|
||||
|
||||
void BitStreamBase::write_copy(uint8_t *src, const std::size_t bitsize)
|
||||
BitBufferSegment *BitBufferBase::segment(
|
||||
const buffer_pos from, const std::size_t bitsize)
|
||||
{
|
||||
// Copy all whole bytes
|
||||
const auto bytes = bitsize / 8;
|
||||
auto written_bytes = 0;
|
||||
while (written_bytes < bytes)
|
||||
{
|
||||
write<uint8_t>(src[written_bytes]);
|
||||
written_bytes++;
|
||||
return new BitBufferSegment(*this, from, bitsize);
|
||||
}
|
||||
|
||||
// Copy left over bits
|
||||
const auto bits = bitsize % 8;
|
||||
if (bits > 0)
|
||||
write<uint8_t>(src[bytes + 1], bits);
|
||||
}
|
||||
|
||||
void BitStreamBase::read_copy(uint8_t *dst, const std::size_t bitsize)
|
||||
void BitBufferBase::write_copy(uint8_t *src,
|
||||
buffer_pos position, const std::size_t bitsize)
|
||||
{
|
||||
// Copy all whole bytes
|
||||
const auto bytes = bitsize / 8;
|
||||
auto read_bytes = 0;
|
||||
while (read_bytes < bytes)
|
||||
auto bits_left = bitsize;
|
||||
while (bits_left > 0)
|
||||
{
|
||||
dst[read_bytes] = read<uint8_t>();
|
||||
read_bytes++;
|
||||
const auto bits = bits_left >= 8 ? 8 : bits_left;
|
||||
write<uint8_t>(*(src++), position, bits);
|
||||
bits_left -= bits;
|
||||
position += bits;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy left over bits
|
||||
const auto bits = bitsize % 8;
|
||||
if (bits > 0)
|
||||
dst[bytes + 1] = read<uint8_t>(bits);
|
||||
void BitBufferBase::read_copy(uint8_t *dst,
|
||||
buffer_pos position, const std::size_t bitsize) const
|
||||
{
|
||||
auto bits_left = bitsize;
|
||||
while (bits_left > 0)
|
||||
{
|
||||
const auto bits = bits_left >= 8 ? 8 : bits_left;
|
||||
*(dst++) = read<uint8_t>(position, bits);
|
||||
bits_left -= bits;
|
||||
position += bits;
|
||||
}
|
||||
}
|
||||
|
||||
BitStream::BitStream(const size_t buffer_size)
|
||||
BitBuffer::BitBuffer(const size_t buffer_size)
|
||||
{
|
||||
m_buffer = new uint8_t[buffer_size]{0};
|
||||
m_buffer_size = buffer_size;
|
||||
m_position = stream_pos(0, 0);
|
||||
}
|
||||
|
||||
BitStream::~BitStream()
|
||||
BitBuffer::~BitBuffer()
|
||||
{
|
||||
delete[] m_buffer;
|
||||
m_buffer = nullptr;
|
||||
m_buffer_size = 0;
|
||||
}
|
||||
|
||||
BitStream::stream_pos BitStream::tell() const
|
||||
BitBuffer::BitBuffer(const BitBuffer& that)
|
||||
{
|
||||
return m_position;
|
||||
m_buffer_size = that.m_buffer_size;
|
||||
m_buffer = new uint8_t[m_buffer_size]{0};
|
||||
std::memcpy(m_buffer, that.m_buffer, m_buffer_size);
|
||||
}
|
||||
|
||||
void BitStream::seek(const stream_pos position)
|
||||
BitBuffer &BitBuffer::operator=(const BitBuffer &that)
|
||||
{
|
||||
m_position = position;
|
||||
validate_buffer();
|
||||
m_buffer_size = that.m_buffer_size;
|
||||
m_buffer = new uint8_t[m_buffer_size]{0};
|
||||
std::memcpy(m_buffer, that.m_buffer, m_buffer_size);
|
||||
return (*this);
|
||||
}
|
||||
|
||||
std::size_t BitStream::capacity() const
|
||||
BitBuffer::BitBuffer(uint8_t *buffer, const std::size_t buffer_size)
|
||||
{
|
||||
m_buffer = buffer;
|
||||
m_buffer_size = buffer_size;
|
||||
}
|
||||
|
||||
std::size_t BitBuffer::size() const
|
||||
{
|
||||
return m_buffer_size;
|
||||
}
|
||||
|
||||
const uint8_t *BitStream::data() const
|
||||
void BitBuffer::resize(const std::size_t new_size)
|
||||
{
|
||||
auto *new_buffer = new uint8_t[new_size]{0};
|
||||
std::memcpy(new_buffer, m_buffer, new_size >= m_buffer_size ? m_buffer_size : new_size);
|
||||
delete[] m_buffer;
|
||||
m_buffer = new_buffer;
|
||||
m_buffer_size = new_size;
|
||||
}
|
||||
|
||||
uint8_t *BitBuffer::data() const
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
uint64_t BitStream::read(const uint8_t bits)
|
||||
uint64_t BitBuffer::read(buffer_pos position, const uint8_t bits) const
|
||||
{
|
||||
// Do we have these bits available to read?
|
||||
if ((m_position + bits).as_bits() > m_buffer_size * 8)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Not enough data in buffer to read "
|
||||
<< static_cast<uint16_t>(bits) << "bits.";
|
||||
throw runtime_error(oss.str());
|
||||
}
|
||||
|
||||
// Iterate until we've read all of the bits
|
||||
uint64_t value = 0;
|
||||
auto unread_bits = bits;
|
||||
|
@ -221,14 +235,14 @@ namespace ki
|
|||
{
|
||||
// Calculate how many bits to read from the current byte based on how many bits
|
||||
// are left and how many bits we still need to read
|
||||
const uint8_t bits_available = (8 - m_position.get_bit());
|
||||
const uint8_t bits_available = (8 - position.get_bit());
|
||||
const auto bit_count = unread_bits < bits_available ? unread_bits : bits_available;
|
||||
|
||||
// Find the bit-mask based on how many bits are being read
|
||||
const uint8_t bit_mask = (1 << bit_count) - 1 << m_position.get_bit();
|
||||
const uint8_t bit_mask = (1 << bit_count) - 1 << position.get_bit();
|
||||
|
||||
// Read the bits from the current byte and position them on the least-signficant bit
|
||||
const uint8_t bits_value = (m_buffer[m_position.get_byte()] & bit_mask) >> m_position.get_bit();
|
||||
const uint8_t bits_value = (m_buffer[position.get_byte()] & bit_mask) >> position.get_bit();
|
||||
|
||||
// Position the value of the bits we just read based on how many bits of the value
|
||||
// we've already read
|
||||
|
@ -239,13 +253,14 @@ namespace ki
|
|||
unread_bits -= bit_count;
|
||||
|
||||
// Move forward the number of bits we just read
|
||||
seek(tell() + bit_count);
|
||||
position += bit_count;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void BitStream::write(const uint64_t value, const uint8_t bits)
|
||||
void BitBuffer::write(const uint64_t value,
|
||||
buffer_pos position, const uint8_t bits)
|
||||
{
|
||||
// Iterate until we've written all of the bits
|
||||
auto unwritten_bits = bits;
|
||||
|
@ -253,7 +268,7 @@ namespace ki
|
|||
{
|
||||
// Calculate how many bits to write based on how many bits are left in the current byte
|
||||
// and how many bits from the value we still need to write
|
||||
const uint8_t bits_available = (8 - m_position.get_bit());
|
||||
const uint8_t bits_available = (8 - position.get_bit());
|
||||
const auto bit_count = unwritten_bits < bits_available ? unwritten_bits : bits_available;
|
||||
|
||||
// Find the bit-mask based on how many bits are being written, and how many bits we've
|
||||
|
@ -263,47 +278,149 @@ namespace ki
|
|||
|
||||
// Get the bits from the value and position them at the current bit position
|
||||
uint8_t value_byte = ((value & bit_mask) >> written_bits) & 0xFF;
|
||||
value_byte <<= m_position.get_bit();
|
||||
value_byte <<= position.get_bit();
|
||||
|
||||
// Write the bits into the byte we're currently at
|
||||
m_buffer[m_position.get_byte()] |= value_byte;
|
||||
m_buffer[position.get_byte()] |= value_byte;
|
||||
unwritten_bits -= bit_count;
|
||||
|
||||
// Move forward the number of bits we just wrote
|
||||
seek(tell() + bit_count);
|
||||
position += bit_count;
|
||||
}
|
||||
}
|
||||
|
||||
void BitStream::expand_buffer()
|
||||
BitBufferSegment::BitBufferSegment(BitBufferBase& buffer,
|
||||
const buffer_pos from, const std::size_t bitsize)
|
||||
{
|
||||
m_buffer = &buffer;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
std::size_t BitBufferSegment::size() const
|
||||
{
|
||||
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 (position.as_bytes() >= m_buffer->size())
|
||||
{
|
||||
// Work out a new buffer size
|
||||
const auto minimum_bits = static_cast<uint64_t>(
|
||||
std::log2(m_position.get_byte())
|
||||
std::log2(position.get_byte())
|
||||
) + 1;
|
||||
auto new_size = (2 << minimum_bits) + 2;
|
||||
if (new_size < m_buffer_size)
|
||||
if (new_size < m_buffer->size())
|
||||
new_size = std::numeric_limits<size_t>::max();
|
||||
|
||||
// Has the buffer reached maximum size?
|
||||
if (new_size == m_buffer_size)
|
||||
if (new_size == m_buffer->size())
|
||||
throw runtime_error("Buffer cannot be expanded as it has reached maximum size.");
|
||||
|
||||
// Allocate a new buffer, copy everything over, and then delete the old buffer
|
||||
auto *new_buffer = new uint8_t[new_size] { 0 };
|
||||
std::memcpy(new_buffer, m_buffer, m_buffer_size);
|
||||
delete[] m_buffer;
|
||||
m_buffer = new_buffer;
|
||||
m_buffer_size = new_size;
|
||||
}
|
||||
|
||||
void BitStream::validate_buffer()
|
||||
{
|
||||
// Make sure we haven't underflowed
|
||||
if (m_position.get_byte() < 0)
|
||||
throw runtime_error("Position of buffer is less than 0!");
|
||||
|
||||
// Expand the buffer if we've overflowed
|
||||
if (m_position.get_byte() >= m_buffer_size)
|
||||
expand_buffer();
|
||||
// Resize the buffer
|
||||
m_buffer->resize(new_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
#define KI_TEST_BITSTREAM_U32 0x0A090807
|
||||
#define KI_TEST_BITSTREAM_U64 0x1211100F0E0D0C0BL
|
||||
|
||||
#define KI_TEST_WRITE_BUI(n) bit_stream->write<bui<n>>(KI_TEST_BITSTREAM_BUI##n)
|
||||
#define KI_TEST_READ_BUI(n) bit_stream->read<bui<n>>() == KI_TEST_BITSTREAM_BUI##n
|
||||
#define KI_TEST_WRITE_BUI(n) bit_stream.write<bui<n>>(KI_TEST_BITSTREAM_BUI##n)
|
||||
#define KI_TEST_READ_BUI(n) bit_stream.read<bui<n>>() == KI_TEST_BITSTREAM_BUI##n
|
||||
|
||||
using namespace ki;
|
||||
|
||||
|
@ -31,55 +31,50 @@ TEST_CASE("BitStream::stream_pos Functionality", "[bit-stream]")
|
|||
SECTION("Increment single bit")
|
||||
{
|
||||
++position;
|
||||
if (position.get_byte() != 1 || position.get_bit() != 5)
|
||||
FAIL();
|
||||
SUCCEED();
|
||||
REQUIRE(position.get_byte() == 1);
|
||||
REQUIRE(position.get_bit() == 5);
|
||||
}
|
||||
|
||||
SECTION("Decrement single bit")
|
||||
{
|
||||
--position;
|
||||
if (position.get_byte() != 1 || position.get_bit() != 3)
|
||||
FAIL();
|
||||
SUCCEED();
|
||||
REQUIRE(position.get_byte() == 1);
|
||||
REQUIRE(position.get_bit() == 3);
|
||||
}
|
||||
|
||||
SECTION("Increment bits and move to next byte")
|
||||
{
|
||||
position += 4;
|
||||
if (position.get_byte() != 2 || position.get_bit() != 0)
|
||||
FAIL();
|
||||
SUCCEED();
|
||||
REQUIRE(position.get_byte() == 2);
|
||||
REQUIRE(position.get_bit() == 0);
|
||||
}
|
||||
|
||||
SECTION("Decrement bits and move to previous byte")
|
||||
{
|
||||
position -= 5;
|
||||
if (position.get_byte() != 0 || position.get_bit() != 7)
|
||||
FAIL();
|
||||
SUCCEED();
|
||||
REQUIRE(position.get_byte() == 0);
|
||||
REQUIRE(position.get_bit() == 7);
|
||||
}
|
||||
|
||||
SECTION("Increment byte")
|
||||
{
|
||||
position += BitStream::stream_pos(1, 0);
|
||||
if (position.get_byte() != 2 || position.get_bit() != 4)
|
||||
FAIL();
|
||||
SUCCEED();
|
||||
REQUIRE(position.get_byte() == 2);
|
||||
REQUIRE(position.get_bit() == 4);
|
||||
}
|
||||
|
||||
SECTION("Decrement byte")
|
||||
{
|
||||
position -= BitStream::stream_pos(1, 0);
|
||||
if (position.get_byte() != 0 || position.get_bit() != 4)
|
||||
FAIL();
|
||||
SUCCEED();
|
||||
REQUIRE(position.get_byte() == 0);
|
||||
REQUIRE(position.get_bit() == 4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BitStream Functionality", "[bit-stream]")
|
||||
{
|
||||
auto *bit_stream = new BitStream();
|
||||
BitBuffer bit_buffer;
|
||||
auto bit_stream = BitStream(bit_buffer);
|
||||
|
||||
SECTION("Writing values with a size less than 8 bits")
|
||||
{
|
||||
|
@ -94,65 +89,60 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
|
|||
KI_TEST_WRITE_BUI(4);
|
||||
|
||||
// Make sure tell is reporting the right position
|
||||
auto position = bit_stream->tell();
|
||||
if (position.get_byte() != 4 || position.get_bit() != 0)
|
||||
FAIL();
|
||||
auto position = bit_stream.tell();
|
||||
REQUIRE(position.get_byte() == 4);
|
||||
REQUIRE(position.get_bit() == 0);
|
||||
const auto size = position.get_byte();
|
||||
|
||||
// Validate what we've got here with a hand-written sample
|
||||
std::ifstream sample("samples/bitstream1.bin", std::ios::binary);
|
||||
if (!sample.is_open())
|
||||
FAIL();
|
||||
REQUIRE(sample.is_open());
|
||||
|
||||
// Load the sample data and compare
|
||||
auto *sample_data = new char[size + 1]{ 0 };
|
||||
auto *sample_data = new char[size];
|
||||
auto *stream_data = bit_buffer.data();
|
||||
sample.read(sample_data, size);
|
||||
if (strcmp(sample_data, (char *)bit_stream->data()) != 0)
|
||||
FAIL();
|
||||
REQUIRE(memcmp(sample_data, stream_data, size) == 0);
|
||||
|
||||
// Free resources
|
||||
delete[] sample_data;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
SECTION("Writing values with a size greater than 8 bits")
|
||||
{
|
||||
// Write some values
|
||||
bit_stream->write<bool>(KI_TEST_BITSTREAM_BOOL);
|
||||
bit_stream->write<uint8_t>(KI_TEST_BITSTREAM_U8);
|
||||
bit_stream->write<uint16_t>(KI_TEST_BITSTREAM_U16);
|
||||
bit_stream->write<bui<24>>(KI_TEST_BITSTREAM_U24);
|
||||
bit_stream->write<uint32_t>(KI_TEST_BITSTREAM_U32);
|
||||
bit_stream->write<uint64_t>(KI_TEST_BITSTREAM_U64);
|
||||
bit_stream.write<bool>(KI_TEST_BITSTREAM_BOOL);
|
||||
bit_stream.write<uint8_t>(KI_TEST_BITSTREAM_U8);
|
||||
bit_stream.write<uint16_t>(KI_TEST_BITSTREAM_U16);
|
||||
bit_stream.write<bui<24>>(KI_TEST_BITSTREAM_U24);
|
||||
bit_stream.write<uint32_t>(KI_TEST_BITSTREAM_U32);
|
||||
bit_stream.write<uint64_t>(KI_TEST_BITSTREAM_U64);
|
||||
|
||||
// Make sure tell is reporting the right position
|
||||
auto position = bit_stream->tell();
|
||||
if (position.get_byte() != 19 || position.get_bit() != 0)
|
||||
FAIL();
|
||||
auto position = bit_stream.tell();
|
||||
REQUIRE(position.get_byte() == 19);
|
||||
REQUIRE(position.get_bit() == 0);
|
||||
const auto size = position.get_byte();
|
||||
|
||||
// Validate what we've got here with a hand-written sample
|
||||
std::ifstream sample("samples/bitstream2.bin", std::ios::binary);
|
||||
if (!sample.is_open())
|
||||
FAIL();
|
||||
REQUIRE(sample.is_open());
|
||||
|
||||
// Load the sample data and compare
|
||||
auto *sample_data = new char[size + 1]{ 0 };
|
||||
auto *stream_data = bit_buffer.data();
|
||||
auto *sample_data = new char[size];
|
||||
sample.read(sample_data, size);
|
||||
if (strcmp(sample_data, (char *)bit_stream->data()) != 0)
|
||||
FAIL();
|
||||
REQUIRE(memcmp(sample_data, stream_data, size) == 0);
|
||||
|
||||
// Free resources
|
||||
delete[] sample_data;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
SECTION("Reading values with a size lower than 8 bits")
|
||||
{
|
||||
// Open a previously hand-written sample
|
||||
std::ifstream sample("samples/bitstream1.bin", std::ios::binary);
|
||||
if (!sample.is_open())
|
||||
FAIL();
|
||||
REQUIRE(sample.is_open());
|
||||
|
||||
// Load the sample data into the bit stream
|
||||
const auto begin = sample.tellg();
|
||||
|
@ -160,35 +150,25 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
|
|||
const auto end = sample.tellg();
|
||||
const size_t size = end - begin;
|
||||
sample.seekg(std::ios::beg);
|
||||
sample.read((char *)bit_stream->data(), size);
|
||||
auto *stream_data = reinterpret_cast<char *>(bit_buffer.data());
|
||||
sample.read(stream_data, size);
|
||||
|
||||
// Read the values and check they are what we are expecting
|
||||
if (!KI_TEST_READ_BUI(1))
|
||||
FAIL();
|
||||
if (!KI_TEST_READ_BUI(2))
|
||||
FAIL();
|
||||
if (!KI_TEST_READ_BUI(3))
|
||||
FAIL();
|
||||
if (!KI_TEST_READ_BUI(4))
|
||||
FAIL();
|
||||
if (!KI_TEST_READ_BUI(5))
|
||||
FAIL();
|
||||
if (!KI_TEST_READ_BUI(6))
|
||||
FAIL();
|
||||
if (!KI_TEST_READ_BUI(7))
|
||||
FAIL();
|
||||
if (!KI_TEST_READ_BUI(4))
|
||||
FAIL();
|
||||
|
||||
SUCCEED();
|
||||
REQUIRE(KI_TEST_READ_BUI(1));
|
||||
REQUIRE(KI_TEST_READ_BUI(2));
|
||||
REQUIRE(KI_TEST_READ_BUI(3));
|
||||
REQUIRE(KI_TEST_READ_BUI(4));
|
||||
REQUIRE(KI_TEST_READ_BUI(5));
|
||||
REQUIRE(KI_TEST_READ_BUI(6));
|
||||
REQUIRE(KI_TEST_READ_BUI(7));
|
||||
REQUIRE(KI_TEST_READ_BUI(4));
|
||||
}
|
||||
|
||||
SECTION("Reading values with a size greater than 8 bits")
|
||||
{
|
||||
// Open a previously hand-written sample
|
||||
std::ifstream sample("samples/bitstream2.bin", std::ios::binary);
|
||||
if (!sample.is_open())
|
||||
FAIL();
|
||||
REQUIRE(sample.is_open());
|
||||
|
||||
// Load the sample data into the bit stream
|
||||
const auto begin = sample.tellg();
|
||||
|
@ -196,63 +176,39 @@ TEST_CASE("BitStream Functionality", "[bit-stream]")
|
|||
const auto end = sample.tellg();
|
||||
const size_t size = end - begin;
|
||||
sample.seekg(std::ios::beg);
|
||||
sample.read((char *)bit_stream->data(), size);
|
||||
auto *stream_data = reinterpret_cast<char *>(bit_buffer.data());
|
||||
sample.read(stream_data, size);
|
||||
|
||||
// Read the values and check they are what we are expecting
|
||||
if (bit_stream->read<bool>() != KI_TEST_BITSTREAM_BOOL)
|
||||
FAIL();
|
||||
if (bit_stream->read<uint8_t>() != KI_TEST_BITSTREAM_U8)
|
||||
FAIL();
|
||||
if (bit_stream->read<uint16_t>() != KI_TEST_BITSTREAM_U16)
|
||||
FAIL();
|
||||
if (bit_stream->read<bui<24>>() != KI_TEST_BITSTREAM_U24)
|
||||
FAIL();
|
||||
if (bit_stream->read<uint32_t>() != KI_TEST_BITSTREAM_U32)
|
||||
FAIL();
|
||||
if (bit_stream->read<uint64_t>() != KI_TEST_BITSTREAM_U64)
|
||||
FAIL();
|
||||
|
||||
SUCCEED();
|
||||
REQUIRE(bit_stream.read<bool>() == KI_TEST_BITSTREAM_BOOL);
|
||||
REQUIRE(bit_stream.read<uint8_t>() == KI_TEST_BITSTREAM_U8);
|
||||
REQUIRE(bit_stream.read<uint16_t>() == KI_TEST_BITSTREAM_U16);
|
||||
REQUIRE(bit_stream.read<bui<24>>() == KI_TEST_BITSTREAM_U24);
|
||||
REQUIRE(bit_stream.read<uint32_t>() == KI_TEST_BITSTREAM_U32);
|
||||
REQUIRE(bit_stream.read<uint64_t>() == KI_TEST_BITSTREAM_U64);
|
||||
}
|
||||
|
||||
SECTION("Buffer underflow")
|
||||
SECTION("Overwriting values")
|
||||
{
|
||||
try
|
||||
{
|
||||
// Attempt to set the position less than 0
|
||||
bit_stream->seek(BitStream::stream_pos(-1, 0));
|
||||
FAIL();
|
||||
bit_stream.write<bui<2>>(0b11);
|
||||
bit_stream.seek(BitStream::stream_pos(0, 0));
|
||||
}
|
||||
catch (std::runtime_error &e)
|
||||
{
|
||||
// An exception was thrown, which is intended behaviour
|
||||
SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
// Free resources
|
||||
delete bit_stream;
|
||||
}
|
||||
|
||||
TEST_CASE("BitStream buffer expansion", "[bit-stream]")
|
||||
{
|
||||
// Create a very small buffer that we know is going to expand
|
||||
auto *bit_stream = new BitStream(1);
|
||||
auto bit_buffer = BitBuffer(1);
|
||||
auto bit_stream = BitStream(bit_buffer);
|
||||
|
||||
// Write a byte, and then check if the capacity grows
|
||||
bit_stream->write<uint8_t>(0x55, 8);
|
||||
if (bit_stream->capacity() != 6)
|
||||
FAIL();
|
||||
bit_stream.write<uint8_t>(0x55, 8);
|
||||
REQUIRE(bit_stream.capacity() == 6);
|
||||
|
||||
// Go back to the first byte, and make sure what we wrote stuck
|
||||
// around after the expansion
|
||||
bit_stream->seek(BitStream::stream_pos(0, 0));
|
||||
if (bit_stream->read<uint8_t>(8) != 0x55)
|
||||
FAIL();
|
||||
|
||||
// Free resources
|
||||
delete bit_stream;
|
||||
SUCCEED();
|
||||
bit_stream.seek(BitStream::stream_pos(0, 0));
|
||||
REQUIRE(bit_stream.read<uint8_t>(8) == 0x55);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue