diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index d9242ad9b7..d7e15a9dd6 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,7 @@ REGISTER_SIMPLE_TYPE_STREAMER(uint) REGISTER_SIMPLE_TYPE_STREAMER(float) REGISTER_SIMPLE_TYPE_STREAMER(QByteArray) REGISTER_SIMPLE_TYPE_STREAMER(QColor) +REGISTER_SIMPLE_TYPE_STREAMER(QScriptValue) REGISTER_SIMPLE_TYPE_STREAMER(QString) REGISTER_SIMPLE_TYPE_STREAMER(QUrl) REGISTER_SIMPLE_TYPE_STREAMER(QVariantList) @@ -285,6 +287,12 @@ void Bitstream::writeDelta(const QVariant& value, const QVariant& reference) { streamer->writeRawDelta(*this, value, reference); } +void Bitstream::writeRawDelta(const QVariant& value, const QVariant& reference) { + const TypeStreamer* streamer = getTypeStreamers().value(value.userType()); + _typeStreamerStreamer << streamer; + streamer->writeRawDelta(*this, value, reference); +} + void Bitstream::readRawDelta(QVariant& value, const QVariant& reference) { TypeReader typeReader; _typeStreamerStreamer >> typeReader; @@ -309,6 +317,272 @@ void Bitstream::readRawDelta(QObject*& value, const QObject* reference) { value = objectReader.readDelta(*this, reference); } +void Bitstream::writeRawDelta(const QScriptValue& value, const QScriptValue& reference) { + if (reference.isUndefined() || reference.isNull()) { + *this << value; + + } else if (reference.isBool()) { + if (value.isBool()) { + *this << false; + *this << value.toBool(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isNumber()) { + if (value.isNumber()) { + *this << false; + *this << value.toNumber(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isString()) { + if (value.isString()) { + *this << false; + *this << value.toString(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isVariant()) { + if (value.isVariant()) { + *this << false; + writeRawDelta(value.toVariant(), reference.toVariant()); + + } else { + *this << true; + *this << value; + } + } else if (reference.isQObject()) { + if (value.isQObject()) { + *this << false; + writeRawDelta(value.toQObject(), reference.toQObject()); + + } else { + *this << true; + *this << value; + } + } else if (reference.isQMetaObject()) { + if (value.isQMetaObject()) { + *this << false; + *this << value.toQMetaObject(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isDate()) { + if (value.isDate()) { + *this << false; + *this << value.toDateTime(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isRegExp()) { + if (value.isRegExp()) { + *this << false; + *this << value.toRegExp(); + + } else { + *this << true; + *this << value; + } + } else if (reference.isArray()) { + if (value.isArray()) { + *this << false; + int length = value.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + *this << length; + int referenceLength = reference.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + for (int i = 0; i < length; i++) { + if (i < referenceLength) { + writeDelta(value.property(i), reference.property(i)); + } else { + *this << value.property(i); + } + } + } else { + *this << true; + *this << value; + } + } else if (reference.isObject()) { + if (value.isObject() && !(value.isArray() || value.isRegExp() || value.isDate() || + value.isQMetaObject() || value.isQObject() || value.isVariant())) { + *this << false; + for (QScriptValueIterator it(value); it.hasNext(); ) { + it.next(); + QScriptValue referenceValue = reference.property(it.scriptName()); + if (it.value() != referenceValue) { + *this << it.scriptName(); + writeRawDelta(it.value(), referenceValue); + } + } + for (QScriptValueIterator it(reference); it.hasNext(); ) { + it.next(); + if (!value.property(it.scriptName()).isValid()) { + *this << it.scriptName(); + writeRawDelta(QScriptValue(), it.value()); + } + } + *this << QScriptString(); + + } else { + *this << true; + *this << value; + } + } else { + *this << value; + } +} + +void Bitstream::readRawDelta(QScriptValue& value, const QScriptValue& reference) { + if (reference.isUndefined() || reference.isNull()) { + *this >> value; + + } else if (reference.isBool()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + bool boolValue; + *this >> boolValue; + value = QScriptValue(boolValue); + } + } else if (reference.isNumber()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + qsreal numberValue; + *this >> numberValue; + value = QScriptValue(numberValue); + } + } else if (reference.isString()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QString stringValue; + *this >> stringValue; + value = QScriptValue(stringValue); + } + } else if (reference.isVariant()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QVariant variant; + readRawDelta(variant, reference.toVariant()); + value = ScriptCache::getInstance()->getEngine()->newVariant(variant); + } + } else if (reference.isQObject()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QObject* object; + readRawDelta(object, reference.toQObject()); + value = ScriptCache::getInstance()->getEngine()->newQObject(object, QScriptEngine::ScriptOwnership); + } + } else if (reference.isQMetaObject()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + const QMetaObject* metaObject; + *this >> metaObject; + value = ScriptCache::getInstance()->getEngine()->newQMetaObject(metaObject); + } + } else if (reference.isDate()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QDateTime dateTime; + *this >> dateTime; + value = ScriptCache::getInstance()->getEngine()->newDate(dateTime); + } + } else if (reference.isRegExp()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + QRegExp regExp; + *this >> regExp; + value = ScriptCache::getInstance()->getEngine()->newRegExp(regExp); + } + } else if (reference.isArray()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + int length; + *this >> length; + value = ScriptCache::getInstance()->getEngine()->newArray(length); + int referenceLength = reference.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + for (int i = 0; i < length; i++) { + QScriptValue element; + if (i < referenceLength) { + readDelta(element, reference.property(i)); + } else { + *this >> element; + } + value.setProperty(i, element); + } + } + } else if (reference.isObject()) { + bool typeChanged; + *this >> typeChanged; + if (typeChanged) { + *this >> value; + + } else { + // start by shallow-copying the reference + value = ScriptCache::getInstance()->getEngine()->newObject(); + for (QScriptValueIterator it(reference); it.hasNext(); ) { + it.next(); + value.setProperty(it.scriptName(), it.value()); + } + // then apply the requested changes + forever { + QScriptString name; + *this >> name; + if (!name.isValid()) { + break; + } + QScriptValue scriptValue; + readRawDelta(scriptValue, reference.property(name)); + value.setProperty(name, scriptValue); + } + } + } else { + *this >> value; + } +} + Bitstream& Bitstream::operator<<(bool value) { if (value) { _byte |= (1 << _position); @@ -350,6 +624,14 @@ Bitstream& Bitstream::operator>>(uint& value) { return *this; } +Bitstream& Bitstream::operator<<(qint64 value) { + return write(&value, 64); +} + +Bitstream& Bitstream::operator>>(qint64& value) { + return read(&value, 64); +} + Bitstream& Bitstream::operator<<(float value) { return write(&value, 32); } @@ -358,6 +640,14 @@ Bitstream& Bitstream::operator>>(float& value) { return read(&value, 32); } +Bitstream& Bitstream::operator<<(double value) { + return write(&value, 64); +} + +Bitstream& Bitstream::operator>>(double& value) { + return read(&value, 64); +} + Bitstream& Bitstream::operator<<(const glm::vec3& value) { return *this << value.x << value.y << value.z; } @@ -420,6 +710,40 @@ Bitstream& Bitstream::operator>>(QUrl& url) { return *this; } +Bitstream& Bitstream::operator<<(const QDateTime& dateTime) { + return *this << dateTime.toMSecsSinceEpoch(); +} + +Bitstream& Bitstream::operator>>(QDateTime& dateTime) { + qint64 msecsSinceEpoch; + *this >> msecsSinceEpoch; + dateTime = QDateTime::fromMSecsSinceEpoch(msecsSinceEpoch); + return *this; +} + +Bitstream& Bitstream::operator<<(const QRegExp& regExp) { + *this << regExp.pattern(); + Qt::CaseSensitivity caseSensitivity = regExp.caseSensitivity(); + write(&caseSensitivity, 1); + QRegExp::PatternSyntax syntax = regExp.patternSyntax(); + write(&syntax, 3); + return *this << regExp.isMinimal(); +} + +Bitstream& Bitstream::operator>>(QRegExp& regExp) { + QString pattern; + *this >> pattern; + Qt::CaseSensitivity caseSensitivity = (Qt::CaseSensitivity)0; + read(&caseSensitivity, 1); + QRegExp::PatternSyntax syntax = (QRegExp::PatternSyntax)0; + read(&syntax, 3); + regExp = QRegExp(pattern, caseSensitivity, syntax); + bool minimal; + *this >> minimal; + regExp.setMinimal(minimal); + return *this; +} + Bitstream& Bitstream::operator<<(const QVariant& value) { if (!value.isValid()) { _typeStreamerStreamer << NULL; @@ -543,6 +867,185 @@ Bitstream& Bitstream::operator>>(QScriptString& string) { return *this; } +enum ScriptValueType { + INVALID_SCRIPT_VALUE, + UNDEFINED_SCRIPT_VALUE, + NULL_SCRIPT_VALUE, + BOOL_SCRIPT_VALUE, + NUMBER_SCRIPT_VALUE, + STRING_SCRIPT_VALUE, + VARIANT_SCRIPT_VALUE, + QOBJECT_SCRIPT_VALUE, + QMETAOBJECT_SCRIPT_VALUE, + DATE_SCRIPT_VALUE, + REGEXP_SCRIPT_VALUE, + ARRAY_SCRIPT_VALUE, + OBJECT_SCRIPT_VALUE +}; + +const int SCRIPT_VALUE_BITS = 4; + +static void writeScriptValueType(Bitstream& out, ScriptValueType type) { + out.write(&type, SCRIPT_VALUE_BITS); +} + +static ScriptValueType readScriptValueType(Bitstream& in) { + ScriptValueType type = (ScriptValueType)0; + in.read(&type, SCRIPT_VALUE_BITS); + return type; +} + +Bitstream& Bitstream::operator<<(const QScriptValue& value) { + if (value.isUndefined()) { + writeScriptValueType(*this, UNDEFINED_SCRIPT_VALUE); + + } else if (value.isNull()) { + writeScriptValueType(*this, NULL_SCRIPT_VALUE); + + } else if (value.isBool()) { + writeScriptValueType(*this, BOOL_SCRIPT_VALUE); + *this << value.toBool(); + + } else if (value.isNumber()) { + writeScriptValueType(*this, NUMBER_SCRIPT_VALUE); + *this << value.toNumber(); + + } else if (value.isString()) { + writeScriptValueType(*this, STRING_SCRIPT_VALUE); + *this << value.toString(); + + } else if (value.isVariant()) { + writeScriptValueType(*this, VARIANT_SCRIPT_VALUE); + *this << value.toVariant(); + + } else if (value.isQObject()) { + writeScriptValueType(*this, QOBJECT_SCRIPT_VALUE); + *this << value.toQObject(); + + } else if (value.isQMetaObject()) { + writeScriptValueType(*this, QMETAOBJECT_SCRIPT_VALUE); + *this << value.toQMetaObject(); + + } else if (value.isDate()) { + writeScriptValueType(*this, DATE_SCRIPT_VALUE); + *this << value.toDateTime(); + + } else if (value.isRegExp()) { + writeScriptValueType(*this, REGEXP_SCRIPT_VALUE); + *this << value.toRegExp(); + + } else if (value.isArray()) { + writeScriptValueType(*this, ARRAY_SCRIPT_VALUE); + int length = value.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + *this << length; + for (int i = 0; i < length; i++) { + *this << value.property(i); + } + } else if (value.isObject()) { + writeScriptValueType(*this, OBJECT_SCRIPT_VALUE); + for (QScriptValueIterator it(value); it.hasNext(); ) { + it.next(); + *this << it.scriptName(); + *this << it.value(); + } + *this << QScriptString(); + + } else { + writeScriptValueType(*this, INVALID_SCRIPT_VALUE); + } + return *this; +} + +Bitstream& Bitstream::operator>>(QScriptValue& value) { + switch (readScriptValueType(*this)) { + case UNDEFINED_SCRIPT_VALUE: + value = QScriptValue(QScriptValue::UndefinedValue); + break; + + case NULL_SCRIPT_VALUE: + value = QScriptValue(QScriptValue::NullValue); + break; + + case BOOL_SCRIPT_VALUE: { + bool boolValue; + *this >> boolValue; + value = QScriptValue(boolValue); + break; + } + case NUMBER_SCRIPT_VALUE: { + qsreal numberValue; + *this >> numberValue; + value = QScriptValue(numberValue); + break; + } + case STRING_SCRIPT_VALUE: { + QString stringValue; + *this >> stringValue; + value = QScriptValue(stringValue); + break; + } + case VARIANT_SCRIPT_VALUE: { + QVariant variantValue; + *this >> variantValue; + value = ScriptCache::getInstance()->getEngine()->newVariant(variantValue); + break; + } + case QOBJECT_SCRIPT_VALUE: { + QObject* object; + *this >> object; + ScriptCache::getInstance()->getEngine()->newQObject(object, QScriptEngine::ScriptOwnership); + break; + } + case QMETAOBJECT_SCRIPT_VALUE: { + const QMetaObject* metaObject; + *this >> metaObject; + ScriptCache::getInstance()->getEngine()->newQMetaObject(metaObject); + break; + } + case DATE_SCRIPT_VALUE: { + QDateTime dateTime; + *this >> dateTime; + value = ScriptCache::getInstance()->getEngine()->newDate(dateTime); + break; + } + case REGEXP_SCRIPT_VALUE: { + QRegExp regExp; + *this >> regExp; + value = ScriptCache::getInstance()->getEngine()->newRegExp(regExp); + break; + } + case ARRAY_SCRIPT_VALUE: { + int length; + *this >> length; + value = ScriptCache::getInstance()->getEngine()->newArray(length); + for (int i = 0; i < length; i++) { + QScriptValue element; + *this >> element; + value.setProperty(i, element); + } + break; + } + case OBJECT_SCRIPT_VALUE: { + value = ScriptCache::getInstance()->getEngine()->newObject(); + forever { + QScriptString name; + *this >> name; + if (!name.isValid()) { + break; + } + QScriptValue scriptValue; + *this >> scriptValue; + value.setProperty(name, scriptValue); + } + break; + } + default: + value = QScriptValue(); + break; + } + return *this; +} + Bitstream& Bitstream::operator<<(const SharedObjectPointer& object) { _sharedObjectStreamer << object; return *this; @@ -561,7 +1064,7 @@ Bitstream& Bitstream::operator<(const QMetaObject* metaObject) { if (_metadataType == NO_METADATA) { return *this; } - const QVector& propertyWriters = getPropertyWriters().value(metaObject); + const PropertyWriterVector& propertyWriters = getPropertyWriters().value(metaObject); *this << propertyWriters.size(); QCryptographicHash hash(QCryptographicHash::Md5); foreach (const PropertyWriter& propertyWriter, propertyWriters) { @@ -600,7 +1103,7 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) { } int storedPropertyCount; *this >> storedPropertyCount; - QVector properties(storedPropertyCount); + PropertyReaderVector properties(storedPropertyCount); for (int i = 0; i < storedPropertyCount; i++) { TypeReader typeReader; *this >> typeReader; @@ -619,7 +1122,7 @@ Bitstream& Bitstream::operator>(ObjectReader& objectReader) { QCryptographicHash hash(QCryptographicHash::Md5); bool matches = true; if (metaObject) { - const QVector& propertyWriters = getPropertyWriters().value(metaObject); + const PropertyWriterVector& propertyWriters = getPropertyWriters().value(metaObject); if (propertyWriters.size() == properties.size()) { for (int i = 0; i < propertyWriters.size(); i++) { const PropertyWriter& propertyWriter = propertyWriters.at(i); @@ -899,14 +1402,17 @@ Bitstream& Bitstream::operator>(AttributePointer& attribute) { return *this; } +const QString INVALID_STRING("%INVALID%"); + Bitstream& Bitstream::operator<(const QScriptString& string) { - return *this << string.toString(); + return *this << (string.isValid() ? string.toString() : INVALID_STRING); } Bitstream& Bitstream::operator>(QScriptString& string) { QString rawString; *this >> rawString; - string = ScriptCache::getInstance()->getEngine()->toStringHandle(rawString); + string = (rawString == INVALID_STRING) ? QScriptString() : + ScriptCache::getInstance()->getEngine()->toStringHandle(rawString); return *this; } @@ -990,17 +1496,17 @@ QHash& Bitstream::getTypeStreamers() { return typeStreamers; } -const QHash, const TypeStreamer*>& Bitstream::getEnumStreamers() { - static QHash, const TypeStreamer*> enumStreamers = createEnumStreamers(); +const QHash& Bitstream::getEnumStreamers() { + static QHash enumStreamers = createEnumStreamers(); return enumStreamers; } -QHash, const TypeStreamer*> Bitstream::createEnumStreamers() { - QHash, const TypeStreamer*> enumStreamers; +QHash Bitstream::createEnumStreamers() { + QHash enumStreamers; foreach (const QMetaObject* metaObject, getMetaObjects()) { for (int i = 0; i < metaObject->enumeratorCount(); i++) { QMetaEnum metaEnum = metaObject->enumerator(i); - const TypeStreamer*& streamer = enumStreamers[QPair(metaEnum.scope(), metaEnum.name())]; + const TypeStreamer*& streamer = enumStreamers[ScopeNamePair(metaEnum.scope(), metaEnum.name())]; if (!streamer) { streamer = new EnumTypeStreamer(metaEnum); } @@ -1022,15 +1528,15 @@ QHash Bitstream::createEnumStreamersByName() { return enumStreamersByName; } -const QHash >& Bitstream::getPropertyReaders() { - static QHash > propertyReaders = createPropertyReaders(); +const QHash& Bitstream::getPropertyReaders() { + static QHash propertyReaders = createPropertyReaders(); return propertyReaders; } -QHash > Bitstream::createPropertyReaders() { - QHash > propertyReaders; +QHash Bitstream::createPropertyReaders() { + QHash propertyReaders; foreach (const QMetaObject* metaObject, getMetaObjects()) { - QVector& readers = propertyReaders[metaObject]; + PropertyReaderVector& readers = propertyReaders[metaObject]; for (int i = 0; i < metaObject->propertyCount(); i++) { QMetaProperty property = metaObject->property(i); if (!property.isStored()) { @@ -1039,7 +1545,7 @@ QHash > Bitstream::createPropertyRea const TypeStreamer* streamer; if (property.isEnumType()) { QMetaEnum metaEnum = property.enumerator(); - streamer = getEnumStreamers().value(QPair( + streamer = getEnumStreamers().value(ScopeNamePair( QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); } else { @@ -1053,15 +1559,15 @@ QHash > Bitstream::createPropertyRea return propertyReaders; } -const QHash >& Bitstream::getPropertyWriters() { - static QHash > propertyWriters = createPropertyWriters(); +const QHash& Bitstream::getPropertyWriters() { + static QHash propertyWriters = createPropertyWriters(); return propertyWriters; } -QHash > Bitstream::createPropertyWriters() { - QHash > propertyWriters; +QHash Bitstream::createPropertyWriters() { + QHash propertyWriters; foreach (const QMetaObject* metaObject, getMetaObjects()) { - QVector& writers = propertyWriters[metaObject]; + PropertyWriterVector& writers = propertyWriters[metaObject]; for (int i = 0; i < metaObject->propertyCount(); i++) { QMetaProperty property = metaObject->property(i); if (!property.isStored()) { @@ -1070,7 +1576,7 @@ QHash > Bitstream::createPropertyWri const TypeStreamer* streamer; if (property.isEnumType()) { QMetaEnum metaEnum = property.enumerator(); - streamer = getEnumStreamers().value(QPair( + streamer = getEnumStreamers().value(ScopeNamePair( QByteArray::fromRawData(metaEnum.scope(), strlen(metaEnum.scope())), QByteArray::fromRawData(metaEnum.name(), strlen(metaEnum.name())))); } else { @@ -1324,7 +1830,7 @@ void FieldReader::readDelta(Bitstream& in, const TypeStreamer* streamer, QVarian } ObjectReader::ObjectReader(const QByteArray& className, const QMetaObject* metaObject, - const QVector& properties) : + const PropertyReaderVector& properties) : _className(className), _metaObject(metaObject), _properties(properties) { diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 80adfc4e8b..d05f6574c0 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -29,6 +29,7 @@ class QByteArray; class QColor; class QDataStream; +class QScriptValue; class QUrl; class Attribute; @@ -44,6 +45,10 @@ class TypeStreamer; typedef SharedObjectPointerTemplate AttributePointer; +typedef QPair ScopeNamePair; +typedef QVector PropertyReaderVector; +typedef QVector PropertyWriterVector; + /// Streams integer identifiers that conform to the following pattern: each ID encountered in the stream is either one that /// has been sent (received) before, or is one more than the highest previously encountered ID (starting at zero). This allows /// us to use the minimum number of bits to encode the IDs. @@ -287,11 +292,15 @@ public: template void writeDelta(const T& value, const T& reference); template void readDelta(T& value, const T& reference); + void writeRawDelta(const QVariant& value, const QVariant& reference); void readRawDelta(QVariant& value, const QVariant& reference); void writeRawDelta(const QObject* value, const QObject* reference); void readRawDelta(QObject*& value, const QObject* reference); + void writeRawDelta(const QScriptValue& value, const QScriptValue& reference); + void readRawDelta(QScriptValue& value, const QScriptValue& reference); + template void writeRawDelta(const T& value, const T& reference); template void readRawDelta(T& value, const T& reference); @@ -316,9 +325,15 @@ public: Bitstream& operator<<(uint value); Bitstream& operator>>(uint& value); + Bitstream& operator<<(qint64 value); + Bitstream& operator>>(qint64& value); + Bitstream& operator<<(float value); Bitstream& operator>>(float& value); + Bitstream& operator<<(double value); + Bitstream& operator>>(double& value); + Bitstream& operator<<(const glm::vec3& value); Bitstream& operator>>(glm::vec3& value); @@ -337,6 +352,12 @@ public: Bitstream& operator<<(const QUrl& url); Bitstream& operator>>(QUrl& url); + Bitstream& operator<<(const QDateTime& dateTime); + Bitstream& operator>>(QDateTime& dateTime); + + Bitstream& operator<<(const QRegExp& regExp); + Bitstream& operator>>(QRegExp& regExp); + Bitstream& operator<<(const QVariant& value); Bitstream& operator>>(QVariant& value); @@ -372,6 +393,9 @@ public: Bitstream& operator<<(const QScriptString& string); Bitstream& operator>>(QScriptString& string); + Bitstream& operator<<(const QScriptValue& value); + Bitstream& operator>>(QScriptValue& value); + Bitstream& operator<<(const SharedObjectPointer& object); Bitstream& operator>>(SharedObjectPointer& object); @@ -422,14 +446,18 @@ private: static QHash& getMetaObjects(); static QMultiHash& getMetaObjectSubClasses(); static QHash& getTypeStreamers(); - static const QHash, const TypeStreamer*>& getEnumStreamers(); - static QHash, const TypeStreamer*> createEnumStreamers(); + + static const QHash& getEnumStreamers(); + static QHash createEnumStreamers(); + static const QHash& getEnumStreamersByName(); static QHash createEnumStreamersByName(); - static const QHash >& getPropertyReaders(); - static QHash > createPropertyReaders(); - static const QHash >& getPropertyWriters(); - static QHash > createPropertyWriters(); + + static const QHash& getPropertyReaders(); + static QHash createPropertyReaders(); + + static const QHash& getPropertyWriters(); + static QHash createPropertyWriters(); }; template inline void Bitstream::writeDelta(const T& value, const T& reference) { diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index dd090613b7..9648a047cb 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -13,11 +13,95 @@ #include #include +#include #include #include "AttributeRegistry.h" #include "ScriptCache.h" +static int scriptValueMetaTypeId = qRegisterMetaType(); +static bool scriptValueComparators = QMetaType::registerComparators(); + +bool operator==(const QScriptValue& first, const QScriptValue& second) { + if (first.isUndefined()) { + return second.isUndefined(); + + } else if (first.isNull()) { + return second.isNull(); + + } else if (first.isBool()) { + return second.isBool() && first.toBool() == second.toBool(); + + } else if (first.isNumber()) { + return second.isNumber() && first.toNumber() == second.toNumber(); + + } else if (first.isString()) { + return second.isString() && first.toString() == second.toString(); + + } else if (first.isVariant()) { + return second.isVariant() && first.toVariant() == second.toVariant(); + + } else if (first.isQObject()) { + return second.isQObject() && first.toQObject() == second.toQObject(); + + } else if (first.isQMetaObject()) { + return second.isQMetaObject() && first.toQMetaObject() == second.toQMetaObject(); + + } else if (first.isDate()) { + return second.isDate() && first.toDateTime() == second.toDateTime(); + + } else if (first.isRegExp()) { + return second.isRegExp() && first.toRegExp() == second.toRegExp(); + + } else if (first.isArray()) { + if (!second.isArray()) { + return false; + } + int length = first.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + if (second.property(ScriptCache::getInstance()->getLengthString()).toInt32() != length) { + return false; + } + for (int i = 0; i < length; i++) { + if (first.property(i) != second.property(i)) { + return false; + } + } + return true; + + } else if (first.isObject()) { + if (!second.isObject()) { + return false; + } + int propertyCount = 0; + for (QScriptValueIterator it(first); it.hasNext(); ) { + it.next(); + if (second.property(it.scriptName()) != it.value()) { + return false; + } + propertyCount++; + } + // make sure the second has exactly as many properties as the first + for (QScriptValueIterator it(second); it.hasNext(); ) { + it.next(); + if (--propertyCount < 0) { + return false; + } + } + return true; + + } else { + return !second.isValid(); + } +} + +bool operator!=(const QScriptValue& first, const QScriptValue& second) { + return !(first == second); +} + +bool operator<(const QScriptValue& first, const QScriptValue& second) { + return first.lessThan(second); +} + ScriptCache* ScriptCache::getInstance() { static ScriptCache cache; return &cache; diff --git a/libraries/metavoxels/src/ScriptCache.h b/libraries/metavoxels/src/ScriptCache.h index f393d0e0a8..5d29157b3d 100644 --- a/libraries/metavoxels/src/ScriptCache.h +++ b/libraries/metavoxels/src/ScriptCache.h @@ -65,6 +65,12 @@ private: QScriptString _generatorString; }; +Q_DECLARE_METATYPE(QScriptValue) + +bool operator==(const QScriptValue& first, const QScriptValue& second); +bool operator!=(const QScriptValue& first, const QScriptValue& second); +bool operator<(const QScriptValue& first, const QScriptValue& second); + /// A program loaded from the network. class NetworkProgram : public Resource { Q_OBJECT diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index 81f1840342..6ec2331b14 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -11,6 +11,8 @@ #include +#include + #include #include @@ -41,6 +43,8 @@ static int streamedBytesReceived = 0; static int sharedObjectsCreated = 0; static int sharedObjectsDestroyed = 0; static int objectMutationsPerformed = 0; +static int scriptObjectsCreated = 0; +static int scriptMutationsPerformed = 0; static QByteArray createRandomBytes(int minimumSize, int maximumSize) { QByteArray bytes(randIntInRange(minimumSize, maximumSize), 0); @@ -79,6 +83,48 @@ static TestSharedObjectA::TestFlags getRandomTestFlags() { return flags; } +static QScriptValue createRandomScriptValue(bool complex = false) { + scriptObjectsCreated++; + switch (randIntInRange(0, complex ? 5 : 3)) { + case 0: + return QScriptValue(QScriptValue::NullValue); + + case 1: + return QScriptValue(randomBoolean()); + + case 2: + return QScriptValue(randFloat()); + + case 3: + return QScriptValue(QString(createRandomBytes())); + + case 4: { + int length = randIntInRange(2, 6); + QScriptValue value = ScriptCache::getInstance()->getEngine()->newArray(length); + for (int i = 0; i < length; i++) { + value.setProperty(i, createRandomScriptValue()); + } + return value; + } + default: { + QScriptValue value = ScriptCache::getInstance()->getEngine()->newObject(); + 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() { TestMessageC message; message.foo = randomBoolean(); @@ -86,6 +132,7 @@ static TestMessageC createRandomMessageC() { message.baz = randFloat(); message.bong.foo = createRandomBytes(); message.bong.baz = getRandomTestEnum(); + message.bizzle = createRandomScriptValue(true); return message; } @@ -201,6 +248,7 @@ bool MetavoxelTests::run() { datagramsReceived << "with" << bytesReceived << "bytes"; qDebug() << "Created" << sharedObjectsCreated << "shared objects, destroyed" << sharedObjectsDestroyed; qDebug() << "Performed" << objectMutationsPerformed << "object mutations"; + qDebug() << "Created" << scriptObjectsCreated << "script objects, mutated" << scriptMutationsPerformed; qDebug(); qDebug() << "Running serialization tests..."; @@ -284,7 +332,7 @@ static QVariant createRandomMessage() { } static SharedObjectPointer mutate(const SharedObjectPointer& state) { - switch(randIntInRange(0, 3)) { + switch (randIntInRange(0, 4)) { case 0: { SharedObjectPointer newState = state->clone(true); static_cast(newState.data())->setFoo(randFloat()); @@ -303,6 +351,38 @@ static SharedObjectPointer mutate(const SharedObjectPointer& state) { objectMutationsPerformed++; return newState; } + case 3: { + SharedObjectPointer newState = state->clone(true); + QScriptValue oldValue = static_cast(newState.data())->getBizzle(); + QScriptValue newValue = ScriptCache::getInstance()->getEngine()->newObject(); + for (QScriptValueIterator it(oldValue); it.hasNext(); ) { + it.next(); + newValue.setProperty(it.scriptName(), it.value()); + } + switch (randIntInRange(0, 2)) { + case 0: { + QScriptValue oldArray = oldValue.property("foo"); + int oldLength = oldArray.property(ScriptCache::getInstance()->getLengthString()).toInt32(); + QScriptValue newArray = ScriptCache::getInstance()->getEngine()->newArray(oldLength); + for (int i = 0; i < oldLength; i++) { + newArray.setProperty(i, oldArray.property(i)); + } + newArray.setProperty(randIntInRange(0, oldLength - 1), createRandomScriptValue(true)); + break; + } + case 1: + newValue.setProperty("bar", QScriptValue(randFloat())); + break; + + default: + newValue.setProperty("baz", createRandomScriptValue(true)); + break; + } + static_cast(newState.data())->setBizzle(newValue); + scriptMutationsPerformed++; + objectMutationsPerformed++; + return newState; + } default: return state; } @@ -503,7 +583,10 @@ TestSharedObjectA::TestSharedObjectA(float foo, TestEnum baz, TestFlags bong) : _foo(foo), _baz(baz), _bong(bong) { - sharedObjectsCreated++; + sharedObjectsCreated++; + + _bizzle = ScriptCache::getInstance()->getEngine()->newObject(); + _bizzle.setProperty("foo", ScriptCache::getInstance()->getEngine()->newArray(4)); } TestSharedObjectA::~TestSharedObjectA() { diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 345ea624df..ac9eda2659 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -16,6 +16,7 @@ #include #include +#include class SequencedTestMessage; @@ -96,7 +97,8 @@ class TestSharedObjectA : public SharedObject { Q_PROPERTY(float foo READ getFoo WRITE setFoo NOTIFY fooChanged) Q_PROPERTY(TestEnum baz READ getBaz WRITE setBaz) Q_PROPERTY(TestFlags bong READ getBong WRITE setBong) - + Q_PROPERTY(QScriptValue bizzle READ getBizzle WRITE setBizzle) + public: enum TestEnum { FIRST_TEST_ENUM, SECOND_TEST_ENUM, THIRD_TEST_ENUM }; @@ -116,6 +118,9 @@ public: void setBong(TestFlags bong) { _bong = bong; } TestFlags getBong() const { return _bong; } + void setBizzle(const QScriptValue& bizzle) { _bizzle = bizzle; } + const QScriptValue& getBizzle() const { return _bizzle; } + signals: void fooChanged(float foo); @@ -125,6 +130,7 @@ private: float _foo; TestEnum _baz; TestFlags _bong; + QScriptValue _bizzle; }; DECLARE_ENUM_METATYPE(TestSharedObjectA, TestEnum) @@ -204,6 +210,7 @@ class TestMessageC : STREAM public TestMessageA { public: STREAM TestMessageB bong; + STREAM QScriptValue bizzle; }; DECLARE_STREAMABLE_METATYPE(TestMessageC)