From b21247ca67457e22d77e8b51ab63d2477a4635ad Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Sun, 16 Mar 2014 17:29:30 -0700 Subject: [PATCH] Optional hash/full metadata streaming for QObjects. --- libraries/metavoxels/src/Bitstream.cpp | 194 +++++++++++++++++++----- libraries/metavoxels/src/Bitstream.h | 55 ++++++- tests/metavoxels/src/MetavoxelTests.cpp | 34 ++++- 3 files changed, 243 insertions(+), 40 deletions(-) diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 1b89acd18f..6c8d8ec559 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -8,8 +8,8 @@ #include +#include #include -#include #include #include #include @@ -377,13 +377,9 @@ Bitstream& Bitstream::operator<<(const QObject* object) { } Bitstream& Bitstream::operator>>(QObject*& object) { - const QMetaObject* metaObject; - _metaObjectStreamer >> metaObject; - if (!metaObject) { - object = NULL; - return *this; - } - readProperties(object = metaObject->newInstance()); + ObjectReader objectReader; + _metaObjectStreamer >> objectReader; + object = objectReader.read(*this); return *this; } @@ -393,7 +389,14 @@ Bitstream& Bitstream::operator<<(const QMetaObject* metaObject) { } Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) { - _metaObjectStreamer >> metaObject; + ObjectReader objectReader; + _metaObjectStreamer >> objectReader; + metaObject = objectReader.getMetaObject(); + return *this; +} + +Bitstream& Bitstream::operator>>(ObjectReader& objectReader) { + _metaObjectStreamer >> objectReader; return *this; } @@ -438,21 +441,111 @@ Bitstream& Bitstream::operator>>(SharedObjectPointer& object) { } Bitstream& Bitstream::operator<(const QMetaObject* metaObject) { - return *this << (metaObject ? QByteArray::fromRawData(metaObject->className(), - strlen(metaObject->className())) : QByteArray()); + if (!metaObject) { + return *this << QByteArray(); + } + *this << QByteArray::fromRawData(metaObject->className(), strlen(metaObject->className())); + if (_metadataType == NO_METADATA) { + return *this; + } + int storedPropertyCount = 0; + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (property.isStored() && getTypeStreamers().contains(property.userType())) { + storedPropertyCount++; + } + } + *this << storedPropertyCount; + QCryptographicHash hash(QCryptographicHash::Md5); + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored()) { + continue; + } + const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); + if (!typeStreamer) { + continue; + } + _typeStreamerStreamer << typeStreamer; + if (_metadataType == FULL_METADATA) { + *this << QByteArray::fromRawData(property.name(), strlen(property.name())); + } else { + hash.addData(property.name(), strlen(property.name()) + 1); + } + } + if (_metadataType == HASH_METADATA) { + QByteArray hashResult = hash.result(); + write(hashResult.constData(), hashResult.size() * BITS_IN_BYTE); + } + return *this; } -Bitstream& Bitstream::operator>(const QMetaObject*& metaObject) { +Bitstream& Bitstream::operator>(ObjectReader& objectReader) { QByteArray className; *this >> className; if (className.isEmpty()) { - metaObject = NULL; + objectReader = ObjectReader(); return *this; } - metaObject = getMetaObjects().value(className); + const QMetaObject* metaObject = getMetaObjects().value(className); if (!metaObject) { qWarning() << "Unknown class name: " << className << "\n"; } + if (_metadataType == NO_METADATA) { + objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject)); + return *this; + } + int storedPropertyCount; + *this >> storedPropertyCount; + QVector properties(storedPropertyCount); + for (int i = 0; i < storedPropertyCount; i++) { + const TypeStreamer* typeStreamer; + *this >> typeStreamer; + QMetaProperty property = QMetaProperty(); + if (_metadataType == FULL_METADATA) { + QByteArray propertyName; + *this >> propertyName; + if (metaObject) { + property = metaObject->property(metaObject->indexOfProperty(propertyName)); + } + } + properties[i] = PropertyReader(typeStreamer, property); + } + // for hash metadata, check the names/types of the properties as well as the name hash against our own class + if (_metadataType == HASH_METADATA) { + QCryptographicHash hash(QCryptographicHash::Md5); + if (metaObject) { + int propertyIndex = 0; + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored()) { + continue; + } + const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); + if (!typeStreamer) { + continue; + } + if (propertyIndex >= properties.size() || properties.at(propertyIndex).getStreamer() != typeStreamer) { + objectReader = ObjectReader(className, metaObject, properties); + return *this; + } + hash.addData(property.name(), strlen(property.name()) + 1); + propertyIndex++; + } + if (propertyIndex != properties.size()) { + objectReader = ObjectReader(className, metaObject, properties); + return *this; + } + } + QByteArray localHashResult = hash.result(); + QByteArray remoteHashResult(localHashResult.size(), 0); + read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE); + if (metaObject && localHashResult == remoteHashResult) { + objectReader = ObjectReader(className, metaObject, getPropertyReaders(metaObject)); + return *this; + } + } + objectReader = ObjectReader(className, metaObject, properties); return *this; } @@ -509,12 +602,9 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { } QPointer& pointer = _weakSharedObjectHash[id]; if (pointer) { - const QMetaObject* metaObject; - _metaObjectStreamer >> metaObject; - if (metaObject != pointer->metaObject()) { - qWarning() << "Class mismatch: " << pointer->metaObject()->className() << metaObject->className(); - } - readProperties(pointer.data()); + ObjectReader objectReader; + _metaObjectStreamer >> objectReader; + objectReader.read(*this, pointer.data()); } else { QObject* rawObject; @@ -533,20 +623,6 @@ void Bitstream::clearSharedObject(QObject* object) { } } -void Bitstream::readProperties(QObject* object) { - const QMetaObject* metaObject = object->metaObject(); - for (int i = 0; i < metaObject->propertyCount(); i++) { - QMetaProperty property = metaObject->property(i); - if (!property.isStored(object)) { - continue; - } - const TypeStreamer* streamer = getTypeStreamers().value(property.userType()); - if (streamer) { - property.write(object, streamer->read(*this)); - } - } -} - QHash& Bitstream::getMetaObjects() { static QHash metaObjects; return metaObjects; @@ -562,3 +638,53 @@ QHash& Bitstream::getTypeStreamers() { return typeStreamers; } +QVector Bitstream::getPropertyReaders(const QMetaObject* metaObject) { + QVector propertyReaders; + if (!metaObject) { + return propertyReaders; + } + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (!property.isStored()) { + continue; + } + const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType()); + if (typeStreamer) { + propertyReaders.append(PropertyReader(typeStreamer, property)); + } + } + return propertyReaders; +} + +ObjectReader::ObjectReader(const QByteArray& className, const QMetaObject* metaObject, + const QVector& properties) : + _className(className), + _metaObject(metaObject), + _properties(properties) { +} + +QObject* ObjectReader::read(Bitstream& in, QObject* object) const { + if (!object && _metaObject) { + object = _metaObject->newInstance(); + } + foreach (const PropertyReader& property, _properties) { + property.read(in, object); + } + return object; +} + +uint qHash(const ObjectReader& objectReader, uint seed) { + return qHash(objectReader.getClassName(), seed); +} + +PropertyReader::PropertyReader(const TypeStreamer* streamer, const QMetaProperty& property) : + _streamer(streamer), + _property(property) { +} + +void PropertyReader::read(Bitstream& in, QObject* object) const { + QVariant value = _streamer->read(in); + if (_property.isValid() && object) { + _property.write(object, value); + } +} diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 1acdd63841..8afea69e4c 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -10,6 +10,7 @@ #define __interface__Bitstream__ #include +#include #include #include #include @@ -29,7 +30,9 @@ class QUrl; class Attribute; class AttributeValue; class Bitstream; +class ObjectReader; class OwnedAttributeValue; +class PropertyReader; class TypeStreamer; typedef SharedObjectPointerTemplate AttributePointer; @@ -187,7 +190,7 @@ public: class ReadMappings { public: - QHash metaObjectValues; + QHash metaObjectValues; QHash typeStreamerValues; QHash attributeValues; QHash scriptStringValues; @@ -300,6 +303,7 @@ public: Bitstream& operator<<(const QMetaObject* metaObject); Bitstream& operator>>(const QMetaObject*& metaObject); + Bitstream& operator>>(ObjectReader& objectReader); Bitstream& operator<<(const TypeStreamer* streamer); Bitstream& operator>>(const TypeStreamer*& streamer); @@ -314,7 +318,7 @@ public: Bitstream& operator>>(SharedObjectPointer& object); Bitstream& operator<(const QMetaObject* metaObject); - Bitstream& operator>(const QMetaObject*& metaObject); + Bitstream& operator>(ObjectReader& objectReader); Bitstream& operator<(const TypeStreamer* streamer); Bitstream& operator>(const TypeStreamer*& streamer); @@ -338,15 +342,13 @@ private slots: private: - void readProperties(QObject* object); - QDataStream& _underlying; quint8 _byte; int _position; MetadataType _metadataType; - RepeatedValueStreamer _metaObjectStreamer; + RepeatedValueStreamer _metaObjectStreamer; RepeatedValueStreamer _typeStreamerStreamer; RepeatedValueStreamer _attributeStreamer; RepeatedValueStreamer _scriptStringStreamer; @@ -357,6 +359,7 @@ private: static QHash& getMetaObjects(); static QMultiHash& getMetaObjectSubClasses(); static QHash& getTypeStreamers(); + static QVector getPropertyReaders(const QMetaObject* metaObject); }; template inline Bitstream& Bitstream::operator<<(const QList& list) { @@ -425,6 +428,48 @@ template inline Bitstream& Bitstream::operator>>(QHash& return *this; } +/// Contains the information required to read an object from the stream. +class ObjectReader { +public: + + ObjectReader(const QByteArray& className = QByteArray(), const QMetaObject* metaObject = NULL, + const QVector& properties = QVector()); + + const QByteArray& getClassName() const { return _className; } + const QMetaObject* getMetaObject() const { return _metaObject; } + + bool isNull() const { return _className.isEmpty(); } + + QObject* read(Bitstream& in, QObject* object = NULL) const; + + bool operator==(const ObjectReader& other) const { return _className == other._className; } + bool operator!=(const ObjectReader& other) const { return _className != other._className; } + +private: + + QByteArray _className; + const QMetaObject* _metaObject; + QVector _properties; +}; + +uint qHash(const ObjectReader& objectReader, uint seed = 0); + +/// Contains the information required to read an object property from the stream and apply it. +class PropertyReader { +public: + + PropertyReader(const TypeStreamer* streamer = NULL, const QMetaProperty& property = QMetaProperty()); + + const TypeStreamer* getStreamer() const { return _streamer; } + + void read(Bitstream& in, QObject* object) const; + +private: + + const TypeStreamer* _streamer; + QMetaProperty _property; +}; + Q_DECLARE_METATYPE(const QMetaObject*) /// Macro for registering streamable meta-objects. diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index b1f3315bc0..9de48364b6 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -34,9 +34,33 @@ static int streamedBytesReceived = 0; static int sharedObjectsCreated = 0; static int sharedObjectsDestroyed = 0; +static bool testSerialization(Bitstream::MetadataType metadataType) { + QByteArray array; + QDataStream outStream(&array, QIODevice::WriteOnly); + Bitstream out(outStream, metadataType); + SharedObjectPointer testObjectWritten = new TestSharedObjectA(randFloat()); + out << testObjectWritten; + out.flush(); + + QDataStream inStream(array); + Bitstream in(inStream, metadataType); + SharedObjectPointer testObjectRead; + in >> testObjectRead; + + if (!testObjectWritten->equals(testObjectRead)) { + QDebug debug = qDebug() << "Read/write mismatch"; + testObjectWritten->dump(debug); + testObjectRead->dump(debug); + return true; + } + + return false; +} + bool MetavoxelTests::run() { - qDebug() << "Running metavoxel tests..."; + qDebug() << "Running transmission tests..."; + qDebug(); // seed the random number generator so that our tests are reproducible srand(0xBAAAAABE); @@ -62,6 +86,14 @@ bool MetavoxelTests::run() { qDebug() << "Sent" << streamedBytesSent << "streamed bytes, received" << streamedBytesReceived; qDebug() << "Sent" << datagramsSent << "datagrams, received" << datagramsReceived; qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed; + qDebug(); + + qDebug() << "Running serialization tests..."; + qDebug(); + + if (testSerialization(Bitstream::HASH_METADATA) || testSerialization(Bitstream::FULL_METADATA)) { + return true; + } qDebug() << "All tests passed!";