mirror of
https://github.com/lubosz/overte.git
synced 2025-04-09 08:22:30 +02:00
Optional hash/full metadata streaming for QObjects.
This commit is contained in:
parent
35f57cb998
commit
b21247ca67
3 changed files with 243 additions and 40 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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!";
|
||||
|
||||
|
|
Loading…
Reference in a new issue