From 4d34d9ae2bc754fde179f551875b068d0b13a3c8 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 25 May 2020 23:39:00 -0400 Subject: [PATCH] Implements BSON deserialisation, other than arrays. --- .../Implementation/MultiConfigurable.cpp | 8 + Reflection/Struct.cpp | 175 +++++++++++++++++- Reflection/Struct.hpp | 33 +++- 3 files changed, 207 insertions(+), 9 deletions(-) diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp index 5d8fb89cd..df7333924 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp @@ -72,6 +72,14 @@ class MultiStruct: public Reflection::Struct { return nullptr; } + void *get(const std::string &name) final { + for(auto &options: options_) { + auto value = options->get(name); + if(value) return value; + } + return nullptr; + } + void set(const std::string &name, const void *value) final { const auto safe_type = type_of(name); if(!safe_type) return; diff --git a/Reflection/Struct.cpp b/Reflection/Struct.cpp index e07f9cc79..8dc88f166 100644 --- a/Reflection/Struct.cpp +++ b/Reflection/Struct.cpp @@ -69,22 +69,58 @@ static size_t size(const std::type_info *type) { // MARK: - Setters +template <> bool Reflection::set(Struct &target, const std::string &name, float value) { + const auto target_type = target.type_of(name); + if(!target_type) return false; + + if(*target_type == typeid(float)) { + target.set(name, &value); + return true; + } + + return set(target, name, value); +} + +template <> bool Reflection::set(Struct &target, const std::string &name, double value) { + const auto target_type = target.type_of(name); + if(!target_type) return false; + + if(*target_type == typeid(double)) { + target.set(name, &value); + return true; + } + + if(*target_type == typeid(float)) { + const float float_value = float(value); + target.set(name, &float_value); + return true; + } + + return false; +} + template <> bool Reflection::set(Struct &target, const std::string &name, int value) { + return set(target, name, value); +} + +template <> bool Reflection::set(Struct &target, const std::string &name, int64_t value) { const auto target_type = target.type_of(name); if(!target_type) return false; // No need to convert an int or a registered enum. if(*target_type == typeid(int) || !Reflection::Enum::name(*target_type).empty()) { + const int value32 = int(value); + target.set(name, &value32); + return true; + } + + // Set an int64_t directly. + if(*target_type == typeid(int64_t)) { target.set(name, &value); return true; } - // Promote to an int64_t. - if(*target_type == typeid(int64_t)) { - const auto int64 = int64_t(value); - target.set(name, &int64); - return true; - } + // TODO: other int sizes. return false; } @@ -93,6 +129,14 @@ template <> bool Reflection::set(Struct &target, const std::string &name, const const auto target_type = target.type_of(name); if(!target_type) return false; + // If the target is a string, assign. + if(*target_type == typeid(std::string)) { + auto child = reinterpret_cast(target.get(name)); + *child = value; + return true; + } + + // From here on, make an attempt to convert to a named enum. if(Reflection::Enum::name(*target_type).empty()) { return false; } @@ -389,6 +433,14 @@ std::vector Reflection::Struct::serialise() const { return; } + // Strings are written naturally. + if(*type == typeid(std::string)) { + const uint8_t *address = reinterpret_cast(get(key)); + const std::string *const text = reinterpret_cast(address + offset*sizeof(std::string)); + push_string(*text); + return; + } + // Store std::vectors as binary data. if(*type == typeid(std::vector)) { result.push_back(0x05); @@ -442,7 +494,7 @@ std::vector Reflection::Struct::serialise() const { if(count > 1) { // In BSON, an array is a sub-document with ASCII keys '0', '1', etc. - result.push_back(0x03); // TODO: 0x04. + result.push_back(0x04); push_name(result, key); std::vector array; @@ -460,3 +512,112 @@ std::vector Reflection::Struct::serialise() const { wrap_object(result); return result; } + +bool Reflection::Struct::deserialise(const std::vector &bson) { + return deserialise(bson.data(), bson.size()); +} + +bool Reflection::Struct::deserialise(const uint8_t *bson, size_t size) { + // Validate the object's declared size. + const auto end = bson + size; + auto read_int = [&bson] (auto &target) { + constexpr auto shift = 8 * (sizeof(target) - 1); + target = 0; + for(size_t c = 0; c < sizeof(target); ++c) { + target >>= 8; + target |= decltype(target)(*bson) << shift; + ++bson; + } + }; + + uint32_t object_size; + read_int(object_size); + if(object_size > size) return false; + + while(true) { + const uint8_t next_type = *bson; + ++bson; + if(!next_type) break; + + std::string key; + while(*bson) { + key.push_back(char(*bson)); + ++bson; + } + ++bson; + + switch(next_type) { + default: + return false; + + // TODO: arrays. + + // 0x03: A subdocument; try to install the inner BSON. + // 0x05: Binary data. Seek to populate a std::vector. + case 0x03: + case 0x05: { + const auto type = type_of(key); + + uint32_t subobject_size; + read_int(subobject_size); + + if(next_type == 0x03 && *type == typeid(Reflection::Struct)) { + auto child = reinterpret_cast(get(key)); + child->deserialise(bson - 4, size_t(end - bson + 4)); + bson += subobject_size - 4; + } + + if(next_type == 0x05 && *type == typeid(std::vector)) { + auto child = reinterpret_cast *>(get(key)); + *child = std::vector(bson, bson + subobject_size); + bson += subobject_size; + } + } break; + + // String. + case 0x02: { + uint32_t length; + read_int(length); + + const std::string value(bson, bson + length - 1); + ::Reflection::set(*this, key, value); + + bson += length; + } break; + + // Boolean. + case 0x08: { + const bool value = *bson; + ++bson; + ::Reflection::set(*this, key, value); + } break; + + // 32-bit int. + case 0x10: { + int32_t value; + read_int(value); + ::Reflection::set(*this, key, value); + } break; + + // 64-bit int. + case 0x12: { + int64_t value; + read_int(value); + ::Reflection::set(*this, key, value); + } break; + + // 64-bit double. + case 0x01: { + uint64_t value; + read_int(value); + + // TODO: real conversion. + double double_value = double(value); + + ::Reflection::set(*this, key, double_value); + } break; + } + } + + return true; +} diff --git a/Reflection/Struct.hpp b/Reflection/Struct.hpp index 703e39ad1..3cf0a4ab0 100644 --- a/Reflection/Struct.hpp +++ b/Reflection/Struct.hpp @@ -29,6 +29,7 @@ struct Struct { virtual const std::type_info *type_of(const std::string &name) const = 0; virtual size_t count_of(const std::string &name) const = 0; virtual void set(const std::string &name, const void *value) = 0; + virtual void *get(const std::string &name) = 0; virtual const void *get(const std::string &name) const = 0; virtual std::vector values_for(const std::string &name) const = 0; virtual ~Struct() {} @@ -40,21 +41,38 @@ struct Struct { std::string description() const; /*! - @returns The BSON serialisation of this struct. + Serialises this struct in BSON format. + + Supported field types: + + * [u]int[8/16/32/64]_t; + * float and double; + * bool; + * std::string; + * plain C-style arrays of any of the above; + * other reflective structs; + * std::vector as raw binary data. + + TODO: vector of string, possibly? Or more general vector support? + + @returns The BSON serialisation. */ std::vector serialise() const; /*! - Applies as many fields as possible from the incoming BSON. + Applies as many fields as possible from the incoming BSON. Supports the same types + as @c serialise. */ bool deserialise(const std::vector &bson); /*! + Called to determine whether @c key should be included in the serialisation of this struct. */ virtual bool should_serialise(const std::string &key) const { return true; } private: void append(std::ostringstream &stream, const std::string &key, const std::type_info *type, size_t offset) const; + bool deserialise(const uint8_t *bson, size_t size); }; /*! @@ -68,9 +86,11 @@ template bool set(Struct &target, const std::string &name, Type Setting an int: * to an int copies the int; + * to a smaller type, truncates the int; * to an int64_t promotes the int; and * to a registered enum, copies the int. */ +template <> bool set(Struct &target, const std::string &name, int64_t value); template <> bool set(Struct &target, const std::string &name, int value); /*! @@ -89,6 +109,9 @@ template <> bool set(Struct &target, const std::string &name, const char *value) template <> bool set(Struct &target, const std::string &name, bool value); +template <> bool set(Struct &target, const std::string &name, float value); +template <> bool set(Struct &target, const std::string &name, double value); + /*! Fuzzy-set attempts to set any property based on a string value. This is intended to allow input provided by the user. @@ -126,6 +149,12 @@ template class StructImpl: public Struct { @returns the value of type @c Type that is loaded from the offset registered for the field @c name. It is the caller's responsibility to provide an appropriate type of data. */ + void *get(const std::string &name) final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return nullptr; + return reinterpret_cast(this) + iterator->second.offset; + } + const void *get(const std::string &name) const final { const auto iterator = contents_.find(name); if(iterator == contents_.end()) return nullptr;