More work on version-resilient type streaming.

This commit is contained in:
Andrzej Kapolka 2014-03-17 15:12:43 -07:00
parent 61660f890e
commit d8b83fd308
3 changed files with 302 additions and 36 deletions

View file

@ -82,6 +82,10 @@ int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) {
return 0;
}
const TypeStreamer* Bitstream::getTypeStreamer(int type) {
return getTypeStreamers().value(type);
}
const QMetaObject* Bitstream::getMetaObject(const QByteArray& className) {
return getMetaObjects().value(className);
}
@ -325,11 +329,9 @@ Bitstream& Bitstream::operator<<(const QVariant& value) {
}
Bitstream& Bitstream::operator>>(QVariant& value) {
const TypeStreamer* streamer;
_typeStreamerStreamer >> streamer;
if (streamer) {
value = streamer->read(*this);
}
TypeReader reader;
_typeStreamerStreamer >> reader;
value = reader.read(*this);
return *this;
}
@ -406,7 +408,14 @@ Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) {
}
Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) {
_typeStreamerStreamer >> streamer;
TypeReader typeReader;
_typeStreamerStreamer >> typeReader;
streamer = typeReader.getStreamer();
return *this;
}
Bitstream& Bitstream::operator>>(TypeReader& reader) {
_typeStreamerStreamer >> reader;
return *this;
}
@ -499,8 +508,8 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) {
*this >> storedPropertyCount;
QVector<PropertyReader> properties(storedPropertyCount);
for (int i = 0; i < storedPropertyCount; i++) {
const TypeStreamer* typeStreamer;
*this >> typeStreamer;
TypeReader typeReader;
*this >> typeReader;
QMetaProperty property = QMetaProperty();
if (_metadataType == FULL_METADATA) {
QByteArray propertyName;
@ -509,7 +518,7 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) {
property = metaObject->property(metaObject->indexOfProperty(propertyName));
}
}
properties[i] = PropertyReader(typeStreamer, property);
properties[i] = PropertyReader(typeReader, 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) {
@ -525,7 +534,8 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) {
if (!typeStreamer) {
continue;
}
if (propertyIndex >= properties.size() || properties.at(propertyIndex).getStreamer() != typeStreamer) {
if (propertyIndex >= properties.size() ||
!properties.at(propertyIndex).getReader().matchesExactly(typeStreamer)) {
objectReader = ObjectReader(className, metaObject, properties);
return *this;
}
@ -551,16 +561,106 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) {
Bitstream& Bitstream::operator<(const TypeStreamer* streamer) {
const char* typeName = QMetaType::typeName(streamer->getType());
return *this << QByteArray::fromRawData(typeName, strlen(typeName));
*this << QByteArray::fromRawData(typeName, strlen(typeName));
if (_metadataType == NO_METADATA) {
return *this;
}
const QVector<MetaField>& metaFields = streamer->getMetaFields();
*this << metaFields.size();
if (metaFields.isEmpty()) {
return *this;
}
QCryptographicHash hash(QCryptographicHash::Md5);
foreach (const MetaField& metaField, metaFields) {
_typeStreamerStreamer << metaField.getStreamer();
if (_metadataType == FULL_METADATA) {
*this << metaField.getName();
} else {
hash.addData(metaField.getName().constData(), metaField.getName().size() + 1);
}
}
if (_metadataType == HASH_METADATA) {
QByteArray hashResult = hash.result();
write(hashResult.constData(), hashResult.size() * BITS_IN_BYTE);
}
return *this;
}
Bitstream& Bitstream::operator>(const TypeStreamer*& streamer) {
Bitstream& Bitstream::operator>(TypeReader& reader) {
QByteArray typeName;
*this >> typeName;
streamer = getTypeStreamers().value(QMetaType::type(typeName.constData()));
const TypeStreamer* streamer = getTypeStreamers().value(QMetaType::type(typeName.constData()));
if (!streamer) {
qWarning() << "Unknown type name: " << typeName << "\n";
}
if (_metadataType == NO_METADATA) {
reader = TypeReader(typeName, streamer);
return *this;
}
int fieldCount;
*this >> fieldCount;
QVector<FieldReader> fields(fieldCount);
for (int i = 0; i < fieldCount; i++) {
TypeReader typeReader;
*this >> typeReader;
int index = -1;
if (_metadataType == FULL_METADATA) {
QByteArray fieldName;
*this >> fieldName;
if (streamer) {
index = streamer->getFieldIndex(fieldName);
}
}
fields[i] = FieldReader(typeReader, index);
}
// for hash metadata, check the names/types of the fields as well as the name hash against our own class
if (_metadataType == HASH_METADATA) {
QCryptographicHash hash(QCryptographicHash::Md5);
if (streamer) {
const QVector<MetaField>& localFields = streamer->getMetaFields();
if (fieldCount != localFields.size()) {
reader = TypeReader(typeName, streamer, false, fields);
return *this;
}
if (fieldCount == 0) {
reader = TypeReader(typeName, streamer);
return *this;
}
for (int i = 0; i < fieldCount; i++) {
const MetaField& localField = localFields.at(i);
if (!fields.at(i).getReader().matchesExactly(localField.getStreamer())) {
reader = TypeReader(typeName, streamer, false, fields);
return *this;
}
hash.addData(localField.getName().constData(), localField.getName().size() + 1);
}
}
QByteArray localHashResult = hash.result();
QByteArray remoteHashResult(localHashResult.size(), 0);
read(remoteHashResult.data(), remoteHashResult.size() * BITS_IN_BYTE);
if (streamer && localHashResult == remoteHashResult) {
// since everything is the same, we can use the default streamer
reader = TypeReader(typeName, streamer);
return *this;
}
} else if (streamer) {
// if all fields are the same type and in the right order, we can use the (more efficient) default streamer
const QVector<MetaField>& localFields = streamer->getMetaFields();
if (fieldCount != localFields.size()) {
reader = TypeReader(typeName, streamer, false, fields);
return *this;
}
for (int i = 0; i < fieldCount; i++) {
const FieldReader& fieldReader = fields.at(i);
if (!fieldReader.getReader().matchesExactly(localFields.at(i).getStreamer()) || fieldReader.getIndex() != i) {
reader = TypeReader(typeName, streamer, false, fields);
return *this;
}
}
reader = TypeReader(typeName, streamer);
return *this;
}
reader = TypeReader(typeName, streamer, false, fields);
return *this;
}
@ -650,12 +750,51 @@ QVector<PropertyReader> Bitstream::getPropertyReaders(const QMetaObject* metaObj
}
const TypeStreamer* typeStreamer = getTypeStreamers().value(property.userType());
if (typeStreamer) {
propertyReaders.append(PropertyReader(typeStreamer, property));
propertyReaders.append(PropertyReader(TypeReader(QByteArray(), typeStreamer), property));
}
}
return propertyReaders;
}
TypeReader::TypeReader(const QByteArray& typeName, const TypeStreamer* streamer,
bool exactMatch, const QVector<FieldReader>& fields) :
_typeName(typeName),
_streamer(streamer),
_exactMatch(exactMatch),
_fields(fields) {
}
QVariant TypeReader::read(Bitstream& in) const {
if (_exactMatch) {
return _streamer->read(in);
}
QVariant object = _streamer ? QVariant(_streamer->getType(), 0) : QVariant();
foreach (const FieldReader& field, _fields) {
field.read(in, _streamer, object);
}
return object;
}
bool TypeReader::matchesExactly(const TypeStreamer* streamer) const {
return _exactMatch && _streamer == streamer;
}
uint qHash(const TypeReader& typeReader, uint seed) {
return qHash(typeReader.getTypeName(), seed);
}
FieldReader::FieldReader(const TypeReader& reader, int index) :
_reader(reader),
_index(index) {
}
void FieldReader::read(Bitstream& in, const TypeStreamer* streamer, QVariant& object) const {
QVariant value = _reader.read(in);
if (_index != -1 && streamer) {
streamer->setField(_index, object, value);
}
}
ObjectReader::ObjectReader(const QByteArray& className, const QMetaObject* metaObject,
const QVector<PropertyReader>& properties) :
_className(className),
@ -677,14 +816,35 @@ uint qHash(const ObjectReader& objectReader, uint seed) {
return qHash(objectReader.getClassName(), seed);
}
PropertyReader::PropertyReader(const TypeStreamer* streamer, const QMetaProperty& property) :
_streamer(streamer),
PropertyReader::PropertyReader(const TypeReader& reader, const QMetaProperty& property) :
_reader(reader),
_property(property) {
}
void PropertyReader::read(Bitstream& in, QObject* object) const {
QVariant value = _streamer->read(in);
QVariant value = _reader.read(in);
if (_property.isValid() && object) {
_property.write(object, value);
}
}
MetaField::MetaField(const QByteArray& name, const TypeStreamer* streamer) :
_name(name),
_streamer(streamer) {
}
TypeStreamer::~TypeStreamer() {
}
const QVector<MetaField>& TypeStreamer::getMetaFields() const {
static QVector<MetaField> emptyMetaFields;
return emptyMetaFields;
}
int TypeStreamer::getFieldIndex(const QByteArray& name) const {
return -1;
}
void TypeStreamer::setField(int index, QVariant& object, const QVariant& value) const {
// nothing by default
}

View file

@ -16,6 +16,7 @@
#include <QScriptString>
#include <QSharedPointer>
#include <QVariant>
#include <QVector>
#include <QtDebug>
#include <glm/glm.hpp>
@ -30,9 +31,11 @@ class QUrl;
class Attribute;
class AttributeValue;
class Bitstream;
class FieldReader;
class ObjectReader;
class OwnedAttributeValue;
class PropertyReader;
class TypeReader;
class TypeStreamer;
typedef SharedObjectPointerTemplate<Attribute> AttributePointer;
@ -191,7 +194,7 @@ public:
class ReadMappings {
public:
QHash<int, ObjectReader> metaObjectValues;
QHash<int, const TypeStreamer*> typeStreamerValues;
QHash<int, TypeReader> typeStreamerValues;
QHash<int, AttributePointer> attributeValues;
QHash<int, QScriptString> scriptStringValues;
QHash<int, SharedObjectPointer> sharedObjectValues;
@ -205,6 +208,9 @@ public:
/// \return zero; the function only returns a value so that it can be used in static initialization
static int registerTypeStreamer(int type, TypeStreamer* streamer);
/// Returns the streamer registered for the supplied type, if any.
static const TypeStreamer* getTypeStreamer(int type);
/// Returns the meta-object registered under the supplied class name, if any.
static const QMetaObject* getMetaObject(const QByteArray& className);
@ -307,6 +313,7 @@ public:
Bitstream& operator<<(const TypeStreamer* streamer);
Bitstream& operator>>(const TypeStreamer*& streamer);
Bitstream& operator>>(TypeReader& reader);
Bitstream& operator<<(const AttributePointer& attribute);
Bitstream& operator>>(AttributePointer& attribute);
@ -321,7 +328,7 @@ public:
Bitstream& operator>(ObjectReader& objectReader);
Bitstream& operator<(const TypeStreamer* streamer);
Bitstream& operator>(const TypeStreamer*& streamer);
Bitstream& operator>(TypeReader& reader);
Bitstream& operator<(const AttributePointer& attribute);
Bitstream& operator>(AttributePointer& attribute);
@ -349,7 +356,7 @@ private:
MetadataType _metadataType;
RepeatedValueStreamer<const QMetaObject*, const QMetaObject*, ObjectReader> _metaObjectStreamer;
RepeatedValueStreamer<const TypeStreamer*> _typeStreamerStreamer;
RepeatedValueStreamer<const TypeStreamer*, const TypeStreamer*, TypeReader> _typeStreamerStreamer;
RepeatedValueStreamer<AttributePointer> _attributeStreamer;
RepeatedValueStreamer<QScriptString> _scriptStringStreamer;
RepeatedValueStreamer<SharedObjectPointer, SharedObject*> _sharedObjectStreamer;
@ -428,6 +435,50 @@ template<class K, class V> inline Bitstream& Bitstream::operator>>(QHash<K, V>&
return *this;
}
/// Contains the information required to read a type from the stream.
class TypeReader {
public:
TypeReader(const QByteArray& typeName = QByteArray(), const TypeStreamer* streamer = NULL,
bool exactMatch = true, const QVector<FieldReader>& fields = QVector<FieldReader>());
const QByteArray& getTypeName() const { return _typeName; }
const TypeStreamer* getStreamer() const { return _streamer; }
QVariant read(Bitstream& in) const;
bool matchesExactly(const TypeStreamer* streamer) const;
bool operator==(const TypeReader& other) const { return _typeName == other._typeName; }
bool operator!=(const TypeReader& other) const { return _typeName != other._typeName; }
private:
QByteArray _typeName;
const TypeStreamer* _streamer;
bool _exactMatch;
QVector<FieldReader> _fields;
};
uint qHash(const TypeReader& typeReader, uint seed = 0);
/// Contains the information required to read a metatype field from the stream and apply it.
class FieldReader {
public:
FieldReader(const TypeReader& reader = TypeReader(), int index = -1);
const TypeReader& getReader() const { return _reader; }
int getIndex() const { return _index; }
void read(Bitstream& in, const TypeStreamer* streamer, QVariant& object) const;
private:
TypeReader _reader;
int _index;
};
/// Contains the information required to read an object from the stream.
class ObjectReader {
public:
@ -438,8 +489,6 @@ public:
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; }
@ -458,18 +507,33 @@ uint qHash(const ObjectReader& objectReader, uint seed = 0);
class PropertyReader {
public:
PropertyReader(const TypeStreamer* streamer = NULL, const QMetaProperty& property = QMetaProperty());
PropertyReader(const TypeReader& reader = TypeReader(), const QMetaProperty& property = QMetaProperty());
const TypeStreamer* getStreamer() const { return _streamer; }
const TypeReader& getReader() const { return _reader; }
void read(Bitstream& in, QObject* object) const;
private:
const TypeStreamer* _streamer;
TypeReader _reader;
QMetaProperty _property;
};
/// Describes a metatype field.
class MetaField {
public:
MetaField(const QByteArray& name = QByteArray(), const TypeStreamer* streamer = NULL);
const QByteArray& getName() const { return _name; }
const TypeStreamer* getStreamer() const { return _streamer; }
private:
QByteArray _name;
const TypeStreamer* _streamer;
};
Q_DECLARE_METATYPE(const QMetaObject*)
/// Macro for registering streamable meta-objects.
@ -479,12 +543,18 @@ Q_DECLARE_METATYPE(const QMetaObject*)
class TypeStreamer {
public:
virtual ~TypeStreamer();
void setType(int type) { _type = type; }
int getType() const { return _type; }
virtual void write(Bitstream& out, const QVariant& value) const = 0;
virtual QVariant read(Bitstream& in) const = 0;
virtual const QVector<MetaField>& getMetaFields() const;
virtual int getFieldIndex(const QByteArray& name) const;
virtual void setField(int index, QVariant& object, const QVariant& value) const;
private:
int _type;
@ -498,6 +568,16 @@ public:
virtual QVariant read(Bitstream& in) const { T value; in >> value; return QVariant::fromValue(value); }
};
/// A streamer that works with Bitstream's operators.
template<class T> class CompoundTypeStreamer : public SimpleTypeStreamer<T> {
public:
virtual const QVector<MetaField>& getMetaFields() const { return T::getMetaFields(); }
virtual int getFieldIndex(const QByteArray& name) const { return T::getFieldIndex(name); }
virtual void setField(int index, QVariant& object, const QVariant& value) const {
static_cast<T*>(object.data())->setField(index, value); }
};
/// Macro for registering simple type streamers.
#define REGISTER_SIMPLE_TYPE_STREAMER(x) static int x##Streamer = \
Bitstream::registerTypeStreamer(QMetaType::type(#x), new SimpleTypeStreamer<x>());
@ -532,16 +612,18 @@ public:
/// Registers a streamable type and its streamer.
template<class T> int registerStreamableMetaType() {
int type = qRegisterMetaType<T>();
Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer<T>());
Bitstream::registerTypeStreamer(type, new CompoundTypeStreamer<T>());
return type;
}
/// Flags a class as streamable (use as you would Q_OBJECT).
#define STREAMABLE public: \
static const int Type; \
static int getFieldCount(); \
void setFieldValue(int index, const QVariant& value); \
private:
static const QVector<MetaField>& getMetaFields(); \
static int getFieldIndex(const QByteArray& name); \
void setField(int index, const QVariant& value); \
private: \
static QHash<QByteArray, int> createFieldIndices();
/// Flags a field or base class as streaming.
#define STREAM

View file

@ -100,21 +100,45 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n";
out << "int " << name << "::getFieldCount() {\n";
out << " return " << str.fields.size();
foreach(const QString& base, str.clazz.bases) {
out << " + " << base << "::getFieldCount()";
out << "const QVector<MetaField>& " << name << "::getMetaFields() {\n";
out << " static QVector<MetaField> metaFields = QVector<MetaField>()";
foreach (const QString& base, str.clazz.bases) {
out << " << " << base << "::getMetaFields()";
}
foreach (const Field& field, str.fields) {
out << "\n << MetaField(\"" << field.name << "\", Bitstream::getTypeStreamer(qMetaTypeId<" <<
field.type << ">()))";
}
out << ";\n";
out << " return metaFields;\n";
out << "}\n";
out << "void " << name << "::setFieldValue(int index, const QVariant& value) {\n";
out << "int " << name << "::getFieldIndex(const QByteArray& name) {\n";
out << " static QHash<QByteArray, int> fieldIndices = createFieldIndices();\n";
out << " return fieldIndices.value(name) - 1;\n";
out << "}\n";
out << "QHash<QByteArray, int> " << name << "::createFieldIndices() {\n";
out << " QHash<QByteArray, int> indices;\n";
out << " int index = 0;\n";
foreach (const QString& base, str.clazz.bases) {
out << " foreach (const MetaField& field, " << base << "::getMetaFields()) {\n";
out << " indices.insert(field.getName(), index++);\n";
out << " }\n";
}
out << " foreach (const MetaField& field, getMetaFields()) {\n";
out << " indices.insert(field.getName(), index++);\n";
out << " }\n";
out << " return indices;\n";
out << "}\n";
out << "void " << name << "::setField(int index, const QVariant& value) {\n";
if (!str.clazz.bases.isEmpty()) {
out << " int nextIndex;\n";
}
foreach (const QString& base, str.clazz.bases) {
out << " if ((nextIndex = index - " << base << "::getFieldCount()) < 0) {\n";
out << " " << base << "::setFieldValue(index, value);\n";
out << " if ((nextIndex = index - " << base << "::getMetaFields().size()) < 0) {\n";
out << " " << base << "::setField(index, value);\n";
out << " return;\n";
out << " }\n";
out << " index = nextIndex;\n";