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