diff --git a/include/ki/pclass/HashCalculator.h b/include/ki/pclass/HashCalculator.h index ac3b157..9b07266 100644 --- a/include/ki/pclass/HashCalculator.h +++ b/include/ki/pclass/HashCalculator.h @@ -25,11 +25,10 @@ namespace ki virtual hash_t calculate_type_hash(const std::string &name) const = 0; /** - * Calculate a property hash from the property's name and type. + * Calculate a property hash from the property's name. * @param name The name of the property. - * @param type_hash The hash of the property's type. */ - virtual hash_t calculate_property_hash(const std::string& name, const hash_t type_hash) const = 0; + virtual hash_t calculate_property_hash(const std::string& name) const = 0; }; /** @@ -40,7 +39,7 @@ namespace ki { public: hash_t calculate_type_hash(const std::string& name) const override; - hash_t calculate_property_hash(const std::string& name, const hash_t type_hash) const override; + hash_t calculate_property_hash(const std::string& name) const override; }; } } diff --git a/include/ki/pclass/Property.h b/include/ki/pclass/Property.h new file mode 100644 index 0000000..dc6d123 --- /dev/null +++ b/include/ki/pclass/Property.h @@ -0,0 +1,65 @@ +#pragma once +#include "ki/pclass/types/Type.h" + +namespace ki +{ +namespace pclass +{ + class PropertyClass; + + /** + * TODO: Documentation + */ + class PropertyBase + { + public: + PropertyBase(PropertyClass &object, + const std::string &name, const Type &type); + virtual ~PropertyBase() { } + + std::string get_name() const; + hash_t get_name_hash() const; + hash_t get_full_hash() const; + const Type &get_type() const; + + virtual bool is_pointer() const; + virtual bool is_dynamic() const; + + virtual Value get_value() const = 0; + virtual const PropertyClass *get_object() const = 0; + + virtual void write_value_to(BitStream &stream) const = 0; + virtual void read_value_from(BitStream &stream) = 0; + + private: + std::string m_name; + hash_t m_name_hash; + hash_t m_full_hash; + const Type *m_type; + }; + + /** + * TODO: Documentation + */ + class DynamicPropertyBase : public PropertyBase + { + public: + DynamicPropertyBase(PropertyClass &object, + const std::string &name, const Type &type); + virtual ~DynamicPropertyBase() {} + + bool is_dynamic() const override; + virtual std::size_t get_element_count() const = 0; + + Value get_value() const final override; + const PropertyClass *get_object() const final override; + void write_value_to(BitStream &stream) const final override; + void read_value_from(BitStream &stream) final override; + + virtual Value get_value(int index) const = 0; + virtual const PropertyClass *get_object(int index) const = 0; + virtual void write_value_to(BitStream &stream, int index) const = 0; + virtual void read_value_from(BitStream &stream, int index) = 0; + }; +} +} diff --git a/include/ki/pclass/PropertyClass.h b/include/ki/pclass/PropertyClass.h index 32cfd1b..e0ce38c 100644 --- a/include/ki/pclass/PropertyClass.h +++ b/include/ki/pclass/PropertyClass.h @@ -1,24 +1,60 @@ #pragma once +#include "ki/pclass/types/Type.h" +#include "ki/pclass/PropertyList.h" + +#define _KI_TYPE ki::pclass::Type +#define _KI_TYPE_SYSTEM ki::pclass::TypeSystem +#define _KI_PCLASS ki::pclass::PropertyClass + +#define _KI_PCLASS_CONSTRUCTOR(derived) \ +explicit derived(const _KI_TYPE &type, const _KI_TYPE_SYSTEM &type_system) + +#define _KI_PCLASS_CONSTRUCT_BASE(base) \ +: base(type, type_system) + +#define DERIVED_PCLASS(derived, base) class derived : public base +#define PCLASS(n) DERIVED_PCLASS(n, _KI_PCLASS) + +#define PCLASS_CONSTRUCTOR(derived) \ +_KI_PCLASS_CONSTRUCTOR(derived) \ + _KI_PCLASS_CONSTRUCT_BASE(_KI_PCLASS) + +#define DERIVED_PCLASS_CONSTRUCTOR(derived, base) \ +_KI_PCLASS_CONSTRUCTOR(derived) \ + _KI_PCLASS_CONSTRUCT_BASE(base) + +#define TYPE(n) type_system.get_type(n) + +#define INIT_PROPERTY(identifier, type) \ + , identifier(*this, #identifier, TYPE(type)) + +#define INIT_PROPERTY_VALUE(identifier, type, value) \ + , identifier(*this, #identifier, TYPE(type), value) namespace ki { namespace pclass { - class Type; + template + class ClassType; /** * TODO: Documentation */ class PropertyClass { - explicit PropertyClass(const Type &type) : m_type(type) {} - virtual ~PropertyClass() {}; + public: + explicit PropertyClass(const Type &type, const TypeSystem &type_system); + virtual ~PropertyClass(); - const Type &get_type(); + const Type &get_type() const; + + PropertyList &get_properties(); + const PropertyList &get_properties() const; - virtual void on_created(); private: - const Type &m_type; + const Type *m_type; + PropertyList *m_properties; }; } } diff --git a/include/ki/pclass/PropertyList.h b/include/ki/pclass/PropertyList.h new file mode 100644 index 0000000..95b518e --- /dev/null +++ b/include/ki/pclass/PropertyList.h @@ -0,0 +1,43 @@ +#pragma once +#include +#include +#include "ki/pclass/HashCalculator.h" + +namespace ki +{ +namespace pclass +{ + class PropertyBase; + + /** + * TODO: Documentation + */ + class PropertyList + { + friend PropertyBase; + + public: + using const_iterator = std::vector::const_iterator; + + std::size_t get_property_count() const; + + bool has_property(const std::string &name) const; + bool has_property(hash_t hash) const; + + const PropertyBase &get_property(int index) const; + const PropertyBase &get_property(const std::string &name) const; + const PropertyBase &get_property(hash_t hash) const; + + const_iterator begin() const; + const_iterator end() const; + + protected: + void add_property(PropertyBase *prop); + + private: + std::vector m_properties; + std::map m_property_name_lookup; + std::map m_property_hash_lookup; + }; +} +} diff --git a/include/ki/pclass/StaticProperty.h b/include/ki/pclass/StaticProperty.h new file mode 100644 index 0000000..09926da --- /dev/null +++ b/include/ki/pclass/StaticProperty.h @@ -0,0 +1,307 @@ +#pragma once +#include "ki/pclass/Property.h" +#include "ki/util/exception.h" + +namespace ki +{ +namespace pclass +{ + // Forward declare StaticProperty for our helpers + template + class StaticProperty; + + /** + * A helper utility that provides the right implementation of construct() + * and get_object() based on characteristics of type: ValueT. + */ + template < + typename ValueT, + typename IsPointerEnable = void, + typename IsBaseEnable = void + > + struct value_object_helper + { + static ValueT construct(const Type &type) + { + // In cases where ValueT is not a pointer, and does not derive from PropertyClass, + // just call the default constructor. + return ValueT(); + } + + static const PropertyClass *get_object(const StaticProperty &prop) + { + // 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." + ); + } + }; + + /** + * Specialization for when ValueT is: + * - A pointer; but + * - does not derive from PropertyClass + * + * This should construct to a nullptr, and throw an exception + * when get_object()is called. + */ + template + struct value_object_helper< + ValueT, + typename std::enable_if< + std::is_pointer::value + >::type, + typename std::enable_if< + !std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type + > + { + static ValueT construct(const Type &type) + { + // The default value of pointers is null + return nullptr; + } + + static const PropertyClass *get_object(const StaticProperty &prop) + { + // 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." + ); + } + }; + + /** + * Specialization for when ValueT is: + * - A pointer; and + * - does derive from PropertyClass + * + * This should construct to a nullptr, and return a pointer to + * a ValueT instance (as a PropertyClass *) when get_object() is called. + */ + template + struct value_object_helper< + ValueT, + typename std::enable_if< + std::is_pointer::value + >::type, + typename std::enable_if< + std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type + > + { + static ValueT construct(const Type &type) + { + // The default value of pointers is null + return nullptr; + } + + static const PropertyClass *get_object(const StaticProperty &prop) + { + // ValueT does derive from PropertyClass, and we have a pointer to an instance + // of ValueT, so we can cast down to a PropertyClass pointer. + return dynamic_cast(prop.m_value); + } + }; + + /** + * Specialization for when ValueT is: + * - Not a pointer; and + * - does derive from PropertyClass + * + * This should construct an instance of ValueT by passing the property + * type and the type's type system, and return a pointer to a ValueT + * instance (as a PropertyClass *) when get_object() is called. + */ + template + struct value_object_helper< + ValueT, + typename std::enable_if< + !std::is_pointer::value + >::type, + typename std::enable_if< + std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type + > + { + static ValueT construct(const Type &type) + { + // Derivitives of PropertyClass cannot have a default constructor since + // they require their Type and TypeSystem, so we need to pass these + // along from what the StaticProperty constructor has been given. + return ValueT(type, type.get_type_system()); + } + + static const PropertyClass *get_object(const StaticProperty &prop) + { + // ValueT does derive from PropertyClass, and we have an instance of ValueT, + // so we can cast down to a PropertyClass pointer. + return dynamic_cast(&prop.m_value); + } + }; + + /** + * A helper utility that provides the right implementation of write() and read() + * based on whether ValueT is a pointer. + */ + template < + typename ValueT, + typename IsPointerEnable = void + > + struct value_rw_helper + { + static void write(const StaticProperty &prop, BitStream &stream) + { + prop.get_type().write_to(stream, prop.m_value); + } + + static void read(StaticProperty &prop, BitStream &stream) + { + prop.get_type().read_from(stream, Value(prop.m_value)); + } + }; + + /** + * Specialization for when ValueT is a pointer. + * + * Dereference the pointer before creating Value instances. + * This is so that the Value stores a pointer to a ValueT instance, + * rather than storing a pointer to a pointer. + */ + template + struct value_rw_helper< + ValueT, + typename std::enable_if< + std::is_pointer::value + >::type + > + { + static void write(const StaticProperty &prop, BitStream &stream) + { + prop.get_type().write_to(stream, *prop.m_value); + } + + static void read(StaticProperty &prop, BitStream &stream) + { + prop.get_type().read_from(stream, Value(*prop.m_value)); + } + }; + + /** + * A helper utility that combines functions provided by value_object_helper + * and value_rw_helper into a single type. + */ + template + struct value_helper + { + static ValueT construct(const Type &type) + { + return value_object_helper::construct(type); + } + + static const PropertyClass *get_object(const StaticProperty &prop) + { + return value_object_helper::get_object(prop); + } + + static void write(const StaticProperty &prop, BitStream &stream) + { + value_rw_helper::write(prop, stream); + } + + static void read(StaticProperty &prop, BitStream &stream) + { + value_rw_helper::read(prop, stream); + } + }; + + /** + * TODO: Documentation + */ + template + class StaticProperty : public PropertyBase + { + // Allow helper utilities access to m_value + friend value_object_helper; + friend value_rw_helper; + + public: + StaticProperty(PropertyClass &object, + const std::string &name, const Type &type) + : PropertyBase(object, name, type) + , m_value(value_helper::construct(type)) + {} + + StaticProperty(PropertyClass &object, + const std::string &name, const Type &type, ValueT value) + : PropertyBase(object, name, type) + { + m_value = value; + } + + constexpr bool is_dynamic() const override + { + return false; + } + + constexpr bool is_pointer() const override + { + return std::is_pointer::value; + } + + void write_value_to(BitStream &stream) const override + { + value_helper::write(*this, stream); + } + + void read_value_from(BitStream &stream) override + { + value_helper::read(*this, stream); + } + + const PropertyClass *get_object() const override + { + return value_helper::get_object(*this); + } + + ValueT &get() + { + return m_value; + } + + Value get_value() const override + { + return m_value; + } + + operator ValueT &() const + { + return m_value; + } + + ValueT *operator&() const + { + return &m_value; + } + + void operator=(const ValueT &value) + { + m_value = value; + } + + protected: + ValueT m_value; + }; +} +} diff --git a/include/ki/pclass/TypeSystem.h b/include/ki/pclass/TypeSystem.h index b2960ca..eb77a57 100644 --- a/include/ki/pclass/TypeSystem.h +++ b/include/ki/pclass/TypeSystem.h @@ -1,8 +1,11 @@ #pragma once #include #include -#include "Type.h" -#include "PrimitiveType.h" +#include "ki/pclass/HashCalculator.h" +#include "ki/pclass/types/Type.h" +#include "ki/pclass/types/PrimitiveType.h" +#include "ki/pclass/types/ClassType.h" +#include "ki/pclass/types/EnumType.h" namespace ki { @@ -14,52 +17,61 @@ namespace pclass class TypeSystem { public: - /** - * @return A singleton instance of TypeSystem with all C++ primitives defined. - */ - static TypeSystem &get_singleton(); - explicit TypeSystem(HashCalculator *hash_calculator); ~TypeSystem(); + const HashCalculator &get_hash_calculator() const; void set_hash_calculator(HashCalculator *hash_calculator); - Type &get_type(const std::string &name) const; - Type &get_type(hash_t hash) const; + bool has_type(const std::string &name) const; + bool has_type(hash_t hash) const; + + const Type &get_type(const std::string &name) const; + const Type &get_type(hash_t hash) const; template - Type &define_primitive(const std::string &name) + PrimitiveType &define_primitive(const std::string &name) { - auto hash = m_hash_calculator->calculate_type_hash(name); - auto *type = new PrimitiveType(name, hash); + auto *type = new PrimitiveType(name, *this); define_type(type); return *type; } template - Type &define_class(const std::string &name) + ClassType &define_class( + const std::string &name, const Type *base_class = nullptr) { - // Ensure that ClassT inherits PropertyClass - static_assert(std::is_base_of::value, "ClassT must inherit PropertyClass!"); + // If the caller does not specify a base class, automatically make + // ki::pclass::PropertyClass the base class (if it has been defined) + if (base_class == nullptr && has_type("class PropertyClass")) + base_class = &get_type("class PropertyClass"); - // TODO: Create class types + auto *type = new ClassType(name, base_class, *this); + define_type(type); + return *type; } template - Type &define_enum(const std::string &name) + EnumType *define_enum(const std::string &name) { - // Ensure that EnumT is an enum - static_assert(std::is_enum::value, "EnumT must be an enum!"); + /* + auto *type = new EnumType(name, this); + define_type(type); + return type; + */ + } - // TODO: Create enum types + template + ClassT *instantiate(const std::string &name) const + { + const auto &type = get_type(name); + return dynamic_cast(type.instantiate()); } protected: void define_type(Type *type); private: - static TypeSystem *s_instance; - TypeList m_types; TypeNameMap m_type_name_lookup; TypeHashMap m_type_hash_lookup; diff --git a/include/ki/pclass/Value.h b/include/ki/pclass/Value.h index 1c67fa8..5b1d964 100644 --- a/include/ki/pclass/Value.h +++ b/include/ki/pclass/Value.h @@ -16,7 +16,14 @@ namespace pclass template Value(T &value) { - m_value_ptr = value; + m_value_ptr = static_cast(&value); + m_type_hash = typeid(value).hash_code(); + } + + template + Value(const T &value) + { + m_value_ptr = const_cast(static_cast(&value)); m_type_hash = typeid(value).hash_code(); } @@ -44,7 +51,7 @@ namespace pclass // Make sure that this is allowed if (!is()) throw std::runtime_error("Type mismatch in Value::get() call."); - return *(T *)m_value_ptr; + return *static_cast(m_value_ptr); } /** @@ -56,7 +63,7 @@ namespace pclass // Make sure that this is allowed if (!is()) throw std::runtime_error("Type mismatch in Value::get() call."); - return *(T *)m_value_ptr; + return *static_cast(m_value_ptr); } private: diff --git a/include/ki/pclass/VectorProperty.h b/include/ki/pclass/VectorProperty.h new file mode 100644 index 0000000..df21ade --- /dev/null +++ b/include/ki/pclass/VectorProperty.h @@ -0,0 +1,166 @@ +#pragma once +#include +#include "ki/pclass/Property.h" +#include "ki/util/exception.h" + +namespace ki +{ +namespace pclass +{ + template + class VectorPropertyBase : public std::vector, public DynamicPropertyBase + { + public: + VectorPropertyBase(PropertyClass &object, + const std::string &name, const Type &type) + : DynamicPropertyBase(object, name, type) { } + + std::size_t get_element_count() const override + { + return this->size(); + } + + Value get_value(int index) const override + { + if (index < 0 || index >= this->size()) + throw runtime_error("Index out of bounds."); + return this->at(index); + } + }; + + template + class VectorPropertyBase2 : public VectorPropertyBase + { + VectorPropertyBase2(PropertyClass &object, + const std::string &name, const Type &type) + : VectorPropertyBase(object, name, type) { } + + constexpr bool is_pointer() const override + { + return false; + } + + void write_value_to(BitStream &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(BitStream &stream, const int index) override + { + if (index < 0 || index >= this->size()) + throw runtime_error("Index out of bounds."); + this->get_type().read_from(stream, this->at(index)); + } + }; + + template + class VectorPropertyBase2< + ValueT, + typename std::enable_if< + std::is_pointer::value + >::type + > : public VectorPropertyBase + { + public: + VectorPropertyBase2(PropertyClass &object, + const std::string &name, const Type &type) + : VectorPropertyBase(object, name, type) { } + + constexpr bool is_pointer() const override + { + return true; + } + + void write_value_to(BitStream &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(BitStream &stream, const int index) override + { + if (index < 0 || index >= this->size()) + throw runtime_error("Index out of bounds."); + this->get_type().read_from(stream, Value(*this->at(index))); + } + }; + + template < + typename ValueT, + typename _IsBaseEnable = void, + typename _IsPointerEnable = void + > + class VectorProperty : public VectorPropertyBase2 + { + public: + VectorProperty(PropertyClass &object, + const std::string &name, const Type &type) + : VectorPropertyBase2(object, name, type) { } + + const PropertyClass *get_object(const int index) const override + { + // We aren't holding an object at all, whoever called this is mistaken. + throw runtime_error( + "Tried calling get_object(index) on a property that does not store an object." + ); + } + }; + + template + class VectorProperty< + ValueT, + typename std::enable_if< + std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type, + typename std::enable_if< + !std::is_pointer::value + >::type + > : public VectorPropertyBase2 + { + public: + VectorProperty(PropertyClass &object, + const std::string &name, const Type &type) + : VectorPropertyBase2(object, name, type) { } + + const PropertyClass *get_object(const int index) const override + { + if (index < 0 || index >= this->size()) + throw runtime_error("Index out of bounds."); + return dynamic_cast(&this->at(index)); + } + }; + + template + class VectorProperty< + ValueT, + typename std::enable_if< + std::is_base_of< + PropertyClass, + typename std::remove_pointer::type + >::value + >::type, + typename std::enable_if< + std::is_pointer::value + >::type + > : public VectorPropertyBase2 + { + public: + VectorProperty(PropertyClass &object, + const std::string &name, const Type &type) + : VectorPropertyBase2(object, name, type) { } + + const PropertyClass *get_object(const int index) const override + { + if (index < 0 || index >= this->size()) + throw runtime_error("Index out of bounds."); + return dynamic_cast(this->at(index)); + } + }; +} +} diff --git a/include/ki/pclass/types/ClassType.h b/include/ki/pclass/types/ClassType.h new file mode 100644 index 0000000..fb50821 --- /dev/null +++ b/include/ki/pclass/types/ClassType.h @@ -0,0 +1,65 @@ +#pragma once +#include +#include +#include "ki/pclass/Property.h" +#include "ki/pclass/types/Type.h" +#include "ki/pclass/PropertyClass.h" +#include "ki/util/exception.h" + +namespace ki +{ +namespace pclass +{ + /** + * TODO: Documentation + */ + class ClassTypeBase : public Type + { + public: + ClassTypeBase(const std::string &name, + const Type *base_class, const TypeSystem &type_system); + virtual ~ClassTypeBase() {} + + bool inherits(const Type &type) const; + + private: + const ClassTypeBase *m_base_class; + }; + + /** + * TODO: Documentation + */ + template + class ClassType : public ClassTypeBase + { + // Ensure that ClassT inherits PropertyClass + static_assert(std::is_base_of::value, "ClassT must inherit PropertyClass!"); + + public: + ClassType(const std::string &name, + const Type *base_class, const TypeSystem &type_system) + : ClassTypeBase(name, base_class, type_system) {} + + PropertyClass *instantiate() const override + { + return new ClassT(*this, get_type_system()); + } + + void write_to(BitStream &stream, const Value &value) const override + { + const auto &object = dynamic_cast(value.get()); + const auto &properties = object.get_properties(); + for (auto it = properties.begin(); it != properties.end(); ++it) + (*it)->write_value_to(stream); + } + + void read_from(BitStream &stream, Value &value) const override + { + auto &object = dynamic_cast(value.get()); + auto &properties = object.get_properties(); + for (auto it = properties.begin(); it != properties.end(); ++it) + (*it)->read_value_from(stream); + } + }; +} +} diff --git a/include/ki/pclass/types/EnumType.h b/include/ki/pclass/types/EnumType.h new file mode 100644 index 0000000..468f700 --- /dev/null +++ b/include/ki/pclass/types/EnumType.h @@ -0,0 +1,32 @@ +#pragma once +#include +#include "ki/pclass/types/Type.h" + +namespace ki +{ +namespace pclass +{ + /** + * TODO: Documentation + */ + class EnumOption + { + private: + std::string m_name; + uint32_t m_value; + }; + + /** + * TODO: Documentation + */ + template + class EnumType : public Type + { + // Ensure that EnumT is an enum + static_assert(std::is_enum::value, "EnumT must be an enum!"); + + public: + EnumType(std::string name, hash_t hash, TypeSystem *type_system); + }; +} +} \ No newline at end of file diff --git a/include/ki/pclass/types/FloatingPointPrimitiveType.h b/include/ki/pclass/types/FloatingPointPrimitiveType.h index 2ea84c3..41fa848 100644 --- a/include/ki/pclass/types/FloatingPointPrimitiveType.h +++ b/include/ki/pclass/types/FloatingPointPrimitiveType.h @@ -4,48 +4,52 @@ namespace ki { - namespace pclass +namespace pclass +{ + template + struct PrimitiveTypeWriter< + ValueT, + typename std::enable_if::value>::type + > { - template - struct PrimitiveTypeWriter< - ValueT, - typename std::enable_if::value>::type - > + static void write_to(BitStream &stream, const ValueT &value) { - static void write_to(BitStream &stream, const ValueT &value) - { - // Reinterpret the reference as a reference to an integer - const uint_type &v = *((const uint_type *)&value); - stream.write(v, bitsizeof::value); - } + // Reinterpret the reference as a reference to an integer + const uint_type &v = *( + reinterpret_cast(&value) + ); + stream.write(v, bitsizeof::value); + } - private: - /** - * An unsigned integer type with the same size as the floating point type - * ValueT. - */ - using uint_type = typename bits::value>::uint_type; - }; + private: + /** + * An unsigned integer type with the same size as the floating point type + * ValueT. + */ + using uint_type = typename bits::value>::uint_type; + }; - template - struct PrimitiveTypeReader< - ValueT, - typename std::enable_if::value>::type - > + template + struct PrimitiveTypeReader< + ValueT, + typename std::enable_if::value>::type + > + { + static void read_from(BitStream &stream, ValueT &value) { - static void read_from(BitStream &stream, ValueT &value) - { - // Reinterpret the reference as a reference to an integer - uint_type &v = *((uint_type *)&value); - v = stream.read(bitsizeof::value); - } + // Reinterpret the reference as a reference to an integer + uint_type &v = *( + reinterpret_cast(&value) + ); + v = stream.read(bitsizeof::value); + } - private: - /** - * An unsigned integer type with the same size as the floating point type - * ValueT. - */ - using uint_type = typename bits::value>::uint_type; - }; - } -} \ No newline at end of file + private: + /** + * An unsigned integer type with the same size as the floating point type + * ValueT. + */ + using uint_type = typename bits::value>::uint_type; + }; +} +} diff --git a/include/ki/pclass/types/IntegralPrimitiveType.h b/include/ki/pclass/types/IntegralPrimitiveType.h index f332205..7fb7302 100644 --- a/include/ki/pclass/types/IntegralPrimitiveType.h +++ b/include/ki/pclass/types/IntegralPrimitiveType.h @@ -14,7 +14,7 @@ namespace pclass { static void write_to(BitStream &stream, const ValueT &value) { - stream.write(value, bitsizeof::value); + stream.write(value); } }; @@ -26,8 +26,8 @@ namespace pclass { static void read_from(BitStream &stream, ValueT &value) { - value = stream.read(bitsizeof::value); + value = stream.read(); } }; } -} \ No newline at end of file +} diff --git a/include/ki/pclass/PrimitiveType.h b/include/ki/pclass/types/PrimitiveType.h similarity index 86% rename from include/ki/pclass/PrimitiveType.h rename to include/ki/pclass/types/PrimitiveType.h index 825ca3d..55d3b88 100644 --- a/include/ki/pclass/PrimitiveType.h +++ b/include/ki/pclass/types/PrimitiveType.h @@ -1,5 +1,5 @@ #pragma once -#include "ki/pclass/Type.h" +#include "ki/pclass/types/Type.h" namespace ki { @@ -44,8 +44,8 @@ namespace pclass class PrimitiveType : public Type { public: - PrimitiveType(const std::string name, const hash_t hash) - : Type(name, hash) + PrimitiveType(const std::string name, const TypeSystem &type_system) + : Type(name, type_system) { m_kind = kind::PRIMITIVE; } @@ -69,4 +69,5 @@ namespace pclass // Include all template specializations #include "ki/pclass/types/IntegralPrimitiveType.h" -#include "ki/pclass/types/FloatingPointPrimitiveType.h" \ No newline at end of file +#include "ki/pclass/types/FloatingPointPrimitiveType.h" +#include "ki/pclass/types/StringPrimitiveType.h" diff --git a/include/ki/pclass/types/StringPrimitiveType.h b/include/ki/pclass/types/StringPrimitiveType.h new file mode 100644 index 0000000..4af6870 --- /dev/null +++ b/include/ki/pclass/types/StringPrimitiveType.h @@ -0,0 +1,53 @@ +#pragma once +#include + +namespace ki +{ +namespace pclass +{ + template < + typename _Elem, + typename _Traits, + typename _Alloc + > + struct PrimitiveTypeWriter> + { + private: + using type = std::basic_string<_Elem, _Traits, _Alloc>; + + public: + static void write_to(BitStream &stream, const type &value) + { + // Write the length as an unsigned short + stream.write(value.length()); + + // Write each character as _Elem + for (auto it = value.begin(); it != value.end(); ++it) + stream.write<_Elem>(*it); + } + }; + + template < + typename _Elem, + typename _Traits, + typename _Alloc + > + struct PrimitiveTypeReader> + { + private: + using type = std::basic_string<_Elem, _Traits, _Alloc>; + + public: + static void read_from(BitStream &stream, type &value) + { + // Read the length and create a new string with the correct capacity + auto length = stream.read(); + value = type(length, ' '); + + // Read each character into the string + for (auto it = value.begin(); it != value.end(); ++it) + *it = stream.read<_Elem>(); + } + }; +} +} diff --git a/include/ki/pclass/Type.h b/include/ki/pclass/types/Type.h similarity index 56% rename from include/ki/pclass/Type.h rename to include/ki/pclass/types/Type.h index d77e8d7..df61dac 100644 --- a/include/ki/pclass/Type.h +++ b/include/ki/pclass/types/Type.h @@ -4,42 +4,53 @@ #include #include "ki/pclass/HashCalculator.h" #include "ki/pclass/Value.h" -#include "ki/pclass/PropertyClass.h" #include "ki/util/BitStream.h" namespace ki { namespace pclass { + class PropertyClass; + /** - * TODO: Documentation + * A base class for classes that represent a Type. */ class Type { friend class TypeSystem; public: - /** - * TODO: Documentation - */ enum class kind { NONE, + + /** + * A Type that contain pure, simple values. + */ PRIMITIVE, + + /** + * A user-defined Type. + */ CLASS, + + /** + * A data type consisting of a set of named values. + */ ENUM }; - Type(std::string name, hash_t hash); - virtual ~Type() {}; + Type(const std::string &name, const TypeSystem &type_system); + virtual ~Type() { } std::string get_name() const; hash_t get_hash() const; kind get_kind() const; + const TypeSystem &get_type_system() const; virtual PropertyClass *instantiate() const; - virtual void write_to(BitStream &stream, const Value &value) const = 0; - virtual void read_from(BitStream &stream, Value &value) const = 0; + virtual void write_to(BitStream &stream, const Value &value) const; + virtual void read_from(BitStream &stream, Value &value) const; protected: kind m_kind; @@ -47,8 +58,13 @@ namespace pclass private: std::string m_name; hash_t m_hash; + const TypeSystem &m_type_system; - void set_hash(hash_t hash); + /** + * Called by a TypeSystem instance when it's HashCalculator + * is changed. + */ + virtual void update_hash(); }; typedef std::vector TypeList; diff --git a/include/ki/util/FlagsEnum.h b/include/ki/util/FlagsEnum.h new file mode 100644 index 0000000..c178526 --- /dev/null +++ b/include/ki/util/FlagsEnum.h @@ -0,0 +1,68 @@ +#pragma once +#include + +#define MAKE_FLAGS_ENUM(n) \ + template <> \ + struct ki::is_flags_enum : std::true_type {} + +#define SET_FLAG(v, f) v |= f +#define UNSET_FLAG(v, f) v &= ~f +#define FLAG_IS_SET(v, f) (v & f) == f + +namespace ki +{ + template + struct is_flags_enum : std::false_type {}; + + template < + typename EnumT, + typename = typename std::enable_if::value>::type + > + constexpr EnumT operator|(EnumT lhs, EnumT rhs) + { + using type = typename std::underlying_type::type; + return static_cast( + static_cast(lhs) | static_cast(rhs) + ); + } + + template < + typename EnumT, + typename = typename std::enable_if::value>::type + > + constexpr EnumT operator&(EnumT lhs, EnumT rhs) + { + using type = typename std::underlying_type::type; + return static_cast( + static_cast(lhs) & static_cast(rhs) + ); + } + + template < + typename EnumT, + typename = typename std::enable_if::value>::type + > + constexpr EnumT operator~(EnumT lhs) + { + using type = typename std::underlying_type::type; + return static_cast(~static_cast(lhs)); + } + + template < + typename EnumT, + typename = typename std::enable_if::value>::type + > + EnumT &operator|=(EnumT &lhs, EnumT rhs) + { + return lhs = lhs | rhs; + } + + template < + typename EnumT, + typename = typename std::enable_if::value>::type + > + EnumT &operator&=(EnumT &lhs, EnumT rhs) + { + return lhs = lhs & rhs; + } +} \ No newline at end of file diff --git a/include/ki/util/exception.h b/include/ki/util/exception.h new file mode 100644 index 0000000..2c240e4 --- /dev/null +++ b/include/ki/util/exception.h @@ -0,0 +1,12 @@ +#pragma once +#include + +namespace ki +{ + class runtime_error : public std::runtime_error + { + public: + explicit runtime_error(const std::string &message) + : std::runtime_error(message) {} + }; +} \ No newline at end of file diff --git a/src/pclass/CMakeLists.txt b/src/pclass/CMakeLists.txt index 2e21a7b..dbc01e3 100644 --- a/src/pclass/CMakeLists.txt +++ b/src/pclass/CMakeLists.txt @@ -1,6 +1,10 @@ target_sources(${PROJECT_NAME} PRIVATE + ${PROJECT_SOURCE_DIR}/src/pclass/ClassType.cpp ${PROJECT_SOURCE_DIR}/src/pclass/HashCalculator.cpp + ${PROJECT_SOURCE_DIR}/src/pclass/Property.cpp + ${PROJECT_SOURCE_DIR}/src/pclass/PropertyClass.cpp + ${PROJECT_SOURCE_DIR}/src/pclass/PropertyList.cpp ${PROJECT_SOURCE_DIR}/src/pclass/Type.cpp ${PROJECT_SOURCE_DIR}/src/pclass/TypeSystem.cpp ) \ No newline at end of file diff --git a/src/pclass/ClassType.cpp b/src/pclass/ClassType.cpp new file mode 100644 index 0000000..dea5885 --- /dev/null +++ b/src/pclass/ClassType.cpp @@ -0,0 +1,44 @@ +#include "ki/pclass/types/ClassType.h" + +namespace ki +{ +namespace pclass +{ + ClassTypeBase::ClassTypeBase(const std::string& name, + const Type* base_class, const TypeSystem& type_system) + : Type(name, type_system) + { + m_kind = kind::CLASS; + + // Have we been given a base class? + if (base_class) + { + // Make sure the base class is a class type + if (base_class->get_kind() != kind::CLASS) + throw runtime_error("base_class must be a class type!"); + + // Cast the base class up to a ClassTypeBase pointer + m_base_class = dynamic_cast(base_class); + if (!m_base_class) + throw runtime_error("base_class must inherit ClassTypeBase!"); + } + } + + bool ClassTypeBase::inherits(const Type &type) const + { + // Types do not technically inherit from themselves, but it is more useful + // if they report that they do since these checks are to make sure that objects + // have a common interface + if (&type == this) + return true; + + // If we have a base class, go down the inheritance tree and see if our + // ancestors inherit from the requested type + if (m_base_class) + return m_base_class->inherits(type); + + // We've reached the bottom of the inheritance tree; there is no inheritance. + return false; + } +} +} diff --git a/src/pclass/HashCalculator.cpp b/src/pclass/HashCalculator.cpp index 6fb7775..3f6ea8a 100644 --- a/src/pclass/HashCalculator.cpp +++ b/src/pclass/HashCalculator.cpp @@ -1,4 +1,5 @@ #include "ki/pclass/HashCalculator.h" +#include namespace ki { @@ -35,16 +36,13 @@ namespace pclass return result; } - hash_t WizardHashCalculator::calculate_property_hash(const std::string& name, const hash_t type_hash) const + hash_t WizardHashCalculator::calculate_property_hash(const std::string& name) const { // Find the hash of the property name hash_t result = 0x1505; for (auto it = name.begin(); it != name.end(); ++it) result = (0x21 * result) + *it; - result &= 0x7FFFFFFF; - - // Add the type hash onto it - return result + type_hash; + return result & 0x7FFFFFFF; } } -} \ No newline at end of file +} diff --git a/src/pclass/Property.cpp b/src/pclass/Property.cpp new file mode 100644 index 0000000..a849b18 --- /dev/null +++ b/src/pclass/Property.cpp @@ -0,0 +1,88 @@ +#include "ki/pclass/Property.h" +#include "ki/pclass/PropertyClass.h" +#include "ki/pclass/TypeSystem.h" +#include "ki/util/exception.h" + +namespace ki +{ +namespace pclass +{ + PropertyBase::PropertyBase(PropertyClass &object, + const std::string &name, const Type &type) + { + m_name = name; + m_name_hash = type + .get_type_system() + .get_hash_calculator() + .calculate_property_hash(name); + m_full_hash = m_name_hash + type.get_hash(); + m_type = &type; + + // Add this property to the object's property list + object.get_properties().add_property(this); + } + + std::string PropertyBase::get_name() const + { + return m_name; + } + + hash_t PropertyBase::get_name_hash() const + { + return m_name_hash; + } + + hash_t PropertyBase::get_full_hash() const + { + return m_full_hash; + } + + const Type &PropertyBase::get_type() const + { + return *m_type; + } + + bool PropertyBase::is_pointer() const + { + return false; + } + + bool PropertyBase::is_dynamic() const + { + return false; + } + + DynamicPropertyBase::DynamicPropertyBase(PropertyClass &object, + const std::string& name, const Type& type) + : PropertyBase(object, name, type) { } + + bool DynamicPropertyBase::is_dynamic() const + { + return true; + } + + Value DynamicPropertyBase::get_value() const + { + // The caller must specify an index + throw runtime_error("Called get_value() on a dynamic property. Use get_value(index) instead."); + } + + const PropertyClass *DynamicPropertyBase::get_object() const + { + // The caller must specify an index + throw runtime_error("Called get_object() on a dynamic property. Use get_object(index) instead."); + } + + void DynamicPropertyBase::write_value_to(BitStream &stream) const + { + // The caller must specify an index + throw runtime_error("Called write_value_to() on a dynamic property. Use write_value_to(index) instead."); + } + + void DynamicPropertyBase::read_value_from(BitStream &stream) + { + // The caller must specify an index + throw runtime_error("Called read_value_from() on a dynamic property. Use read_value_from(index) instead."); + } +} +} diff --git a/src/pclass/PropertyClass.cpp b/src/pclass/PropertyClass.cpp new file mode 100644 index 0000000..5f45534 --- /dev/null +++ b/src/pclass/PropertyClass.cpp @@ -0,0 +1,34 @@ +#include "ki/pclass/PropertyClass.h" + +namespace ki +{ +namespace pclass +{ + PropertyClass::PropertyClass(const Type &type, const TypeSystem &type_system) + { + m_type = &type; + m_properties = new PropertyList(); + } + + PropertyClass::~PropertyClass() + { + // Delete the list of properties + delete m_properties; + } + + const Type& PropertyClass::get_type() const + { + return *m_type; + } + + PropertyList &PropertyClass::get_properties() + { + return *m_properties; + } + + const PropertyList& PropertyClass::get_properties() const + { + return *m_properties; + } +} +} diff --git a/src/pclass/PropertyList.cpp b/src/pclass/PropertyList.cpp new file mode 100644 index 0000000..736b32b --- /dev/null +++ b/src/pclass/PropertyList.cpp @@ -0,0 +1,107 @@ +#include "ki/pclass/PropertyList.h" +#include "ki/pclass/Property.h" +#include "ki/util/exception.h" +#include +#include + +namespace ki +{ +namespace pclass +{ + std::size_t PropertyList::get_property_count() const + { + return m_properties.size(); + } + + bool PropertyList::has_property(const std::string& name) const + { + return m_property_name_lookup.find(name) + != m_property_name_lookup.end(); + } + + bool PropertyList::has_property(const hash_t hash) const + { + return m_property_hash_lookup.find(hash) + != m_property_hash_lookup.end(); + } + + 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::const_iterator PropertyList::begin() const + { + return m_properties.cbegin(); + } + + PropertyList::const_iterator PropertyList::end() const + { + return m_properties.cend(); + } + + void PropertyList::add_property(PropertyBase *prop) + { + // Make sure a property with the same name as another isn't being added + if (has_property(prop->get_name())) + { + // This pointer is going to be lost, so delete it now + delete prop; + + std::ostringstream oss; + oss << "A property has already been added with name: '" + << prop->get_name() << "'."; + throw runtime_error(oss.str()); + } + + // Check for hash collisions + if (has_property(prop->get_full_hash())) + { + const auto &other = get_property(prop->get_full_hash()); + + // This pointer is going to be lost, so delete it now + delete prop; + + std::ostringstream oss; + oss << "Cannot add property '" << prop->get_name() << "'. " + << "Hash collision with property '" << other.get_name() << "'."; + throw runtime_error(oss.str()); + } + + // Add the property to lookups + m_properties.push_back(prop); + m_property_name_lookup[prop->get_name()] = prop; + m_property_hash_lookup[prop->get_full_hash()] = prop; + } +} +} diff --git a/src/pclass/Type.cpp b/src/pclass/Type.cpp index b81672a..97cabad 100644 --- a/src/pclass/Type.cpp +++ b/src/pclass/Type.cpp @@ -1,4 +1,6 @@ -#include "ki/pclass/Type.h" +#include "ki/pclass/types/Type.h" +#include "ki/pclass/TypeSystem.h" +#include "ki/util/exception.h" #include #include @@ -6,10 +8,13 @@ namespace ki { namespace pclass { - Type::Type(const std::string name, const hash_t hash) + Type::Type(const std::string &name, const TypeSystem &type_system) + : m_type_system(type_system) { m_name = name; - m_hash = hash; + m_hash = m_type_system + .get_hash_calculator() + .calculate_type_hash(name); m_kind = kind::NONE; } @@ -18,11 +23,6 @@ namespace pclass return m_name; } - void Type::set_hash(const hash_t hash) - { - m_hash = hash; - } - hash_t Type::get_hash() const { return m_hash; @@ -33,11 +33,37 @@ namespace pclass return m_kind; } + const TypeSystem &Type::get_type_system() const + { + return m_type_system; + } + + void Type::write_to(BitStream& stream, const Value& value) const + { + std::ostringstream oss; + oss << "Type '" << m_name << "' does not implement Type::write_to."; + throw runtime_error(oss.str()); + } + + void Type::read_from(BitStream& stream, Value& value) const + { + std::ostringstream oss; + oss << "Type '" << m_name << "' does not implement Type::read_from."; + throw runtime_error(oss.str()); + } + PropertyClass *Type::instantiate() const { std::ostringstream oss; - oss << "Type '" << m_name << "' cannot be instantiated."; - throw std::runtime_error(oss.str()); + oss << "Type '" << m_name << "' does not implement Type::instantiate."; + throw runtime_error(oss.str()); + } + + void Type::update_hash() + { + m_hash = m_type_system + .get_hash_calculator() + .calculate_type_hash(m_name); } } -} \ No newline at end of file +} diff --git a/src/pclass/TypeSystem.cpp b/src/pclass/TypeSystem.cpp index 718213b..1c5f4ec 100644 --- a/src/pclass/TypeSystem.cpp +++ b/src/pclass/TypeSystem.cpp @@ -1,4 +1,6 @@ #include "ki/pclass/TypeSystem.h" +#include "ki/util/BitTypes.h" +#include "ki/util/exception.h" #include #include @@ -21,15 +23,7 @@ namespace ki { namespace pclass { - TypeSystem& TypeSystem::get_singleton() - { - if (s_instance == nullptr) - // Create the static instance with the default hash calculator - s_instance = new TypeSystem(new WizardHashCalculator()); - return *s_instance; - } - - TypeSystem::TypeSystem(HashCalculator* hash_calculator) + TypeSystem::TypeSystem(HashCalculator *hash_calculator) { m_hash_calculator = hash_calculator; @@ -69,8 +63,12 @@ namespace pclass define_primitive("float"); define_primitive("double"); - // TODO: Define bit floating point types - // TODO: Define string types + // Define string types + define_primitive("std::string"); + define_primitive("std::wstring"); + + // Define the base class for all classes + define_class("class PropertyClass"); } TypeSystem::~TypeSystem() @@ -87,6 +85,15 @@ namespace pclass delete m_hash_calculator; } + const HashCalculator &TypeSystem::get_hash_calculator() const + { + // Make sure the hash calculator isn't null + if (m_hash_calculator == nullptr) + throw runtime_error("TypeSystem::get_hash_calculator() called but hash calculator is null."); + + return *m_hash_calculator; + } + void TypeSystem::set_hash_calculator(HashCalculator* hash_calculator) { // Update the hash calculator @@ -96,46 +103,47 @@ namespace pclass m_type_hash_lookup.clear(); for (auto it = m_types.begin(); it != m_types.end(); ++it) { - // Calculate the new hash and update the type - auto *type = *it; - const auto new_hash = m_hash_calculator->calculate_type_hash(type->get_name()); - type->set_hash(new_hash); - // Add the new hash to lookup - m_type_hash_lookup[new_hash] = type; - - // Is this type a class? - if (type->get_kind() == Type::kind::CLASS) - { - // TODO: Recalculate property hashes - } + auto *type = *it; + type->update_hash(); + m_type_hash_lookup[type->get_hash()] = type; } } - Type& TypeSystem::get_type(const std::string &name) const + bool TypeSystem::has_type(const std::string &name) const + { + return m_type_name_lookup.find(name) != m_type_name_lookup.end(); + } + + bool TypeSystem::has_type(const hash_t hash) const + { + return m_type_hash_lookup.find(hash) != m_type_hash_lookup.end(); + } + + const Type &TypeSystem::get_type(const std::string &name) const { const auto it = m_type_name_lookup.find(name); if (it == m_type_name_lookup.end()) { std::ostringstream oss; oss << "Could not find type with name '" << name << "'."; - throw std::runtime_error(oss.str()); + throw runtime_error(oss.str()); } - return *(it->second); + return *it->second; } - Type& TypeSystem::get_type(const hash_t hash) const + const Type &TypeSystem::get_type(const hash_t hash) const { const auto it = m_type_hash_lookup.find(hash); if (it == m_type_hash_lookup.end()) { std::ostringstream oss; - oss << "Could not find type with hash: " << + oss << "Could not find type with hash: 0x" << std::hex << std::setw(8) << std::setfill('0') << std::uppercase << hash << "."; - throw std::runtime_error(oss.str()); + throw runtime_error(oss.str()); } - return *(it->second); + return *it->second; } void TypeSystem::define_type(Type *type) @@ -149,7 +157,7 @@ namespace pclass // Throw an error std::ostringstream oss; oss << "Type '" << type->get_name() << "' is already defined."; - throw std::runtime_error(oss.str()); + throw runtime_error(oss.str()); } // Does a type with this hash already exist? @@ -163,7 +171,7 @@ namespace pclass std::ostringstream oss; oss << "Type hash collision between '" << type->get_name() << "' and '" << other_type.get_name() << "'."; - throw std::runtime_error(oss.str()); + throw runtime_error(oss.str()); } // This type is safe to add to our lookups