mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 18:23:54 +02:00
Merge pull request #3038 from ey6es/metavoxels
Support for writing to and reading from a JSON format that mirrors the bitstream format, with tools to convert between them (using generics so as not to require the original classes).
This commit is contained in:
commit
71bbbac1c0
12 changed files with 1637 additions and 54 deletions
|
@ -49,4 +49,5 @@ add_subdirectory(assignment-client)
|
|||
add_subdirectory(domain-server)
|
||||
add_subdirectory(interface)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(voxel-edit)
|
||||
|
|
|
@ -9,13 +9,9 @@
|
|||
#
|
||||
|
||||
macro(AUTO_MTC TARGET ROOT_DIR)
|
||||
if (NOT TARGET mtc)
|
||||
add_subdirectory("${ROOT_DIR}/tools/mtc" "${ROOT_DIR}/tools/mtc")
|
||||
endif ()
|
||||
|
||||
set(AUTOMTC_SRC ${TARGET}_automtc.cpp)
|
||||
|
||||
file(GLOB INCLUDE_FILES src/*.h)
|
||||
|
||||
add_custom_command(OUTPUT ${AUTOMTC_SRC} COMMAND mtc -o ${AUTOMTC_SRC} ${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
|
||||
endmacro()
|
||||
endmacro()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -13,6 +13,9 @@
|
|||
#define hifi_Bitstream_h
|
||||
|
||||
#include <QHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMetaProperty>
|
||||
#include <QMetaType>
|
||||
#include <QPointer>
|
||||
|
@ -36,6 +39,7 @@ class Attribute;
|
|||
class AttributeValue;
|
||||
class Bitstream;
|
||||
class GenericValue;
|
||||
class JSONWriter;
|
||||
class ObjectReader;
|
||||
class ObjectStreamer;
|
||||
class OwnedAttributeValue;
|
||||
|
@ -195,12 +199,50 @@ template<class K, class P, class V> inline RepeatedValueStreamer<K, P, V>&
|
|||
return *this;
|
||||
}
|
||||
|
||||
/// A stream for bit-aligned data.
|
||||
/// A stream for bit-aligned data. Through a combination of code generation, reflection, macros, and templates, provides a
|
||||
/// serialization mechanism that may be used for both networking and persistent storage. For unreliable networking, the
|
||||
/// class provides a mapping system that resends mappings for ids until they are acknowledged (and thus persisted). For
|
||||
/// version-resilient persistence, the class provides a metadata system that maps stored types to local types (or to
|
||||
/// generic containers), allowing one to add and remove fields to classes without breaking compatibility with previously
|
||||
/// stored data.
|
||||
///
|
||||
/// The basic usage requires one to create a Bitstream that wraps an underlying QDataStream, specifying the metadata type
|
||||
/// desired and (for readers) the generics mode. Then, one uses the << or >> operators to write or read values to/from
|
||||
/// the stream (a stream instance may be used for reading or writing, but not both). For write streams, the flush
|
||||
/// function should be called on completion to write any partial data.
|
||||
///
|
||||
/// Polymorphic types are supported via the QVariant and QObject*/SharedObjectPointer types. When you write a QVariant or
|
||||
/// QObject, the type or class name (at minimum) is written to the stream. When you read a QVariant or QObject, the default
|
||||
/// behavior is to look for a corresponding registered type with the written name. With hash metadata, Bitstream can verify
|
||||
/// that the local type matches the stream type, throwing the streamed version away if not. With full metadata, Bitstream can
|
||||
/// create a mapping from the streamed type to the local type that applies the intersection of the two types' fields.
|
||||
///
|
||||
/// To register types for streaming, select from the provided templates and macros. To register a QObject (or SharedObject)
|
||||
/// subclass, use REGISTER_META_OBJECT in the class's source file. To register a streamable class for use in QVariant, use
|
||||
/// the STREAMABLE/STREAM/DECLARE_STREAMABLE_METATYPE macros and use the mtc tool to generate the associated implementation
|
||||
/// code. To register a QObject enum for use outside the QObject's direct properties, use the
|
||||
/// DECLARE_ENUM_METATYPE/IMPLEMENT_ENUM_METATYPE macro pair. To register a collection type (QList, QVector, QSet, QMap) of
|
||||
/// streamable types, use the registerCollectionMetaType template function.
|
||||
///
|
||||
/// Delta-streaming is supported through the writeDelta/readDelta functions (analogous to <</>>), which accept a reference
|
||||
/// value and stream only the information necessary to turn the reference into the target value. This assumes that the
|
||||
/// reference value provided when reading is identical to the one used when writing.
|
||||
///
|
||||
/// Special delta handling is provided for objects tracked by SharedObjectPointers. SharedObjects have IDs (representing their
|
||||
/// unique identity on one system) as well as origin IDs (representing their identity as preserved over the course of
|
||||
/// mutation). Once a shared object with a given ID is written (and its mapping persisted), that object's state will not be
|
||||
/// written again; only its ID will be sent. However, if an object with a new ID but the same origin ID is written, that
|
||||
/// object's state will be encoded as a delta between the previous object and the new one. So, to transmit delta-encoded
|
||||
/// objects, one should treat each local SharedObject instance as immutable, replacing it when mutated with a cloned instance
|
||||
/// generated by calling SharedObject::clone(true) and applying the desired changes.
|
||||
class Bitstream : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Stores a set of mappings from values to ids written. Typically, one would store these mappings along with the send
|
||||
/// record of the packet that contained them, persisting the mappings if/when the packet is acknowledged by the remote
|
||||
/// party.
|
||||
class WriteMappings {
|
||||
public:
|
||||
QHash<const ObjectStreamer*, int> objectStreamerOffsets;
|
||||
|
@ -210,6 +252,9 @@ public:
|
|||
QHash<SharedObjectPointer, int> sharedObjectOffsets;
|
||||
};
|
||||
|
||||
/// Stores a set of mappings from ids to values read. Typically, one would store these mappings along with the receive
|
||||
/// record of the packet that contained them, persisting the mappings if/when the remote party indicates that it
|
||||
/// has received the local party's acknowledgement of the packet.
|
||||
class ReadMappings {
|
||||
public:
|
||||
QHash<int, ObjectStreamerPointer> objectStreamerValues;
|
||||
|
@ -219,11 +264,14 @@ public:
|
|||
QHash<int, SharedObjectPointer> sharedObjectValues;
|
||||
};
|
||||
|
||||
/// Registers a metaobject under its name so that instances of it can be streamed.
|
||||
/// Registers a metaobject under its name so that instances of it can be streamed. Consider using the REGISTER_META_OBJECT
|
||||
/// at the top level of the source file associated with the class rather than calling this function directly.
|
||||
/// \return zero; the function only returns a value so that it can be used in static initialization
|
||||
static int registerMetaObject(const char* className, const QMetaObject* metaObject);
|
||||
|
||||
/// Registers a streamer for the specified Qt-registered type.
|
||||
/// Registers a streamer for the specified Qt-registered type. Consider using one of the registration macros (such as
|
||||
/// REGISTER_SIMPLE_TYPE_STREAMER) at the top level of the associated source file rather than calling this function
|
||||
/// directly.
|
||||
/// \return zero; the function only returns a value so that it can be used in static initialization
|
||||
static int registerTypeStreamer(int type, TypeStreamer* streamer);
|
||||
|
||||
|
@ -233,7 +281,9 @@ public:
|
|||
/// Returns the meta-object registered under the supplied class name, if any.
|
||||
static const QMetaObject* getMetaObject(const QByteArray& className);
|
||||
|
||||
/// Returns the list of registered subclasses for the supplied meta-object.
|
||||
/// Returns the list of registered subclasses for the supplied meta-object. When registered, metaobjects register
|
||||
/// themselves as subclasses of all of their parents, mostly in order to allow editors to provide lists of available
|
||||
/// subclasses.
|
||||
static QList<const QMetaObject*> getMetaObjectSubClasses(const QMetaObject* metaObject);
|
||||
|
||||
enum MetadataType { NO_METADATA, HASH_METADATA, FULL_METADATA };
|
||||
|
@ -250,7 +300,8 @@ public:
|
|||
Bitstream(QDataStream& underlying, MetadataType metadataType = NO_METADATA,
|
||||
GenericsMode = NO_GENERICS, QObject* parent = NULL);
|
||||
|
||||
/// Substitutes the supplied metaobject for the given class name's default mapping.
|
||||
/// Substitutes the supplied metaobject for the given class name's default mapping. This is mostly useful for testing the
|
||||
/// process of mapping between different types, but may in the future be used for permanently renaming classes.
|
||||
void addMetaObjectSubstitution(const QByteArray& className, const QMetaObject* metaObject);
|
||||
|
||||
/// Substitutes the supplied type for the given type name's default mapping.
|
||||
|
@ -445,6 +496,9 @@ private slots:
|
|||
|
||||
private:
|
||||
|
||||
friend class JSONReader;
|
||||
friend class JSONWriter;
|
||||
|
||||
ObjectStreamerPointer readGenericObjectStreamer(const QByteArray& name);
|
||||
TypeStreamerPointer readGenericTypeStreamer(const QByteArray& name, int category);
|
||||
|
||||
|
@ -471,7 +525,7 @@ private:
|
|||
static QHash<QByteArray, const QMetaObject*>& getMetaObjects();
|
||||
static QMultiHash<const QMetaObject*, const QMetaObject*>& getMetaObjectSubClasses();
|
||||
static QHash<int, const TypeStreamer*>& getTypeStreamers();
|
||||
|
||||
|
||||
static const QHash<const QMetaObject*, const ObjectStreamer*>& getObjectStreamers();
|
||||
static QHash<const QMetaObject*, const ObjectStreamer*> createObjectStreamers();
|
||||
|
||||
|
@ -766,6 +820,189 @@ template<class K, class V> inline Bitstream& Bitstream::operator>>(QHash<K, V>&
|
|||
return *this;
|
||||
}
|
||||
|
||||
/// Provides a means of writing Bitstream-able data to JSON rather than the usual binary format in a manner that allows it to
|
||||
/// be manipulated and re-read, converted to binary, etc. To use, create a JSONWriter, stream values in using the << operator,
|
||||
/// and call getDocument to obtain the JSON data.
|
||||
class JSONWriter {
|
||||
public:
|
||||
|
||||
QJsonValue getData(bool value);
|
||||
QJsonValue getData(int value);
|
||||
QJsonValue getData(uint value);
|
||||
QJsonValue getData(float value);
|
||||
QJsonValue getData(const QByteArray& value);
|
||||
QJsonValue getData(const QColor& value);
|
||||
QJsonValue getData(const QScriptValue& value);
|
||||
QJsonValue getData(const QString& value);
|
||||
QJsonValue getData(const QUrl& value);
|
||||
QJsonValue getData(const QDateTime& value);
|
||||
QJsonValue getData(const QRegExp& value);
|
||||
QJsonValue getData(const glm::vec3& value);
|
||||
QJsonValue getData(const glm::quat& value);
|
||||
QJsonValue getData(const QMetaObject* value);
|
||||
QJsonValue getData(const QVariant& value);
|
||||
QJsonValue getData(const SharedObjectPointer& value);
|
||||
QJsonValue getData(const QObject* value);
|
||||
QJsonValue getData(const GenericValue& value);
|
||||
|
||||
template<class T> QJsonValue getData(const T& value) { return QJsonValue(); }
|
||||
|
||||
template<class T> QJsonValue getData(const QList<T>& list);
|
||||
template<class T> QJsonValue getData(const QVector<T>& list);
|
||||
template<class T> QJsonValue getData(const QSet<T>& set);
|
||||
template<class K, class V> QJsonValue getData(const QHash<K, V>& hash);
|
||||
|
||||
template<class T> JSONWriter& operator<<(const T& value) { _contents.append(getData(value)); return *this; }
|
||||
|
||||
void appendToContents(const QJsonValue& value) { _contents.append(value); }
|
||||
|
||||
void addSharedObject(const SharedObjectPointer& object);
|
||||
void addObjectStreamer(const ObjectStreamer* streamer);
|
||||
void addTypeStreamer(const TypeStreamer* streamer);
|
||||
|
||||
QJsonDocument getDocument() const;
|
||||
|
||||
private:
|
||||
|
||||
QJsonArray _contents;
|
||||
|
||||
QSet<int> _sharedObjectIDs;
|
||||
QJsonArray _sharedObjects;
|
||||
|
||||
QSet<QByteArray> _objectStreamerNames;
|
||||
QJsonArray _objectStreamers;
|
||||
|
||||
QSet<QByteArray> _typeStreamerNames;
|
||||
QJsonArray _typeStreamers;
|
||||
};
|
||||
|
||||
template<class T> inline QJsonValue JSONWriter::getData(const QList<T>& list) {
|
||||
QJsonArray array;
|
||||
foreach (const T& value, list) {
|
||||
array.append(getData(value));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
template<class T> inline QJsonValue JSONWriter::getData(const QVector<T>& vector) {
|
||||
QJsonArray array;
|
||||
foreach (const T& value, vector) {
|
||||
array.append(getData(value));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
template<class T> inline QJsonValue JSONWriter::getData(const QSet<T>& set) {
|
||||
QJsonArray array;
|
||||
foreach (const T& value, set) {
|
||||
array.append(getData(value));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
template<class K, class V> inline QJsonValue JSONWriter::getData(const QHash<K, V>& hash) {
|
||||
QJsonArray array;
|
||||
for (typename QHash<K, V>::const_iterator it = hash.constBegin(); it != hash.constEnd(); it++) {
|
||||
QJsonArray pair;
|
||||
pair.append(getData(it.key()));
|
||||
pair.append(getData(it.value()));
|
||||
array.append(pair);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/// Reads a document written by JSONWriter. To use, create a JSONReader and stream values out using the >> operator.
|
||||
class JSONReader {
|
||||
public:
|
||||
|
||||
/// Creates a reader to read from the supplied document.
|
||||
/// \param genericsMode the generics mode to use: NO_GENERICS to map all types in the document to built-in types,
|
||||
/// FALLBACK_GENERICS to use generic containers where no matching built-in type is found, or ALL_GENERICS to
|
||||
/// read all types to generic containers
|
||||
JSONReader(const QJsonDocument& document, Bitstream::GenericsMode genericsMode = Bitstream::NO_GENERICS);
|
||||
|
||||
void putData(const QJsonValue& data, bool& value);
|
||||
void putData(const QJsonValue& data, int& value);
|
||||
void putData(const QJsonValue& data, uint& value);
|
||||
void putData(const QJsonValue& data, float& value);
|
||||
void putData(const QJsonValue& data, QByteArray& value);
|
||||
void putData(const QJsonValue& data, QColor& value);
|
||||
void putData(const QJsonValue& data, QScriptValue& value);
|
||||
void putData(const QJsonValue& data, QString& value);
|
||||
void putData(const QJsonValue& data, QUrl& value);
|
||||
void putData(const QJsonValue& data, QDateTime& value);
|
||||
void putData(const QJsonValue& data, QRegExp& value);
|
||||
void putData(const QJsonValue& data, glm::vec3& value);
|
||||
void putData(const QJsonValue& data, glm::quat& value);
|
||||
void putData(const QJsonValue& data, const QMetaObject*& value);
|
||||
void putData(const QJsonValue& data, QVariant& value);
|
||||
void putData(const QJsonValue& data, SharedObjectPointer& value);
|
||||
void putData(const QJsonValue& data, QObject*& value);
|
||||
|
||||
template<class T> void putData(const QJsonValue& data, T& value) { value = T(); }
|
||||
|
||||
template<class T> void putData(const QJsonValue& data, QList<T>& list);
|
||||
template<class T> void putData(const QJsonValue& data, QVector<T>& list);
|
||||
template<class T> void putData(const QJsonValue& data, QSet<T>& set);
|
||||
template<class K, class V> void putData(const QJsonValue& data, QHash<K, V>& hash);
|
||||
|
||||
template<class T> JSONReader& operator>>(T& value) { putData(*_contentsIterator++, value); return *this; }
|
||||
|
||||
QJsonValue retrieveNextFromContents() { return *_contentsIterator++; }
|
||||
|
||||
TypeStreamerPointer getTypeStreamer(const QString& name) const;
|
||||
ObjectStreamerPointer getObjectStreamer(const QString& name) const { return _objectStreamers.value(name); }
|
||||
SharedObjectPointer getSharedObject(int id) const { return _sharedObjects.value(id); }
|
||||
|
||||
private:
|
||||
|
||||
QJsonArray _contents;
|
||||
QJsonArray::const_iterator _contentsIterator;
|
||||
|
||||
QHash<QString, TypeStreamerPointer> _typeStreamers;
|
||||
QHash<QString, ObjectStreamerPointer> _objectStreamers;
|
||||
QHash<int, SharedObjectPointer> _sharedObjects;
|
||||
};
|
||||
|
||||
template<class T> inline void JSONReader::putData(const QJsonValue& data, QList<T>& list) {
|
||||
list.clear();
|
||||
foreach (const QJsonValue& element, data.toArray()) {
|
||||
T value;
|
||||
putData(element, value);
|
||||
list.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void JSONReader::putData(const QJsonValue& data, QVector<T>& list) {
|
||||
list.clear();
|
||||
foreach (const QJsonValue& element, data.toArray()) {
|
||||
T value;
|
||||
putData(element, value);
|
||||
list.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T> inline void JSONReader::putData(const QJsonValue& data, QSet<T>& set) {
|
||||
set.clear();
|
||||
foreach (const QJsonValue& element, data.toArray()) {
|
||||
T value;
|
||||
putData(element, value);
|
||||
set.insert(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<class K, class V> inline void JSONReader::putData(const QJsonValue& data, QHash<K, V>& hash) {
|
||||
hash.clear();
|
||||
foreach (const QJsonValue& element, data.toArray()) {
|
||||
QJsonArray pair = element.toArray();
|
||||
K key;
|
||||
putData(pair.at(0), key);
|
||||
V value;
|
||||
putData(pair.at(1), value);
|
||||
hash.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
typedef QPair<TypeStreamerPointer, QMetaProperty> StreamerPropertyPair;
|
||||
|
||||
/// Contains the information required to stream an object.
|
||||
|
@ -781,6 +1018,10 @@ public:
|
|||
virtual const char* getName() const = 0;
|
||||
virtual const QVector<StreamerPropertyPair>& getProperties() const;
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const = 0;
|
||||
virtual QJsonObject getJSONMetadata(JSONWriter& writer) const = 0;
|
||||
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const = 0;
|
||||
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const = 0;
|
||||
|
||||
virtual void write(Bitstream& out, const QObject* object) const = 0;
|
||||
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const = 0;
|
||||
virtual QObject* read(Bitstream& in, QObject* object = NULL) const = 0;
|
||||
|
@ -791,7 +1032,7 @@ protected:
|
|||
friend class Bitstream;
|
||||
|
||||
const QMetaObject* _metaObject;
|
||||
ObjectStreamerPointer _self;
|
||||
ObjectStreamerPointer _self; ///< set/used for built-in classes (never deleted), to obtain shared pointers
|
||||
};
|
||||
|
||||
/// A streamer that maps to a local class.
|
||||
|
@ -803,6 +1044,9 @@ public:
|
|||
virtual const char* getName() const;
|
||||
virtual const QVector<StreamerPropertyPair>& getProperties() const;
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
|
||||
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
|
||||
virtual void write(Bitstream& out, const QObject* object) const;
|
||||
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
|
||||
virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
|
||||
|
@ -823,6 +1067,9 @@ public:
|
|||
|
||||
virtual const char* getName() const;
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
virtual QJsonObject getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonObject getJSONData(JSONWriter& writer, const QObject* object) const;
|
||||
virtual QObject* putJSONData(JSONReader& reader, const QJsonObject& jsonObject) const;
|
||||
virtual void write(Bitstream& out, const QObject* object) const;
|
||||
virtual void writeRawDelta(Bitstream& out, const QObject* object, const QObject* reference) const;
|
||||
virtual QObject* read(Bitstream& in, QObject* object = NULL) const;
|
||||
|
@ -831,9 +1078,10 @@ public:
|
|||
private:
|
||||
|
||||
friend class Bitstream;
|
||||
friend class JSONReader;
|
||||
|
||||
QByteArray _name;
|
||||
WeakObjectStreamerPointer _weakSelf;
|
||||
WeakObjectStreamerPointer _weakSelf; ///< promoted to strong references in GenericSharedObject when reading
|
||||
QVector<StreamerNamePair> _properties;
|
||||
QByteArray _hash;
|
||||
};
|
||||
|
@ -855,10 +1103,12 @@ private:
|
|||
|
||||
Q_DECLARE_METATYPE(const QMetaObject*)
|
||||
|
||||
/// Macro for registering streamable meta-objects.
|
||||
/// Macro for registering streamable meta-objects. Typically, one would use this macro at the top level of the source file
|
||||
/// associated with the class.
|
||||
#define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject);
|
||||
|
||||
/// Contains a value along with a pointer to its streamer.
|
||||
/// Contains a value along with a pointer to its streamer. This is stored in QVariants when using fallback generics and
|
||||
/// no mapping to a built-in type can be found, or when using all generics and the value is any non-simple type.
|
||||
class GenericValue {
|
||||
public:
|
||||
|
||||
|
@ -877,7 +1127,8 @@ private:
|
|||
|
||||
Q_DECLARE_METATYPE(GenericValue)
|
||||
|
||||
/// Contains a list of property values along with a pointer to their metadata.
|
||||
/// Contains a list of property values along with a pointer to their metadata. This is stored when using fallback generics
|
||||
/// and no mapping to a built-in class can be found, or for all QObjects when using all generics.
|
||||
class GenericSharedObject : public SharedObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -914,6 +1165,12 @@ public:
|
|||
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
|
||||
virtual QJsonValue getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonValue getJSONData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual QJsonValue getJSONVariantData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual void putJSONVariantData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
|
||||
virtual bool equal(const QVariant& first, const QVariant& second) const;
|
||||
|
||||
virtual void write(Bitstream& out, const QVariant& value) const;
|
||||
|
@ -958,7 +1215,7 @@ protected:
|
|||
friend class Bitstream;
|
||||
|
||||
int _type;
|
||||
TypeStreamerPointer _self;
|
||||
TypeStreamerPointer _self; ///< set/used for built-in types (never deleted), to obtain shared pointers
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const TypeStreamer* typeStreamer);
|
||||
|
@ -969,6 +1226,10 @@ QDebug& operator<<(QDebug& debug, const QMetaObject* metaObject);
|
|||
template<class T> class SimpleTypeStreamer : public TypeStreamer {
|
||||
public:
|
||||
|
||||
virtual QJsonValue getJSONData(JSONWriter& writer, const QVariant& value) const {
|
||||
return writer.getData(value.value<T>()); }
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const {
|
||||
T rawValue; reader.putData(data, rawValue); value = QVariant::fromValue(rawValue); }
|
||||
virtual bool equal(const QVariant& first, const QVariant& second) const { return first.value<T>() == second.value<T>(); }
|
||||
virtual void write(Bitstream& out, const QVariant& value) const { out << value.value<T>(); }
|
||||
virtual QVariant read(Bitstream& in) const { T value; in >> value; return QVariant::fromValue(value); }
|
||||
|
@ -991,6 +1252,9 @@ public:
|
|||
|
||||
virtual const char* getName() const;
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
virtual QJsonValue getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonValue getJSONData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual Category getCategory() const;
|
||||
virtual int getBits() const;
|
||||
virtual QMetaEnum getMetaEnum() const;
|
||||
|
@ -1018,6 +1282,7 @@ public:
|
|||
|
||||
MappedEnumTypeStreamer(const TypeStreamer* baseStreamer, int bits, const QHash<int, int>& mappings);
|
||||
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual void readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const;
|
||||
|
||||
|
@ -1035,14 +1300,16 @@ public:
|
|||
GenericTypeStreamer(const QByteArray& name);
|
||||
|
||||
virtual const char* getName() const;
|
||||
virtual void putJSONVariantData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual QVariant readVariant(Bitstream& in) const;
|
||||
|
||||
protected:
|
||||
|
||||
friend class Bitstream;
|
||||
friend class JSONReader;
|
||||
|
||||
QByteArray _name;
|
||||
WeakTypeStreamerPointer _weakSelf;
|
||||
WeakTypeStreamerPointer _weakSelf; ///< promoted to strong references in GenericValue when reading
|
||||
};
|
||||
|
||||
/// A streamer for generic enums.
|
||||
|
@ -1052,6 +1319,9 @@ public:
|
|||
GenericEnumTypeStreamer(const QByteArray& name, const QVector<NameIntPair>& values, int bits, const QByteArray& hash);
|
||||
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
virtual QJsonValue getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonValue getJSONData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual void write(Bitstream& out, const QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual Category getCategory() const;
|
||||
|
@ -1084,6 +1354,7 @@ public:
|
|||
|
||||
MappedStreamableTypeStreamer(const TypeStreamer* baseStreamer, const QVector<StreamerIndexPair>& fields);
|
||||
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual void readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const;
|
||||
|
||||
|
@ -1100,6 +1371,9 @@ public:
|
|||
GenericStreamableTypeStreamer(const QByteArray& name, const QVector<StreamerNamePair>& fields, const QByteArray& hash);
|
||||
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
virtual QJsonValue getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonValue getJSONData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual void write(Bitstream& out, const QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual Category getCategory() const;
|
||||
|
@ -1154,6 +1428,7 @@ public:
|
|||
|
||||
MappedListTypeStreamer(const TypeStreamer* baseStreamer, const TypeStreamerPointer& valueStreamer);
|
||||
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual void readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const;
|
||||
|
||||
|
@ -1170,11 +1445,14 @@ public:
|
|||
GenericListTypeStreamer(const QByteArray& name, const TypeStreamerPointer& valueStreamer);
|
||||
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
virtual QJsonValue getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonValue getJSONData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual void write(Bitstream& out, const QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual Category getCategory() const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
TypeStreamerPointer _valueStreamer;
|
||||
};
|
||||
|
@ -1207,6 +1485,7 @@ public:
|
|||
|
||||
GenericSetTypeStreamer(const QByteArray& name, const TypeStreamerPointer& valueStreamer);
|
||||
|
||||
virtual QJsonValue getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual Category getCategory() const;
|
||||
};
|
||||
|
||||
|
@ -1233,6 +1512,7 @@ public:
|
|||
MappedMapTypeStreamer(const TypeStreamer* baseStreamer, const TypeStreamerPointer& keyStreamer,
|
||||
const TypeStreamerPointer& valueStreamer);
|
||||
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual void readRawDelta(Bitstream& in, QVariant& object, const QVariant& reference) const;
|
||||
|
||||
|
@ -1251,6 +1531,9 @@ public:
|
|||
const TypeStreamerPointer& valueStreamer);
|
||||
|
||||
virtual void writeMetadata(Bitstream& out, bool full) const;
|
||||
virtual QJsonValue getJSONMetadata(JSONWriter& writer) const;
|
||||
virtual QJsonValue getJSONData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual void putJSONData(JSONReader& reader, const QJsonValue& data, QVariant& value) const;
|
||||
virtual void write(Bitstream& out, const QVariant& value) const;
|
||||
virtual QVariant read(Bitstream& in) const;
|
||||
virtual Category getCategory() const;
|
||||
|
@ -1265,25 +1548,31 @@ private:
|
|||
class GenericValueStreamer : public SimpleTypeStreamer<GenericValue> {
|
||||
public:
|
||||
|
||||
QJsonValue getJSONVariantData(JSONWriter& writer, const QVariant& value) const;
|
||||
virtual void writeVariant(Bitstream& out, const QVariant& value) const;
|
||||
};
|
||||
|
||||
/// Macro for registering simple type streamers.
|
||||
/// Macro for registering simple type streamers. Typically, one would use this at the top level of the source file
|
||||
/// associated with the type.
|
||||
#define REGISTER_SIMPLE_TYPE_STREAMER(X) static int X##Streamer = \
|
||||
Bitstream::registerTypeStreamer(qMetaTypeId<X>(), new SimpleTypeStreamer<X>());
|
||||
|
||||
/// Macro for registering collection type streamers.
|
||||
/// Macro for registering collection type (QList, QVector, QSet, QMap) streamers. Typically, one would use this at the top
|
||||
/// level of the source file associated with the type.
|
||||
#define REGISTER_COLLECTION_TYPE_STREAMER(X) static int x##Streamer = \
|
||||
Bitstream::registerTypeStreamer(qMetaTypeId<X>(), new CollectionTypeStreamer<X>());
|
||||
|
||||
/// Declares the metatype and the streaming operators. The last lines
|
||||
/// ensure that the generated file will be included in the link phase.
|
||||
/// Declares the metatype and the streaming operators. Typically, one would use this immediately after the definition of a
|
||||
/// type flagged as STREAMABLE in its header file. The last lines ensure that the generated file will be included in the link
|
||||
/// phase.
|
||||
#ifdef _WIN32
|
||||
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
|
||||
Bitstream& operator<<(Bitstream& out, const X& obj); \
|
||||
Bitstream& operator>>(Bitstream& in, X& obj); \
|
||||
template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \
|
||||
template<> void Bitstream::readRawDelta(X& value, const X& reference); \
|
||||
template<> QJsonValue JSONWriter::getData(const X& value); \
|
||||
template<> void JSONReader::putData(const QJsonValue& data, X& value); \
|
||||
bool operator==(const X& first, const X& second); \
|
||||
bool operator!=(const X& first, const X& second); \
|
||||
static const int* _TypePtr##X = &X::Type;
|
||||
|
@ -1293,6 +1582,8 @@ public:
|
|||
Bitstream& operator>>(Bitstream& in, X& obj); \
|
||||
template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \
|
||||
template<> void Bitstream::readRawDelta(X& value, const X& reference); \
|
||||
template<> QJsonValue JSONWriter::getData(const X& value); \
|
||||
template<> void JSONReader::putData(const QJsonValue& data, X& value); \
|
||||
bool operator==(const X& first, const X& second); \
|
||||
bool operator!=(const X& first, const X& second); \
|
||||
__attribute__((unused)) static const int* _TypePtr##X = &X::Type;
|
||||
|
@ -1303,18 +1594,27 @@ public:
|
|||
Bitstream& operator>>(Bitstream& in, X& obj); \
|
||||
template<> void Bitstream::writeRawDelta(const X& value, const X& reference); \
|
||||
template<> void Bitstream::readRawDelta(X& value, const X& reference); \
|
||||
template<> QJsonValue JSONWriter::getData(const X& value); \
|
||||
template<> void JSONReader::putData(const QJsonValue& data, X& value); \
|
||||
bool operator==(const X& first, const X& second); \
|
||||
bool operator!=(const X& first, const X& second); \
|
||||
static const int* _TypePtr##X = &X::Type; \
|
||||
_Pragma(STRINGIFY(unused(_TypePtr##X)))
|
||||
#endif
|
||||
|
||||
/// Declares an enum metatype. This is used when one desires to use an enum defined in a QObject outside of that class's
|
||||
/// direct properties. Typically, one would use this immediately after the definition of the QObject containing the enum
|
||||
/// in its header file.
|
||||
#define DECLARE_ENUM_METATYPE(S, N) Q_DECLARE_METATYPE(S::N) \
|
||||
Bitstream& operator<<(Bitstream& out, const S::N& obj); \
|
||||
Bitstream& operator>>(Bitstream& in, S::N& obj); \
|
||||
template<> inline void Bitstream::writeRawDelta(const S::N& value, const S::N& reference) { *this << value; } \
|
||||
template<> inline void Bitstream::readRawDelta(S::N& value, const S::N& reference) { *this >> value; }
|
||||
template<> inline void Bitstream::readRawDelta(S::N& value, const S::N& reference) { *this >> value; } \
|
||||
template<> inline QJsonValue JSONWriter::getData(const S::N& value) { return (int)value; } \
|
||||
template<> inline void JSONReader::putData(const QJsonValue& data, S::N& value) { value = (S::N)data.toInt(); }
|
||||
|
||||
/// Implements an enum metatype. This performs the implementation of the previous macro, and would normally be used at the
|
||||
/// top level of the source file associated with the QObject containing the enum.
|
||||
#define IMPLEMENT_ENUM_METATYPE(S, N) \
|
||||
static int S##N##MetaTypeId = registerEnumMetaType<S::N>(&S::staticMetaObject, #N); \
|
||||
Bitstream& operator<<(Bitstream& out, const S::N& obj) { \
|
||||
|
@ -1327,7 +1627,9 @@ public:
|
|||
return in.read(&obj, bits); \
|
||||
}
|
||||
|
||||
/// Registers a simple type and its streamer.
|
||||
/// Registers a simple type and its streamer. This would typically be used at the top level of the source file associated with
|
||||
/// the type, and combines the registration of the type with the Qt metatype system with the registration of a simple type
|
||||
/// streamer.
|
||||
/// \return the metatype id
|
||||
template<class T> int registerSimpleMetaType() {
|
||||
int type = qRegisterMetaType<T>();
|
||||
|
@ -1335,7 +1637,8 @@ template<class T> int registerSimpleMetaType() {
|
|||
return type;
|
||||
}
|
||||
|
||||
/// Registers an enum type and its streamer.
|
||||
/// Registers an enum type and its streamer. Rather than using this directly, consider using the IMPLEMENT_ENUM_METATYPE
|
||||
/// macro.
|
||||
/// \return the metatype id
|
||||
template<class T> int registerEnumMetaType(const QMetaObject* metaObject, const char* name) {
|
||||
int type = qRegisterMetaType<T>();
|
||||
|
@ -1343,7 +1646,8 @@ template<class T> int registerEnumMetaType(const QMetaObject* metaObject, const
|
|||
return type;
|
||||
}
|
||||
|
||||
/// Registers a streamable type and its streamer.
|
||||
/// Registers a streamable type and its streamer. Rather than using this directly, consider using the
|
||||
/// DECLARE_STREAMABLE_METATYPE macro and the mtc code generation tool.
|
||||
/// \return the metatype id
|
||||
template<class T> int registerStreamableMetaType() {
|
||||
int type = qRegisterMetaType<T>();
|
||||
|
@ -1351,7 +1655,9 @@ template<class T> int registerStreamableMetaType() {
|
|||
return type;
|
||||
}
|
||||
|
||||
/// Registers a collection type and its streamer.
|
||||
/// Registers a collection type and its streamer. This would typically be used at the top level of the source file associated
|
||||
/// with the type, and combines the registration of the type with the Qt metatype system with the registration of a collection
|
||||
/// type streamer.
|
||||
/// \return the metatype id
|
||||
template<class T> int registerCollectionMetaType() {
|
||||
int type = qRegisterMetaType<T>();
|
||||
|
@ -1359,7 +1665,9 @@ template<class T> int registerCollectionMetaType() {
|
|||
return type;
|
||||
}
|
||||
|
||||
/// Flags a class as streamable (use as you would Q_OBJECT).
|
||||
/// Flags a class as streamable. Use as you would Q_OBJECT: as the first line of a class definition, before any members or
|
||||
/// access qualifiers. Typically, one would follow the definition with DECLARE_STREAMABLE_METATYPE. The mtc tool looks for
|
||||
/// this macro in order to generate streaming (etc.) code for types.
|
||||
#define STREAMABLE public: \
|
||||
static const int Type; \
|
||||
static const QVector<MetaField>& getMetaFields(); \
|
||||
|
@ -1369,7 +1677,8 @@ template<class T> int registerCollectionMetaType() {
|
|||
private: \
|
||||
static QHash<QByteArray, int> createFieldIndices();
|
||||
|
||||
/// Flags a field or base class as streaming.
|
||||
/// Flags a (public) field within or base class of a streamable type as streaming. Use before all base classes or fields that
|
||||
/// you want to stream.
|
||||
#define STREAM
|
||||
|
||||
#endif // hifi_Bitstream_h
|
||||
|
|
|
@ -23,18 +23,50 @@
|
|||
|
||||
class ReliableChannel;
|
||||
|
||||
/// Performs simple datagram sequencing, packet fragmentation and reassembly.
|
||||
/// Performs datagram sequencing, packet fragmentation and reassembly. Works with Bitstream to provide methods to send and
|
||||
/// receive data over UDP with varying reliability and latency characteristics. To use, create a DatagramSequencer with the
|
||||
/// fixed-size header that will be included with all outgoing datagrams and expected in all incoming ones (the contents of the
|
||||
/// header are not checked on receive, only skipped over, and may be modified by the party that actually send the
|
||||
/// datagram--this means that the header may include dynamically generated data, as long as its size remains fixed). Connect
|
||||
/// the readyToWrite signal to a slot that will actually transmit the datagram to the remote party. When a datagram is
|
||||
/// received from that party, call receivedDatagram with its contents.
|
||||
///
|
||||
/// A "packet" represents a batch of data sent at one time (split into one or more datagrams sized below the MTU). Packets are
|
||||
/// received in full and in order or not at all (that is, a packet being assembled is dropped as soon as a fragment from the
|
||||
/// next packet is received). Packets can be any size, but the larger a packet is, the more likely it is to be dropped--so,
|
||||
/// it's better to keep packet sizes close to the MTU. To write a packet, call startPacket, write data to the returned
|
||||
/// Bitstream, then call endPacket (which will result in one or more firings of readyToWrite). Data written in this way is not
|
||||
/// guaranteed to be received, but if it is received, it will arrive in order. This is a good way to transmit delta state:
|
||||
/// state that represents the change between the last acknowledged state and the current state (which, if not received, will
|
||||
/// not be resent as-is; instead, it will be replaced by up-to-date new deltas).
|
||||
///
|
||||
/// There are two methods for sending reliable data. The first, for small messages that require minimum-latency processing, is
|
||||
/// the high priority messaging system. When you call sendHighPriorityMessage, the message that you send will be included with
|
||||
/// every outgoing packet until it is acknowledged. When the receiving party first sees the message, it will fire a
|
||||
/// receivedHighPriorityMessage signal.
|
||||
///
|
||||
/// The second method employs a set of independent reliable channels multiplexed onto the packet stream. These channels are
|
||||
/// created lazily through the getReliableOutputChannel/getReliableInputChannel functions. Output channels contain buffers
|
||||
/// to which one may write either arbitrary data (as a QIODevice) or messages (as QVariants), or switch between the two.
|
||||
/// Each time a packet is sent, data pending for reliable output channels is added, in proportion to their relative priorities,
|
||||
/// until the packet size limit set by setMaxPacketSize is reached. On the receive side, the streams are reconstructed and
|
||||
/// (again, depending on whether messages are enabled) either the QIODevice reports that data is available, or, when a complete
|
||||
/// message is decoded, the receivedMessage signal is fired.
|
||||
class DatagramSequencer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Contains the content of a high-priority message along with the number of the first packet in which it was sent.
|
||||
class HighPriorityMessage {
|
||||
public:
|
||||
QVariant data;
|
||||
int firstPacketNumber;
|
||||
};
|
||||
|
||||
/// Creates a new datagram sequencer.
|
||||
/// \param datagramHeader the content of the header that will be prepended to each outgoing datagram and whose length
|
||||
/// will be skipped over in each incoming datagram
|
||||
DatagramSequencer(const QByteArray& datagramHeader = QByteArray(), QObject* parent = NULL);
|
||||
|
||||
/// Returns a reference to the weak hash mapping remote ids to shared objects.
|
||||
|
@ -267,15 +299,24 @@ class ReliableChannel : public QObject {
|
|||
|
||||
public:
|
||||
|
||||
/// Returns the channel's index in the sequencer's channel map.
|
||||
int getIndex() const { return _index; }
|
||||
|
||||
/// Returns a reference to the buffer used to write/read data to/from this channel.
|
||||
CircularBuffer& getBuffer() { return _buffer; }
|
||||
|
||||
/// Returns a reference to the data stream created on this channel's buffer.
|
||||
QDataStream& getDataStream() { return _dataStream; }
|
||||
|
||||
/// Returns a reference to the bitstream created on this channel's data stream.
|
||||
Bitstream& getBitstream() { return _bitstream; }
|
||||
|
||||
/// Sets the channel priority, which determines how much of this channel's data (in proportion to the other channels) to
|
||||
/// include in each outgoing packet.
|
||||
void setPriority(float priority) { _priority = priority; }
|
||||
float getPriority() const { return _priority; }
|
||||
|
||||
/// Returns the number of bytes available to read from this channel.
|
||||
int getBytesAvailable() const;
|
||||
|
||||
/// Sets whether we expect to write/read framed messages.
|
||||
|
@ -287,6 +328,7 @@ public:
|
|||
|
||||
signals:
|
||||
|
||||
/// Fired when a framed message has been received on this channel.
|
||||
void receivedMessage(const QVariant& message);
|
||||
|
||||
private slots:
|
||||
|
|
|
@ -83,7 +83,7 @@ static TestSharedObjectA::TestFlags getRandomTestFlags() {
|
|||
return flags;
|
||||
}
|
||||
|
||||
static QScriptValue createRandomScriptValue(bool complex = false) {
|
||||
static QScriptValue createRandomScriptValue(bool complex = false, bool ensureHashOrder = false) {
|
||||
scriptObjectsCreated++;
|
||||
switch (randIntInRange(0, complex ? 5 : 3)) {
|
||||
case 0:
|
||||
|
@ -108,31 +108,37 @@ static QScriptValue createRandomScriptValue(bool complex = false) {
|
|||
}
|
||||
default: {
|
||||
QScriptValue value = ScriptCache::getInstance()->getEngine()->newObject();
|
||||
if (randomBoolean()) {
|
||||
if (ensureHashOrder) {
|
||||
// we can't depend on the iteration order, so if we need it to be the same (as when comparing bytes), we
|
||||
// can only have one property
|
||||
value.setProperty("foo", createRandomScriptValue());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
value.setProperty("bar", createRandomScriptValue());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
value.setProperty("baz", createRandomScriptValue());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
value.setProperty("bong", createRandomScriptValue());
|
||||
} else {
|
||||
if (randomBoolean()) {
|
||||
value.setProperty("foo", createRandomScriptValue());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
value.setProperty("bar", createRandomScriptValue());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
value.setProperty("baz", createRandomScriptValue());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
value.setProperty("bong", createRandomScriptValue());
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TestMessageC createRandomMessageC() {
|
||||
static TestMessageC createRandomMessageC(bool ensureHashOrder = false) {
|
||||
TestMessageC message;
|
||||
message.foo = randomBoolean();
|
||||
message.bar = rand();
|
||||
message.baz = randFloat();
|
||||
message.bong.foo = createRandomBytes();
|
||||
message.bong.baz = getRandomTestEnum();
|
||||
message.bizzle = createRandomScriptValue(true);
|
||||
message.bizzle = createRandomScriptValue(true, ensureHashOrder);
|
||||
return message;
|
||||
}
|
||||
|
||||
|
@ -146,7 +152,7 @@ static bool testSerialization(Bitstream::MetadataType metadataType) {
|
|||
SharedObjectPointer testObjectWrittenB = new TestSharedObjectB(randFloat(), createRandomBytes(),
|
||||
TestSharedObjectB::THIRD_TEST_ENUM, TestSharedObjectB::SECOND_TEST_FLAG);
|
||||
out << testObjectWrittenB;
|
||||
TestMessageC messageWritten = createRandomMessageC();
|
||||
TestMessageC messageWritten = createRandomMessageC(true);
|
||||
out << QVariant::fromValue(messageWritten);
|
||||
QByteArray endWritten = "end";
|
||||
out << endWritten;
|
||||
|
@ -243,6 +249,74 @@ static bool testSerialization(Bitstream::MetadataType metadataType) {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (metadataType != Bitstream::FULL_METADATA) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// now write to JSON
|
||||
JSONWriter jsonWriter;
|
||||
jsonWriter << testObjectReadA;
|
||||
jsonWriter << testObjectReadB;
|
||||
jsonWriter << messageRead;
|
||||
jsonWriter << endRead;
|
||||
QByteArray encodedJson = jsonWriter.getDocument().toJson();
|
||||
|
||||
// and read from JSON
|
||||
JSONReader jsonReader(QJsonDocument::fromJson(encodedJson), Bitstream::ALL_GENERICS);
|
||||
jsonReader >> testObjectReadA;
|
||||
jsonReader >> testObjectReadB;
|
||||
jsonReader >> messageRead;
|
||||
jsonReader >> endRead;
|
||||
|
||||
// reassign the ids
|
||||
testObjectReadA->setID(testObjectWrittenA->getID());
|
||||
testObjectReadA->setOriginID(testObjectWrittenA->getOriginID());
|
||||
testObjectReadB->setID(testObjectWrittenB->getID());
|
||||
testObjectReadB->setOriginID(testObjectWrittenB->getOriginID());
|
||||
|
||||
// and back to binary
|
||||
QByteArray secondCompareArray;
|
||||
QDataStream secondCompareOutStream(&secondCompareArray, QIODevice::WriteOnly);
|
||||
Bitstream secondCompareOut(secondCompareOutStream, Bitstream::FULL_METADATA);
|
||||
secondCompareOut << testObjectReadA;
|
||||
secondCompareOut << testObjectReadB;
|
||||
secondCompareOut << messageRead;
|
||||
secondCompareOut << endRead;
|
||||
secondCompareOut.flush();
|
||||
|
||||
if (compareArray != secondCompareArray) {
|
||||
qDebug() << "Mismatch between written/JSON streams (generics).";
|
||||
return true;
|
||||
}
|
||||
|
||||
// once more, with mapping!
|
||||
JSONReader secondJSONReader(QJsonDocument::fromJson(encodedJson));
|
||||
secondJSONReader >> testObjectReadA;
|
||||
secondJSONReader >> testObjectReadB;
|
||||
secondJSONReader >> messageRead;
|
||||
secondJSONReader >> endRead;
|
||||
|
||||
// reassign the ids
|
||||
testObjectReadA->setID(testObjectWrittenA->getID());
|
||||
testObjectReadA->setOriginID(testObjectWrittenA->getOriginID());
|
||||
testObjectReadB->setID(testObjectWrittenB->getID());
|
||||
testObjectReadB->setOriginID(testObjectWrittenB->getOriginID());
|
||||
|
||||
// and back to binary
|
||||
QByteArray thirdCompareArray;
|
||||
QDataStream thirdCompareOutStream(&thirdCompareArray, QIODevice::WriteOnly);
|
||||
Bitstream thirdCompareOut(thirdCompareOutStream, Bitstream::FULL_METADATA);
|
||||
thirdCompareOut << testObjectReadA;
|
||||
thirdCompareOut << testObjectReadB;
|
||||
thirdCompareOut << messageRead;
|
||||
thirdCompareOut << endRead;
|
||||
thirdCompareOut.flush();
|
||||
|
||||
if (compareArray != thirdCompareArray) {
|
||||
qDebug() << "Mismatch between written/JSON streams (mapped).";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
6
tools/CMakeLists.txt
Normal file
6
tools/CMakeLists.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# add the tool directories
|
||||
add_subdirectory(bitstream2json)
|
||||
add_subdirectory(json2bitstream)
|
||||
add_subdirectory(mtc)
|
26
tools/bitstream2json/CMakeLists.txt
Normal file
26
tools/bitstream2json/CMakeLists.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME bitstream2json)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
# setup for find modules
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
|
||||
|
||||
find_package(Qt5 COMPONENTS Network Script Widgets)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||
|
||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
|
70
tools/bitstream2json/src/main.cpp
Normal file
70
tools/bitstream2json/src/main.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// main.cpp
|
||||
// tools/bitstream2json/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/17/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QFile>
|
||||
|
||||
#include <AttributeRegistry.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main (int argc, char** argv) {
|
||||
// need the core application for the script engine
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
if (argc < 3) {
|
||||
cerr << "Usage: bitstream2json inputfile outputfile [types...]" << endl;
|
||||
return 0;
|
||||
}
|
||||
QFile inputFile(argv[1]);
|
||||
if (!inputFile.open(QIODevice::ReadOnly)) {
|
||||
cerr << "Failed to open input file: " << inputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
QDataStream inputData(&inputFile);
|
||||
Bitstream input(inputData, Bitstream::FULL_METADATA, Bitstream::ALL_GENERICS);
|
||||
|
||||
QFile outputFile(argv[2]);
|
||||
if (!outputFile.open(QIODevice::WriteOnly)) {
|
||||
cerr << "Failed to open output file: " << outputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
JSONWriter output;
|
||||
|
||||
if (argc < 4) {
|
||||
// default type is a single QVariant
|
||||
QVariant value;
|
||||
input >> value;
|
||||
output << value;
|
||||
|
||||
} else {
|
||||
for (int i = 3; i < argc; i++) {
|
||||
int type = QMetaType::type(argv[i]);
|
||||
if (type == QMetaType::UnknownType) {
|
||||
cerr << "Unknown type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
const TypeStreamer* streamer = Bitstream::getTypeStreamer(type);
|
||||
if (!streamer) {
|
||||
cerr << "Non-streamable type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
QVariant value = streamer->read(input);
|
||||
output.appendToContents(streamer->getJSONData(output, value));
|
||||
}
|
||||
}
|
||||
|
||||
outputFile.write(output.getDocument().toJson());
|
||||
|
||||
return 0;
|
||||
}
|
26
tools/json2bitstream/CMakeLists.txt
Normal file
26
tools/json2bitstream/CMakeLists.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(TARGET_NAME json2bitstream)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
# setup for find modules
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
|
||||
|
||||
find_package(Qt5 COMPONENTS Network Script Widgets)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
setup_hifi_project(${TARGET_NAME} TRUE)
|
||||
|
||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
|
76
tools/json2bitstream/src/main.cpp
Normal file
76
tools/json2bitstream/src/main.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// main.cpp
|
||||
// tools/json2bitstream/src
|
||||
//
|
||||
// Created by Andrzej Kapolka on 6/17/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDataStream>
|
||||
#include <QFile>
|
||||
|
||||
#include <AttributeRegistry.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main (int argc, char** argv) {
|
||||
// need the core application for the script engine
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
if (argc < 3) {
|
||||
cerr << "Usage: bitstream2json inputfile outputfile [types...]" << endl;
|
||||
return 0;
|
||||
}
|
||||
QFile inputFile(argv[1]);
|
||||
if (!inputFile.open(QIODevice::ReadOnly)) {
|
||||
cerr << "Failed to open input file: " << inputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
QJsonParseError error;
|
||||
QJsonDocument document = QJsonDocument::fromJson(inputFile.readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
cerr << "Failed to read input file: " << error.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
JSONReader input(document, Bitstream::ALL_GENERICS);
|
||||
|
||||
QFile outputFile(argv[2]);
|
||||
if (!outputFile.open(QIODevice::WriteOnly)) {
|
||||
cerr << "Failed to open output file: " << outputFile.errorString().toLatin1().constData() << endl;
|
||||
return 1;
|
||||
}
|
||||
QDataStream outputData(&outputFile);
|
||||
Bitstream output(outputData, Bitstream::FULL_METADATA);
|
||||
|
||||
if (argc < 4) {
|
||||
// default type is a single QVariant
|
||||
QVariant value;
|
||||
input >> value;
|
||||
output << value;
|
||||
|
||||
} else {
|
||||
for (int i = 3; i < argc; i++) {
|
||||
int type = QMetaType::type(argv[i]);
|
||||
if (type == QMetaType::UnknownType) {
|
||||
cerr << "Unknown type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
const TypeStreamer* streamer = Bitstream::getTypeStreamer(type);
|
||||
if (!streamer) {
|
||||
cerr << "Non-streamable type: " << argv[i] << endl;
|
||||
return 1;
|
||||
}
|
||||
QVariant value;
|
||||
streamer->putJSONData(input, input.retrieveNextFromContents(), value);
|
||||
streamer->write(output, value);
|
||||
}
|
||||
}
|
||||
output.flush();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -123,11 +123,6 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
out << "QHash<QByteArray, int> " << name << "::createFieldIndices() {\n";
|
||||
out << " QHash<QByteArray, int> indices;\n";
|
||||
out << " int index = 1;\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";
|
||||
|
@ -217,6 +212,36 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
|
|||
}
|
||||
out << "}\n";
|
||||
|
||||
out << "template<> QJsonValue JSONWriter::getData(const " << name << "& value) {\n";
|
||||
out << " QJsonArray array;\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " foreach (const QJsonValue& element, getData(static_cast<const " << base << "&>(value)).toArray()) {\n";
|
||||
out << " array.append(element);\n";
|
||||
out << " }\n";
|
||||
}
|
||||
foreach (const Field& field, str.fields) {
|
||||
out << " array.append(getData(value." << field.name << "));\n";
|
||||
}
|
||||
out << " return array;\n";
|
||||
out << "}\n";
|
||||
|
||||
out << "template<> void JSONReader::putData(const QJsonValue& data, " << name << "& value) {\n";
|
||||
if (!(str.clazz.bases.isEmpty() && str.fields.isEmpty())) {
|
||||
out << " QJsonArray array = data.toArray(), subarray;\n";
|
||||
out << " QJsonArray::const_iterator it = array.constBegin();\n";
|
||||
foreach (const QString& base, str.clazz.bases) {
|
||||
out << " subarray = QJsonArray();\n";
|
||||
out << " for (int i = 0; i < " << base << "::getMetaFields().size(); i++) {\n";
|
||||
out << " subarray.append(*it++);\n";
|
||||
out << " }\n";
|
||||
out << " putData(subarray, static_cast<" << base << "&>(value));\n";
|
||||
}
|
||||
foreach (const Field& field, str.fields) {
|
||||
out << " putData(*it++, value." << field.name << ");\n";
|
||||
}
|
||||
}
|
||||
out << "}\n";
|
||||
|
||||
out << "bool operator==(const " << name << "& first, const " << name << "& second) {\n";
|
||||
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
|
||||
out << " return true";
|
||||
|
|
Loading…
Reference in a new issue