Optional hash/full metadata streaming for QObjects.

This commit is contained in:
Andrzej Kapolka 2014-03-16 17:29:30 -07:00
parent 35f57cb998
commit b21247ca67
3 changed files with 243 additions and 40 deletions

View file

@ -8,8 +8,8 @@
#include <cstring>
#include <QCryptographicHash>
#include <QDataStream>
#include <QMetaProperty>
#include <QMetaType>
#include <QUrl>
#include <QtDebug>
@ -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<PropertyReader> 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<SharedObject>& 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<QByteArray, const QMetaObject*>& Bitstream::getMetaObjects() {
static QHash<QByteArray, const QMetaObject*> metaObjects;
return metaObjects;
@ -562,3 +638,53 @@ QHash<int, const TypeStreamer*>& Bitstream::getTypeStreamers() {
return typeStreamers;
}
QVector<PropertyReader> Bitstream::getPropertyReaders(const QMetaObject* metaObject) {
QVector<PropertyReader> 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<PropertyReader>& 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);
}
}

View file

@ -10,6 +10,7 @@
#define __interface__Bitstream__
#include <QHash>
#include <QMetaProperty>
#include <QMetaType>
#include <QPointer>
#include <QScriptString>
@ -29,7 +30,9 @@ class QUrl;
class Attribute;
class AttributeValue;
class Bitstream;
class ObjectReader;
class OwnedAttributeValue;
class PropertyReader;
class TypeStreamer;
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
@ -187,7 +190,7 @@ public:
class ReadMappings {
public:
QHash<int, const QMetaObject*> metaObjectValues;
QHash<int, ObjectReader> metaObjectValues;
QHash<int, const TypeStreamer*> typeStreamerValues;
QHash<int, AttributePointer> attributeValues;
QHash<int, QScriptString> 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<const QMetaObject*, const QMetaObject*, const QMetaObject*> _metaObjectStreamer;
RepeatedValueStreamer<const QMetaObject*, const QMetaObject*, ObjectReader> _metaObjectStreamer;
RepeatedValueStreamer<const TypeStreamer*> _typeStreamerStreamer;
RepeatedValueStreamer<AttributePointer> _attributeStreamer;
RepeatedValueStreamer<QScriptString> _scriptStringStreamer;
@ -357,6 +359,7 @@ private:
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
static QMultiHash<const QMetaObject*, const QMetaObject*>& getMetaObjectSubClasses();
static QHash<int, const TypeStreamer*>& getTypeStreamers();
static QVector<PropertyReader> getPropertyReaders(const QMetaObject* metaObject);
};
template<class T> inline Bitstream& Bitstream::operator<<(const QList<T>& list) {
@ -425,6 +428,48 @@ template<class K, class V> inline Bitstream& Bitstream::operator>>(QHash<K, V>&
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<PropertyReader>& properties = QVector<PropertyReader>());
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<PropertyReader> _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.

View file

@ -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!";