From c938c595c017147b9210b6a0b03d0e891cfb05bb Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 10 Oct 2018 17:11:42 -0700 Subject: [PATCH 01/23] Detect failed conversion from QVariant to JSON better --- libraries/octree/src/Octree.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 3d387e0956..37dd4e410f 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -878,14 +878,24 @@ bool Octree::toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& elem return false; } - *doc = QJsonDocument::fromVariant(entityDescription); + + bool noEntities = entityDescription["Entities"].toList().empty(); + QJsonDocument jsonDocTree = QJsonDocument::fromVariant(entityDescription); + QJsonValue entitiesJson = jsonDocTree["Entities"]; + if (entitiesJson.isNull() || (entitiesJson.toArray().empty() && !noEntities)) { + // Json version of entities too large. + return false; + } else { + *doc = jsonDocTree; + } + return true; } bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool doGzip) { QJsonDocument doc; if (!toJSONDocument(&doc, element)) { - qCritical("Failed to convert Entities to QVariantMap while converting to json."); + qCritical("Failed to convert Entities to JSON document."); return false; } From 0eec0e376c8102ebc931f2cddbf9bdede0b082da Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 11 Oct 2018 18:13:26 -0700 Subject: [PATCH 02/23] Convert EntityTree direct to JSON, not via QJsonDocument --- libraries/entities/src/EntityTree.cpp | 12 +++++ libraries/entities/src/EntityTree.h | 2 + .../src/RecurseOctreeToJSONOperator.cpp | 45 +++++++++++++++++ .../src/RecurseOctreeToJSONOperator.h | 31 ++++++++++++ libraries/octree/src/Octree.cpp | 50 ++++++++++++++++++- libraries/octree/src/Octree.h | 2 + 6 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 libraries/entities/src/RecurseOctreeToJSONOperator.cpp create mode 100644 libraries/entities/src/RecurseOctreeToJSONOperator.h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index a7c88ddc7d..aa2167029d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -35,6 +35,7 @@ #include "QVariantGLM.h" #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" +#include "RecurseOctreeToJsonOperator.h" #include "LogHandler.h" #include "EntityEditFilters.h" #include "EntityDynamicFactoryInterface.h" @@ -2658,6 +2659,17 @@ bool EntityTree::readFromMap(QVariantMap& map) { return success; } +bool EntityTree::writeToJSON(QString & jsonString, const OctreeElementPointer & element) { + QScriptEngine scriptEngine; + RecurseOctreeToJSONOperator theOperator(element, &scriptEngine, jsonString); + withReadLock([&] { + recurseTreeWithOperator(&theOperator); + }); + + jsonString = theOperator.getJson(); + return true; +} + void EntityTree::resetClientEditStats() { _treeResetTime = usecTimestampNow(); _maxEditDelta = 0; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 2f971b8566..b852a695d8 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -224,6 +224,8 @@ public: virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) override; virtual bool readFromMap(QVariantMap& entityDescription) override; + virtual bool writeToJSON(QString& jsonString, const OctreeElementPointer& element) override; + glm::vec3 getContentsDimensions(); float getContentsLargestDimension(); diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp new file mode 100644 index 0000000000..2db1a2b228 --- /dev/null +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp @@ -0,0 +1,45 @@ +// +// RecurseOctreeToJSONOperator.cpp +// libraries/entities/src +// +// Created by Simon Walton on Oct 11, 2018. +// Copyright 2018 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 "RecurseOctreeToJSONOperator.h" + +#include "EntityItemProperties.h" + +RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer& top, QScriptEngine* engine, + QString jsonPrefix /* = QString() */) + : _top(top) + , _engine(engine) + , _json(jsonPrefix) +{ + _toStringMethod = _engine->evaluate("(function() { return JSON.stringify(this, null, ' ') })"); +} + +bool RecurseOctreeToJSONOperator::postRecursion(const OctreeElementPointer& element) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + + entityTreeElement->forEachEntity([&](const EntityItemPointer& entity) { processEntity(entity); } ); + return true; +} + +void RecurseOctreeToJSONOperator::processEntity(const EntityItemPointer& entity) { + QScriptValue qScriptValues = EntityItemNonDefaultPropertiesToScriptValue(_engine, entity->getProperties()); + if (comma) { + _json += ','; + }; + comma = true; + _json += "\n "; + + // Override default toString(): + qScriptValues.setProperty("toString", _toStringMethod); + QString jsonResult = qScriptValues.toString(); + //auto exceptionString2 = _engine->uncaughtException().toString(); + _json += jsonResult; +} diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.h b/libraries/entities/src/RecurseOctreeToJSONOperator.h new file mode 100644 index 0000000000..92ce6bfdca --- /dev/null +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.h @@ -0,0 +1,31 @@ +// +// RecurseOctreeToJSONOperator.h +// libraries/entities/src +// +// Created by Simon Walton on Oct 11, 2018. +// Copyright 2018 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 "EntityTree.h" + +class RecurseOctreeToJSONOperator : public RecurseOctreeOperator { +public: + RecurseOctreeToJSONOperator(const OctreeElementPointer& top, QScriptEngine* engine, QString jsonPrefix = QString()); + virtual bool preRecursion(const OctreeElementPointer& element) override { return true; }; + virtual bool postRecursion(const OctreeElementPointer& element) override; + + QString getJson() const { return _json; } + +private: + void processEntity(const EntityItemPointer& entity); + + const OctreeElementPointer& _top; + QScriptEngine* _engine; + QScriptValue _toStringMethod; + + QString _json; + bool comma { false }; +}; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 37dd4e410f..40d2449a38 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -51,7 +51,6 @@ #include "OctreeQueryNode.h" #include "OctreeUtils.h" - QVector PERSIST_EXTENSIONS = {"json", "json.gz"}; Octree::Octree(bool shouldReaverage) : @@ -892,13 +891,61 @@ bool Octree::toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& elem return true; } +bool Octree::toJSONString(QString& jsonString, const OctreeElementPointer& element) { + OctreeElementPointer top; + if (element) { + top = element; + } else { + top = _rootElement; + } + + jsonString += QString(R"({ + "DataVersion": %1, + "Entities": [)").arg(_persistDataVersion); + + writeToJSON(jsonString, top); + + // include the "bitstream" version + PacketType expectedType = expectedDataPacketType(); + PacketVersion expectedVersion = versionForPacketType(expectedType); + + jsonString += QString(R"( + ], + "Id": "%1", + "Version": %2 +} +)").arg(_persistID.toString()).arg((int)expectedVersion); + + return true; +} + bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool doGzip) { +#define HIFI_USE_DIRECT_TO_JSON +#ifdef HIFI_USE_DIRECT_TO_JSON + + QString jsonString; + toJSONString(jsonString); + + if (doGzip) { + if (!gzip(jsonString.toUtf8(), *data, -1)) { + qCritical("Unable to gzip data while saving to json."); + return false; + } + } else { + *data = jsonString.toUtf8(); + } + +#else + QJsonDocument doc; if (!toJSONDocument(&doc, element)) { qCritical("Failed to convert Entities to JSON document."); return false; } + QString jsonString; + toJSONString(jsonString); + if (doGzip) { QByteArray jsonData = doc.toJson(); @@ -909,6 +956,7 @@ bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool } else { *data = doc.toJson(); } +#endif // HIFI_USE_DIRECT_TO_JSON return true; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index a2b2f227cb..678d8aa5de 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -202,11 +202,13 @@ public: // Octree exporters bool toJSONDocument(QJsonDocument* doc, const OctreeElementPointer& element = nullptr); + bool toJSONString(QString& jsonString, const OctreeElementPointer& element = nullptr); bool toJSON(QByteArray* data, const OctreeElementPointer& element = nullptr, bool doGzip = false); bool writeToFile(const char* filename, const OctreeElementPointer& element = nullptr, QString persistAsFileType = "json.gz"); bool writeToJSONFile(const char* filename, const OctreeElementPointer& element = nullptr, bool doGzip = false); virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) = 0; + virtual bool writeToJSON(QString& jsonString, const OctreeElementPointer& element) = 0; // Octree importers bool readFromFile(const char* filename); From ad9f7f3a1d97d1a3fe766fcdb2b8190764d5e44b Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 12 Oct 2018 11:18:38 -0700 Subject: [PATCH 03/23] Include the skipDefaults option in case we ever need it Also print errors from parsing entities file; other tweaks --- libraries/entities/src/EntityTree.cpp | 2 +- .../entities/src/RecurseOctreeToJSONOperator.cpp | 12 +++++++----- libraries/entities/src/RecurseOctreeToJSONOperator.h | 3 ++- libraries/octree/src/Octree.cpp | 2 +- libraries/octree/src/OctreeDataUtils.cpp | 6 +++++- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index aa2167029d..19182d8b22 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2659,7 +2659,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { return success; } -bool EntityTree::writeToJSON(QString & jsonString, const OctreeElementPointer & element) { +bool EntityTree::writeToJSON(QString& jsonString, const OctreeElementPointer& element) { QScriptEngine scriptEngine; RecurseOctreeToJSONOperator theOperator(element, &scriptEngine, jsonString); withReadLock([&] { diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp index 2db1a2b228..d67e7176a3 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp @@ -10,14 +10,14 @@ // #include "RecurseOctreeToJSONOperator.h" - #include "EntityItemProperties.h" RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer& top, QScriptEngine* engine, - QString jsonPrefix /* = QString() */) + QString jsonPrefix /* = QString() */, bool skipDefaults /* = true */) : _top(top) , _engine(engine) , _json(jsonPrefix) + , _skipDefaults(skipDefaults) { _toStringMethod = _engine->evaluate("(function() { return JSON.stringify(this, null, ' ') })"); } @@ -30,7 +30,10 @@ bool RecurseOctreeToJSONOperator::postRecursion(const OctreeElementPointer& elem } void RecurseOctreeToJSONOperator::processEntity(const EntityItemPointer& entity) { - QScriptValue qScriptValues = EntityItemNonDefaultPropertiesToScriptValue(_engine, entity->getProperties()); + QScriptValue qScriptValues = _skipDefaults + ? EntityItemNonDefaultPropertiesToScriptValue(_engine, entity->getProperties()) + : EntityItemPropertiesToScriptValue(_engine, entity->getProperties()); + if (comma) { _json += ','; }; @@ -39,7 +42,6 @@ void RecurseOctreeToJSONOperator::processEntity(const EntityItemPointer& entity) // Override default toString(): qScriptValues.setProperty("toString", _toStringMethod); - QString jsonResult = qScriptValues.toString(); + _json += qScriptValues.toString(); //auto exceptionString2 = _engine->uncaughtException().toString(); - _json += jsonResult; } diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.h b/libraries/entities/src/RecurseOctreeToJSONOperator.h index 92ce6bfdca..f661e08d5b 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.h +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.h @@ -13,7 +13,7 @@ class RecurseOctreeToJSONOperator : public RecurseOctreeOperator { public: - RecurseOctreeToJSONOperator(const OctreeElementPointer& top, QScriptEngine* engine, QString jsonPrefix = QString()); + RecurseOctreeToJSONOperator(const OctreeElementPointer& top, QScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true); virtual bool preRecursion(const OctreeElementPointer& element) override { return true; }; virtual bool postRecursion(const OctreeElementPointer& element) override; @@ -27,5 +27,6 @@ private: QScriptValue _toStringMethod; QString _json; + const bool _skipDefaults; bool comma { false }; }; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 40d2449a38..d583901b41 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -956,8 +956,8 @@ bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool } else { *data = doc.toJson(); } -#endif // HIFI_USE_DIRECT_TO_JSON +#endif // HIFI_USE_DIRECT_TO_JSON return true; } diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index 44a56fe97a..4b1700d593 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -35,7 +35,11 @@ bool readOctreeFile(QString path, QJsonDocument* doc) { jsonData = data; } - *doc = QJsonDocument::fromJson(jsonData); + QJsonParseError parserError; + *doc = QJsonDocument::fromJson(jsonData, &parserError); + if (parserError.error != QJsonParseError::NoError) { + qWarning() << "Error reading JSON file" << path << "-" << parserError.errorString(); + } return !doc->isNull(); } From e86d1691ce00bc99cdfa556a15bbda51c59bfef9 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 12 Oct 2018 17:26:46 -0700 Subject: [PATCH 04/23] Cache the local Entities file to avoid reading 2 or 3 times --- libraries/octree/src/OctreeDataUtils.cpp | 42 ++++++++++---------- libraries/octree/src/OctreeDataUtils.h | 1 + libraries/octree/src/OctreePersistThread.cpp | 39 +++++++++++++----- libraries/octree/src/OctreePersistThread.h | 1 + 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index 4b1700d593..a2b0d78209 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -18,30 +18,32 @@ #include #include -// Reads octree file and parses it into a QJsonDocument. Handles both gzipped and non-gzipped files. -// Returns true if the file was successfully opened and parsed, otherwise false. -// Example failures: file does not exist, gzipped file cannot be unzipped, invalid JSON. -bool readOctreeFile(QString path, QJsonDocument* doc) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Cannot open json file for reading: " << path; - return false; - } +namespace { + // Reads octree file and parses it into a QJsonDocument. Handles both gzipped and non-gzipped files. + // Returns true if the file was successfully opened and parsed, otherwise false. + // Example failures: file does not exist, gzipped file cannot be unzipped, invalid JSON. + bool readOctreeFile(QString path, QJsonDocument* doc) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Cannot open json file for reading: " << path; + return false; + } - QByteArray data = file.readAll(); - QByteArray jsonData; + QByteArray data = file.readAll(); + QByteArray jsonData; - if (!gunzip(data, jsonData)) { - jsonData = data; - } + if (!gunzip(data, jsonData)) { + jsonData = data; + } - QJsonParseError parserError; - *doc = QJsonDocument::fromJson(jsonData, &parserError); - if (parserError.error != QJsonParseError::NoError) { - qWarning() << "Error reading JSON file" << path << "-" << parserError.errorString(); + QJsonParseError parserError; + *doc = QJsonDocument::fromJson(jsonData, &parserError); + if (parserError.error != QJsonParseError::NoError) { + qWarning() << "Error reading JSON file" << path << "-" << parserError.errorString(); + } + return !doc->isNull(); } - return !doc->isNull(); -} +} // Anon namespace. bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) { if (root.contains("Id") && root.contains("DataVersion") && root.contains("Version")) { diff --git a/libraries/octree/src/OctreeDataUtils.h b/libraries/octree/src/OctreeDataUtils.h index 9060e7b460..ef96415044 100644 --- a/libraries/octree/src/OctreeDataUtils.h +++ b/libraries/octree/src/OctreeDataUtils.h @@ -46,6 +46,7 @@ public: }; class RawEntityData : public RawOctreeData { +public: PacketType dataPacketType() const override; void readSubclassData(const QJsonObject& root) override; void writeSubclassData(QJsonObject& root) const override; diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 8b1d766418..0c78a1a797 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "OctreeLogging.h" #include "OctreeUtils.h" @@ -72,14 +73,27 @@ void OctreePersistThread::start() { OctreeUtils::RawOctreeData data; qCDebug(octree) << "Reading octree data from" << _filename; - if (data.readOctreeDataInfoFromFile(_filename)) { - qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; - packet->writePrimitive(true); - auto id = data.id.toRfc4122(); - packet->write(id); - packet->writePrimitive(data.version); + QFile file(_filename); + if (file.open(QIODevice::ReadOnly)) { + QByteArray jsonData(file.readAll()); + file.close(); + if (!gunzip(jsonData, _cachedJSONData)) { + _cachedJSONData = jsonData; + } + + if (data.readOctreeDataInfoFromData(_cachedJSONData)) { + qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; + packet->writePrimitive(true); + auto id = data.id.toRfc4122(); + packet->write(id); + packet->writePrimitive(data.version); + } else { + _cachedJSONData.clear(); + qCWarning(octree) << "No octree data found"; + packet->writePrimitive(false); + } } else { - qCWarning(octree) << "No octree data found"; + qCWarning(octree) << "Couldn't access file" << _filename << file.errorString(); packet->writePrimitive(false); } @@ -99,6 +113,7 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointerreadAll(); replaceData(replacementData); hasValidOctreeData = data.readOctreeDataInfoFromFile(_filename); @@ -108,7 +123,7 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointerwithWriteLock([&] { PerformanceWarning warn(true, "Loading Octree File", true); - persistentFileRead = _tree->readFromFile(_filename.toLocal8Bit().constData()); + if (_cachedJSONData.isEmpty()) { + persistentFileRead = _tree->readFromFile(_filename.toLocal8Bit().constData()); + } else { + QDataStream jsonStream(_cachedJSONData); + persistentFileRead = _tree->readFromStream(-1, QDataStream(_cachedJSONData)); + } _tree->pruneTree(); }); + _cachedJSONData.clear(); quint64 loadDone = usecTimestampNow(); _loadTimeUSecs = loadDone - loadStarted; diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index 0044a8fa5a..6a38a21a84 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -78,6 +78,7 @@ private: quint64 _lastTimeDebug; QString _persistAsFileType; + QByteArray _cachedJSONData; }; #endif // hifi_OctreePersistThread_h From f5f34e8e7de96dc49766c98af48abd92a1435429 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 15 Oct 2018 18:14:40 -0700 Subject: [PATCH 05/23] Pasre Entities JSON with our own top-level parser Use QJsonDocument for each individual entity. --- libraries/octree/src/Octree.cpp | 8 + .../octree/src/OctreeEntitiesFileParser.cpp | 218 ++++++++++++++++++ .../octree/src/OctreeEntitiesFileParser.h | 38 +++ 3 files changed, 264 insertions(+) create mode 100644 libraries/octree/src/OctreeEntitiesFileParser.cpp create mode 100644 libraries/octree/src/OctreeEntitiesFileParser.h diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index d583901b41..30108d973e 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -50,6 +50,7 @@ #include "OctreeLogging.h" #include "OctreeQueryNode.h" #include "OctreeUtils.h" +#include "OctreeEntitiesFileParser.h" QVector PERSIST_EXTENSIONS = {"json", "json.gz"}; @@ -827,12 +828,19 @@ bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, jsonBuffer += QByteArray(rawData, got); } + OctreeEntitiesFileParser octreeParser; + octreeParser.setEntitiesString(jsonBuffer); + QVariantMap asMap; + bool parseSuccess = octreeParser.parseEntities(asMap); + + /* QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer); if (!marketplaceID.isEmpty()) { asDocument = addMarketplaceIDToDocumentEntities(asDocument, marketplaceID); } QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); + */ bool success = readFromMap(asMap); delete[] rawData; return success; diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp new file mode 100644 index 0000000000..14380de835 --- /dev/null +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -0,0 +1,218 @@ +// +// OctreeEntititesFileParser.cpp +// libraries/octree/src +// +// Created by Simon Walton on Oct 15, 2018. +// Copyright 2018 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 +#include +#include +#include +#include + +#include "OctreeEntitiesFileParser.h" + +using std::string; + +OctreeEntitiesFileParser::OctreeEntitiesFileParser() { +} + +std::string OctreeEntitiesFileParser::getErrorString() const { + std::ostringstream err; + if (_errorString.size() != 0) { + err << "Error: Line " << _line << ", byte position " << _position << ": " << _errorString; + }; + + return err.str(); +} + +void OctreeEntitiesFileParser::setEntitiesString(const QByteArray& entitiesContents) { + _entitiesContents = entitiesContents; + _entitiesLength = _entitiesContents.length(); + _position = 0; + _line = 1; +} + +bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { + if (nextToken() != '{') { + _errorString = "Text before start of object"; + return false; + } + + bool gotDataVersion = false; + bool gotEntities = false; + bool gotId = false; + bool gotVersion = false; + + while (!(gotDataVersion && gotEntities && gotId && gotVersion)) { + if (nextToken() != '"') { + _errorString = "Incorrect key string"; + return false; + } + + string key = readString(); + if (key.size() == 0) { + _errorString = "Missing object key"; + return false; + } + + if (nextToken() != ':') { + _errorString = "Ill-formed id/value entry"; + return false; + } + + if (key == "DataVersion") { + if (gotDataVersion) { + _errorString = "Duplicate DataVersion entries"; + return false; + } + + int dataVersionValue = readInteger(); + parsedEntities["DataVersion"] = dataVersionValue; + gotDataVersion = true; + } else if (key == "Entities") { + if (gotEntities) { + _errorString = "Duplicate Entities entries"; + return false; + } + + QVariantList entitiesValue; + if (!readEntitiesArray(entitiesValue)) { + return false; + } + + parsedEntities["Entities"] = entitiesValue; + gotEntities = true; + } else if (key == "Id") { + if (gotId) { + _errorString = "Duplicate Id entries"; + return false; + } + + if (nextToken() != '"') { + _errorString = "Invalid Id value"; + return false; + }; + string idString = readString(); + if (idString.size() == 0) { + _errorString = "Invalid Id string"; + return false; + } + QUuid idValue = QUuid::fromString(QLatin1String(idString.c_str()) ); + if (idValue.isNull()) { + _errorString = "Id value invalid UUID string: " + idString; + return false; + } + + parsedEntities["Id"] = idValue; + gotId = true; + } else if (key == "Version") { + if (gotVersion) { + _errorString = "Duplicate Version entries"; + return false; + } + + int versionValue = readInteger(); + parsedEntities["Version"] = versionValue; + gotVersion = true; + } else { + _errorString = "Unrecognized key name: " + key; + return false; + } + + if (gotDataVersion && gotEntities && gotId && gotVersion) { + break; + } else if (nextToken() != ',') { + _errorString = "Id/value incorrectly terminated"; + return false; + } + } + + if (nextToken() != '}' || nextToken() != -1) { + _errorString = "Ill-formed end of object"; + return false; + } + + return true; +} + +int OctreeEntitiesFileParser::nextToken() { + while (_position < _entitiesLength) { + char c = _entitiesContents[_position++]; + if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { + return c; + } + if (c == '\n') { + ++_line; + } + } + + return -1; +} + +string OctreeEntitiesFileParser::readString() { + string returnString; + while (_position < _entitiesLength) { + char c = _entitiesContents[_position++]; + if (c == '"') { + break; + } else { + returnString.push_back(c); + } + } + + return returnString; +} + +int OctreeEntitiesFileParser::readInteger() { + const char* currentPosition = _entitiesContents.constData() + _position; + int i = atoi(currentPosition); + + int token; + do { + token = nextToken(); + } while (token == '-' || token == '+' || (token >= '0' && token <= '9')); + + --_position; + return i; +} + +bool OctreeEntitiesFileParser::readEntitiesArray(QVariantList& entitiesArray) { + if (nextToken() != '[') { + _errorString = "Entities entry is not an array"; + return false; + } + + while (true) { + QJsonParseError parseError; + QByteArray entitiesJson(_entitiesContents.right(_entitiesLength - _position)); + QJsonDocument entity = QJsonDocument::fromJson(entitiesJson, &parseError); + if (parseError.error != QJsonParseError::GarbageAtEnd) { + _errorString = "Ill-formed entity array"; + return false; + } + int entityLength = parseError.offset; + entitiesJson.truncate(entityLength); + _position += entityLength; + + entity = QJsonDocument::fromJson(entitiesJson, &parseError); + if (parseError.error != QJsonParseError::NoError) { + _errorString = "Entity item parse error"; + return false; + } + entitiesArray.append(entity.object()); + char c = nextToken(); + if (c == ']') { + return true; + } else if (c != ',') { + _errorString = "Entity array item incorrectly terminated"; + return false; + } + } + return true; +} diff --git a/libraries/octree/src/OctreeEntitiesFileParser.h b/libraries/octree/src/OctreeEntitiesFileParser.h new file mode 100644 index 0000000000..562a18e833 --- /dev/null +++ b/libraries/octree/src/OctreeEntitiesFileParser.h @@ -0,0 +1,38 @@ +// +// OctreeEntititesFileParser.h +// libraries/octree/src +// +// Created by Simon Walton on Oct 15, 2018. +// Copyright 2018 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 +// + +#ifndef hifi_OctreeEntitiesFileParser_h +#define hifi_OctreeEntitiesFileParser_h + +#include +#include + +class OctreeEntitiesFileParser { +public: + OctreeEntitiesFileParser(); + void setEntitiesString(const QByteArray& entitiesContents); + bool parseEntities(QVariantMap& parsedEntities); + std::string getErrorString() const; + +private: + int nextToken(); + std::string readString(); + int readInteger(); + bool readEntitiesArray(QVariantList& entitiesArray); + + QByteArray _entitiesContents; + int _position { 0 }; + int _line { 1 }; + int _entitiesLength { 0 }; + std::string _errorString; +}; + +#endif // hifi_OctreeEntitiesFileParser_h From c031769c67d0c5391b7fd13cd937a8576e8d20e7 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 16 Oct 2018 18:08:07 -0700 Subject: [PATCH 06/23] Faster JSON entities parser --- libraries/octree/src/OctreeDataUtils.cpp | 20 +++++-- libraries/octree/src/OctreeDataUtils.h | 1 + .../octree/src/OctreeEntitiesFileParser.cpp | 58 +++++++++++++++---- .../octree/src/OctreeEntitiesFileParser.h | 3 + 4 files changed, 67 insertions(+), 15 deletions(-) diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index a2b0d78209..7bbad652f8 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html #include "OctreeDataUtils.h" +#include "OctreeEntitiesFileParser.h" #include #include @@ -55,19 +56,30 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) { return true; } +bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromMap(QVariantMap map) { + if (map.contains("Id") && map.contains("DataVersion") && map.contains("Version")) { + id = map["Id"].toUuid(); + dataVersion = map["DataVersion"].toInt(); + version = map["Version"].toInt(); + } + return true; +} + bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromData(QByteArray data) { QByteArray jsonData; if (gunzip(data, jsonData)) { data = jsonData; } - auto doc = QJsonDocument::fromJson(data); - if (doc.isNull()) { + OctreeEntitiesFileParser jsonParser; + jsonParser.setEntitiesString(data); + QVariantMap entitiesMap; + if (!jsonParser.parseEntities(entitiesMap)) { + qCritical() << "Can't parse Entities JSON: " << jsonParser.getErrorString().c_str(); return false; } - auto root = doc.object(); - return readOctreeDataInfoFromJSON(root); + return readOctreeDataInfoFromMap(entitiesMap); } // Reads octree file and parses it into a RawOctreeData object. diff --git a/libraries/octree/src/OctreeDataUtils.h b/libraries/octree/src/OctreeDataUtils.h index ef96415044..236d280bbf 100644 --- a/libraries/octree/src/OctreeDataUtils.h +++ b/libraries/octree/src/OctreeDataUtils.h @@ -43,6 +43,7 @@ public: bool readOctreeDataInfoFromData(QByteArray data); bool readOctreeDataInfoFromFile(QString path); bool readOctreeDataInfoFromJSON(QJsonObject root); + bool readOctreeDataInfoFromMap(QVariantMap map); }; class RawEntityData : public RawOctreeData { diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index 14380de835..a0e43d0810 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -189,23 +189,25 @@ bool OctreeEntitiesFileParser::readEntitiesArray(QVariantList& entitiesArray) { } while (true) { - QJsonParseError parseError; - QByteArray entitiesJson(_entitiesContents.right(_entitiesLength - _position)); - QJsonDocument entity = QJsonDocument::fromJson(entitiesJson, &parseError); - if (parseError.error != QJsonParseError::GarbageAtEnd) { - _errorString = "Ill-formed entity array"; + if (nextToken() != '{') { + _errorString = "Entity array item is not an object"; + return false; + } + int matchingBrace = findMatchingBrace(); + if (matchingBrace < 0) { + _errorString = "Unterminated entity object"; return false; } - int entityLength = parseError.offset; - entitiesJson.truncate(entityLength); - _position += entityLength; - entity = QJsonDocument::fromJson(entitiesJson, &parseError); - if (parseError.error != QJsonParseError::NoError) { - _errorString = "Entity item parse error"; + QByteArray jsonEntity = _entitiesContents.mid(_position - 1, matchingBrace - _position + 1); + QJsonDocument entity = QJsonDocument::fromJson(jsonEntity); + if (entity.isNull()) { + _errorString = "Ill-formed entity"; return false; } + entitiesArray.append(entity.object()); + _position = matchingBrace; char c = nextToken(); if (c == ']') { return true; @@ -216,3 +218,37 @@ bool OctreeEntitiesFileParser::readEntitiesArray(QVariantList& entitiesArray) { } return true; } + +int OctreeEntitiesFileParser::findMatchingBrace() const { + int index = _position; + int nestCount = 1; + while (index < _entitiesLength && nestCount != 0) { + switch (_entitiesContents[index++]) { + case '{': + ++nestCount; + break; + + case '}': + --nestCount; + break; + + case '"': + // Skip string + while (index < _entitiesLength) { + if (_entitiesContents[index] == '"') { + ++index; + break; + } else if (_entitiesContents[index] == '\\' && _entitiesContents[++index] == 'u') { + index += 4; + } + ++index; + } + break; + + default: + break; + } + } + + return nestCount == 0 ? index : -1; +} diff --git a/libraries/octree/src/OctreeEntitiesFileParser.h b/libraries/octree/src/OctreeEntitiesFileParser.h index 562a18e833..2daeb7e561 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.h +++ b/libraries/octree/src/OctreeEntitiesFileParser.h @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// Parse the top-level of the Models object ourselves - use QJsonDocument for each Entity object. + #ifndef hifi_OctreeEntitiesFileParser_h #define hifi_OctreeEntitiesFileParser_h @@ -27,6 +29,7 @@ private: std::string readString(); int readInteger(); bool readEntitiesArray(QVariantList& entitiesArray); + int findMatchingBrace() const; QByteArray _entitiesContents; int _position { 0 }; From be279f08dc3d8819002125bb5e36eae9c121e0e8 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 17 Oct 2018 15:43:38 -0700 Subject: [PATCH 07/23] Restore the addMarketplaceIDToDocumentEntities() hack Fixes for Linux compiles; other improvements --- libraries/entities/src/EntityTree.cpp | 2 +- libraries/octree/src/Octree.cpp | 35 +++++++++---------- .../octree/src/OctreeEntitiesFileParser.cpp | 6 ++-- .../octree/src/OctreeEntitiesFileParser.h | 2 +- libraries/octree/src/OctreePersistThread.cpp | 2 +- 5 files changed, 22 insertions(+), 25 deletions(-) diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 19182d8b22..4cac2205a4 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -35,7 +35,7 @@ #include "QVariantGLM.h" #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" -#include "RecurseOctreeToJsonOperator.h" +#include "RecurseOctreeToJSONOperator.h" #include "LogHandler.h" #include "EntityEditFilters.h" #include "EntityDynamicFactoryInterface.h" diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 30108d973e..bbf1e44d08 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -785,28 +785,26 @@ bool Octree::readFromStream(uint64_t streamLength, QDataStream& inputStream, con } +namespace { // hack to get the marketplace id into the entities. We will create a way to get this from a hash of // the entity later, but this helps us move things along for now -QJsonDocument addMarketplaceIDToDocumentEntities(QJsonDocument& doc, const QString& marketplaceID) { +QVariantMap addMarketplaceIDToDocumentEntities(QVariantMap& doc, const QString& marketplaceID) { if (!marketplaceID.isEmpty()) { - QJsonDocument newDoc; - QJsonObject rootObj = doc.object(); - QJsonArray newEntitiesArray; + QVariantList newEntitiesArray; // build a new entities array - auto entitiesArray = rootObj["Entities"].toArray(); - for(auto it = entitiesArray.begin(); it != entitiesArray.end(); it++) { - auto entity = (*it).toObject(); + auto entitiesArray = doc["Entities"].toList(); + for (auto it = entitiesArray.begin(); it != entitiesArray.end(); it++) { + auto entity = (*it).toMap(); entity["marketplaceID"] = marketplaceID; newEntitiesArray.append(entity); } - rootObj["Entities"] = newEntitiesArray; - newDoc.setObject(rootObj); - return newDoc; + doc["Entities"] = newEntitiesArray; } return doc; } +} // Unnamed namepsace const int READ_JSON_BUFFER_SIZE = 2048; bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID /*=""*/) { @@ -831,16 +829,15 @@ bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, OctreeEntitiesFileParser octreeParser; octreeParser.setEntitiesString(jsonBuffer); QVariantMap asMap; - bool parseSuccess = octreeParser.parseEntities(asMap); - - /* - QJsonDocument asDocument = QJsonDocument::fromJson(jsonBuffer); - if (!marketplaceID.isEmpty()) { - asDocument = addMarketplaceIDToDocumentEntities(asDocument, marketplaceID); + if (!octreeParser.parseEntities(asMap)) { + qCritical() << "Couldn't parse Entities JSON:" << octreeParser.getErrorString().c_str(); + return false; } - QVariant asVariant = asDocument.toVariant(); - QVariantMap asMap = asVariant.toMap(); - */ + + if (!marketplaceID.isEmpty()) { + addMarketplaceIDToDocumentEntities(asMap, marketplaceID); + } + bool success = readFromMap(asMap); delete[] rawData; return success; diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index a0e43d0810..0b75991fd2 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -10,10 +10,10 @@ // #include +#include #include #include #include -#include #include "OctreeEntitiesFileParser.h" @@ -171,12 +171,12 @@ string OctreeEntitiesFileParser::readString() { int OctreeEntitiesFileParser::readInteger() { const char* currentPosition = _entitiesContents.constData() + _position; - int i = atoi(currentPosition); + int i = std::atoi(currentPosition); int token; do { token = nextToken(); - } while (token == '-' || token == '+' || (token >= '0' && token <= '9')); + } while (token == '-' || token == '+' || std::isdigit(token)); --_position; return i; diff --git a/libraries/octree/src/OctreeEntitiesFileParser.h b/libraries/octree/src/OctreeEntitiesFileParser.h index 2daeb7e561..e4e82f0e66 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.h +++ b/libraries/octree/src/OctreeEntitiesFileParser.h @@ -14,8 +14,8 @@ #ifndef hifi_OctreeEntitiesFileParser_h #define hifi_OctreeEntitiesFileParser_h -#include #include +#include class OctreeEntitiesFileParser { public: diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 0c78a1a797..2717be593b 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -158,7 +158,7 @@ void OctreePersistThread::handleOctreeDataFileReply(QSharedPointerreadFromFile(_filename.toLocal8Bit().constData()); } else { QDataStream jsonStream(_cachedJSONData); - persistentFileRead = _tree->readFromStream(-1, QDataStream(_cachedJSONData)); + persistentFileRead = _tree->readFromStream(-1, jsonStream); } _tree->pruneTree(); }); From 6f6c92e6475503ae6fd05fd87f440b04700e5321 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 30 Oct 2018 09:40:08 -0700 Subject: [PATCH 08/23] Remove unused variable --- libraries/entities/src/RecurseOctreeToJSONOperator.cpp | 5 ++--- libraries/entities/src/RecurseOctreeToJSONOperator.h | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp index d67e7176a3..a865009566 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp @@ -12,10 +12,9 @@ #include "RecurseOctreeToJSONOperator.h" #include "EntityItemProperties.h" -RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer& top, QScriptEngine* engine, +RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, QString jsonPrefix /* = QString() */, bool skipDefaults /* = true */) - : _top(top) - , _engine(engine) + : _engine(engine) , _json(jsonPrefix) , _skipDefaults(skipDefaults) { diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.h b/libraries/entities/src/RecurseOctreeToJSONOperator.h index f661e08d5b..d0f03d02eb 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.h +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.h @@ -13,7 +13,7 @@ class RecurseOctreeToJSONOperator : public RecurseOctreeOperator { public: - RecurseOctreeToJSONOperator(const OctreeElementPointer& top, QScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true); + RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true); virtual bool preRecursion(const OctreeElementPointer& element) override { return true; }; virtual bool postRecursion(const OctreeElementPointer& element) override; @@ -22,7 +22,6 @@ public: private: void processEntity(const EntityItemPointer& entity); - const OctreeElementPointer& _top; QScriptEngine* _engine; QScriptValue _toStringMethod; From d183968175a98782385a511ebd0a17791943a6c4 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 8 Nov 2018 18:20:20 -0800 Subject: [PATCH 09/23] Add skipThoseWithBadParents functionality to octree visitor; reviewer suggestions --- .../src/RecurseOctreeToJSONOperator.cpp | 18 ++++++++----- .../src/RecurseOctreeToJSONOperator.h | 6 +++-- libraries/octree/src/Octree.cpp | 26 ------------------- libraries/octree/src/OctreeDataUtils.cpp | 2 +- libraries/octree/src/OctreeDataUtils.h | 2 +- .../octree/src/OctreeEntitiesFileParser.cpp | 5 +--- .../octree/src/OctreeEntitiesFileParser.h | 1 - 7 files changed, 18 insertions(+), 42 deletions(-) diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp index a865009566..b64a700abc 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.cpp +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.cpp @@ -13,10 +13,11 @@ #include "EntityItemProperties.h" RecurseOctreeToJSONOperator::RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, - QString jsonPrefix /* = QString() */, bool skipDefaults /* = true */) - : _engine(engine) - , _json(jsonPrefix) - , _skipDefaults(skipDefaults) + QString jsonPrefix, bool skipDefaults, bool skipThoseWithBadParents): + _engine(engine), + _json(jsonPrefix), + _skipDefaults(skipDefaults), + _skipThoseWithBadParents(skipThoseWithBadParents) { _toStringMethod = _engine->evaluate("(function() { return JSON.stringify(this, null, ' ') })"); } @@ -29,18 +30,21 @@ bool RecurseOctreeToJSONOperator::postRecursion(const OctreeElementPointer& elem } void RecurseOctreeToJSONOperator::processEntity(const EntityItemPointer& entity) { + if (_skipThoseWithBadParents && !entity->isParentIDValid()) { + return; // we weren't able to resolve a parent from _parentID, so don't save this entity. + } + QScriptValue qScriptValues = _skipDefaults ? EntityItemNonDefaultPropertiesToScriptValue(_engine, entity->getProperties()) : EntityItemPropertiesToScriptValue(_engine, entity->getProperties()); - if (comma) { + if (_comma) { _json += ','; }; - comma = true; + _comma = true; _json += "\n "; // Override default toString(): qScriptValues.setProperty("toString", _toStringMethod); _json += qScriptValues.toString(); - //auto exceptionString2 = _engine->uncaughtException().toString(); } diff --git a/libraries/entities/src/RecurseOctreeToJSONOperator.h b/libraries/entities/src/RecurseOctreeToJSONOperator.h index d0f03d02eb..a1d388ed22 100644 --- a/libraries/entities/src/RecurseOctreeToJSONOperator.h +++ b/libraries/entities/src/RecurseOctreeToJSONOperator.h @@ -13,7 +13,8 @@ class RecurseOctreeToJSONOperator : public RecurseOctreeOperator { public: - RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true); + RecurseOctreeToJSONOperator(const OctreeElementPointer&, QScriptEngine* engine, QString jsonPrefix = QString(), bool skipDefaults = true, + bool skipThoseWithBadParents = false); virtual bool preRecursion(const OctreeElementPointer& element) override { return true; }; virtual bool postRecursion(const OctreeElementPointer& element) override; @@ -27,5 +28,6 @@ private: QString _json; const bool _skipDefaults; - bool comma { false }; + bool _skipThoseWithBadParents; + bool _comma { false }; }; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index bbf1e44d08..cfa207dbf8 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -925,9 +925,6 @@ bool Octree::toJSONString(QString& jsonString, const OctreeElementPointer& eleme } bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool doGzip) { -#define HIFI_USE_DIRECT_TO_JSON -#ifdef HIFI_USE_DIRECT_TO_JSON - QString jsonString; toJSONString(jsonString); @@ -940,29 +937,6 @@ bool Octree::toJSON(QByteArray* data, const OctreeElementPointer& element, bool *data = jsonString.toUtf8(); } -#else - - QJsonDocument doc; - if (!toJSONDocument(&doc, element)) { - qCritical("Failed to convert Entities to JSON document."); - return false; - } - - QString jsonString; - toJSONString(jsonString); - - if (doGzip) { - QByteArray jsonData = doc.toJson(); - - if (!gzip(jsonData, *data, -1)) { - qCritical("Unable to gzip data while saving to json."); - return false; - } - } else { - *data = doc.toJson(); - } - -#endif // HIFI_USE_DIRECT_TO_JSON return true; } diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index 7bbad652f8..a94fa31800 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -56,7 +56,7 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) { return true; } -bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromMap(QVariantMap map) { +bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromMap(const QVariantMap& map) { if (map.contains("Id") && map.contains("DataVersion") && map.contains("Version")) { id = map["Id"].toUuid(); dataVersion = map["DataVersion"].toInt(); diff --git a/libraries/octree/src/OctreeDataUtils.h b/libraries/octree/src/OctreeDataUtils.h index 236d280bbf..0dfa99a9a0 100644 --- a/libraries/octree/src/OctreeDataUtils.h +++ b/libraries/octree/src/OctreeDataUtils.h @@ -43,7 +43,7 @@ public: bool readOctreeDataInfoFromData(QByteArray data); bool readOctreeDataInfoFromFile(QString path); bool readOctreeDataInfoFromJSON(QJsonObject root); - bool readOctreeDataInfoFromMap(QVariantMap map); + bool readOctreeDataInfoFromMap(const QVariantMap& map); }; class RawEntityData : public RawOctreeData { diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index 0b75991fd2..72853947c6 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -19,9 +19,6 @@ using std::string; -OctreeEntitiesFileParser::OctreeEntitiesFileParser() { -} - std::string OctreeEntitiesFileParser::getErrorString() const { std::ostringstream err; if (_errorString.size() != 0) { @@ -86,7 +83,7 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { return false; } - parsedEntities["Entities"] = entitiesValue; + parsedEntities["Entities"] = std::move(entitiesValue); gotEntities = true; } else if (key == "Id") { if (gotId) { diff --git a/libraries/octree/src/OctreeEntitiesFileParser.h b/libraries/octree/src/OctreeEntitiesFileParser.h index e4e82f0e66..bc51896b18 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.h +++ b/libraries/octree/src/OctreeEntitiesFileParser.h @@ -19,7 +19,6 @@ class OctreeEntitiesFileParser { public: - OctreeEntitiesFileParser(); void setEntitiesString(const QByteArray& entitiesContents); bool parseEntities(QVariantMap& parsedEntities); std::string getErrorString() const; From 8c347fae7082a746d594f3a3b5fb743618addcc8 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 12 Nov 2018 12:45:01 -0800 Subject: [PATCH 10/23] Fixes for domain server use of changed OctreeDataUtils --- libraries/octree/src/OctreeDataUtils.cpp | 33 +++++++++++++++++++----- libraries/octree/src/OctreeDataUtils.h | 3 +++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index a94fa31800..255933a783 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -44,6 +44,7 @@ namespace { } return !doc->isNull(); } + } // Anon namespace. bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) { @@ -62,6 +63,7 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromMap(const QVariantMap& ma dataVersion = map["DataVersion"].toInt(); version = map["Version"].toInt(); } + readSubclassData(map); return true; } @@ -85,13 +87,20 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromData(QByteArray data) { // Reads octree file and parses it into a RawOctreeData object. // Returns false if readOctreeFile fails. bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromFile(QString path) { - QJsonDocument doc; - if (!readOctreeFile(path, &doc)) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Cannot open json file for reading: " << path; return false; } - auto root = doc.object(); - return readOctreeDataInfoFromJSON(root); + QByteArray data = file.readAll(); + QByteArray jsonData; + + if (!gunzip(data, jsonData)) { + jsonData = data; + } + + return readOctreeDataInfoFromData(jsonData); } QByteArray OctreeUtils::RawOctreeData::toByteArray() { @@ -139,8 +148,18 @@ void OctreeUtils::RawEntityData::readSubclassData(const QJsonObject& root) { } } -void OctreeUtils::RawEntityData::writeSubclassData(QJsonObject& root) const { - root["Entities"] = entityData; +void OctreeUtils::RawEntityData::readSubclassData(const QVariantMap& root) { + variantEntityData = root["Entities"].toList(); } -PacketType OctreeUtils::RawEntityData::dataPacketType() const { return PacketType::EntityData; } \ No newline at end of file +void OctreeUtils::RawEntityData::writeSubclassData(QJsonObject& root) const { + //root["Entities"] = entityData; + QJsonArray entitiesJsonArray; + for (auto entityIter = variantEntityData.begin(); entityIter != variantEntityData.end(); ++entityIter) { + entitiesJsonArray += entityIter->toJsonObject(); + } + + root["Entities"] = entitiesJsonArray; +} + +PacketType OctreeUtils::RawEntityData::dataPacketType() const { return PacketType::EntityData; } diff --git a/libraries/octree/src/OctreeDataUtils.h b/libraries/octree/src/OctreeDataUtils.h index 0dfa99a9a0..20092cd451 100644 --- a/libraries/octree/src/OctreeDataUtils.h +++ b/libraries/octree/src/OctreeDataUtils.h @@ -34,6 +34,7 @@ public: virtual PacketType dataPacketType() const; virtual void readSubclassData(const QJsonObject& root) { } + virtual void readSubclassData(const QVariantMap& root) { } virtual void writeSubclassData(QJsonObject& root) const { } void resetIdAndVersion(); @@ -50,9 +51,11 @@ class RawEntityData : public RawOctreeData { public: PacketType dataPacketType() const override; void readSubclassData(const QJsonObject& root) override; + void readSubclassData(const QVariantMap& root) override; void writeSubclassData(QJsonObject& root) const override; QJsonArray entityData; + QVariantList variantEntityData; }; } From 6ee837d47c2f198e2284828baefff5687cd86534 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 12 Nov 2018 15:43:09 -0800 Subject: [PATCH 11/23] OctreeDataUtils - use new json from DS also; allow upload of large domains --- domain-server/src/DomainServer.cpp | 13 +----- libraries/octree/src/OctreeDataUtils.cpp | 58 +++++++++--------------- libraries/octree/src/OctreeDataUtils.h | 8 +--- 3 files changed, 25 insertions(+), 54 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8136bf3b53..f90155c786 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3411,20 +3411,11 @@ void DomainServer::maybeHandleReplacementEntityFile() { } void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { - //Assume we have compressed data - auto compressedOctree = octreeFile; - QByteArray jsonOctree; - - bool wasCompressed = gunzip(compressedOctree, jsonOctree); - if (!wasCompressed) { - // the source was not compressed, assume we were sent regular JSON data - jsonOctree = compressedOctree; - } - OctreeUtils::RawEntityData data; - if (data.readOctreeDataInfoFromData(jsonOctree)) { + if (data.readOctreeDataInfoFromData(octreeFile)) { data.resetIdAndVersion(); + QByteArray compressedOctree; gzip(data.toByteArray(), compressedOctree); // write the compressed octree data to a special file diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index 255933a783..8c6e45ae66 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -47,16 +47,6 @@ namespace { } // Anon namespace. -bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromJSON(QJsonObject root) { - if (root.contains("Id") && root.contains("DataVersion") && root.contains("Version")) { - id = root["Id"].toVariant().toUuid(); - dataVersion = root["DataVersion"].toInt(); - version = root["Version"].toInt(); - } - readSubclassData(root); - return true; -} - bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromMap(const QVariantMap& map) { if (map.contains("Id") && map.contains("DataVersion") && map.contains("Version")) { id = map["Id"].toUuid(); @@ -94,28 +84,25 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromFile(QString path) { } QByteArray data = file.readAll(); - QByteArray jsonData; - if (!gunzip(data, jsonData)) { - jsonData = data; - } - - return readOctreeDataInfoFromData(jsonData); + return readOctreeDataInfoFromData(data); } QByteArray OctreeUtils::RawOctreeData::toByteArray() { - QJsonObject obj { - { "DataVersion", QJsonValue((qint64)dataVersion) }, - { "Id", QJsonValue(id.toString()) }, - { "Version", QJsonValue((qint64)version) }, - }; + QByteArray jsonString; - writeSubclassData(obj); + jsonString += QString(R"({ + "DataVersion": %1, +)").arg(dataVersion); - QJsonDocument doc; - doc.setObject(obj); + writeSubclassData(jsonString); - return doc.toJson(); + jsonString += QString(R"(, + "Id": "%1", + "Version": %2 +})").arg(id.toString()).arg(version); + + return jsonString; } QByteArray OctreeUtils::RawOctreeData::toGzippedByteArray() { @@ -142,24 +129,21 @@ void OctreeUtils::RawOctreeData::resetIdAndVersion() { qDebug() << "Reset octree data to: " << id << dataVersion; } -void OctreeUtils::RawEntityData::readSubclassData(const QJsonObject& root) { - if (root.contains("Entities")) { - entityData = root["Entities"].toArray(); - } -} - void OctreeUtils::RawEntityData::readSubclassData(const QVariantMap& root) { variantEntityData = root["Entities"].toList(); } -void OctreeUtils::RawEntityData::writeSubclassData(QJsonObject& root) const { - //root["Entities"] = entityData; - QJsonArray entitiesJsonArray; +void OctreeUtils::RawEntityData::writeSubclassData(QByteArray& root) const { + root += " \"Entities\": ["; for (auto entityIter = variantEntityData.begin(); entityIter != variantEntityData.end(); ++entityIter) { - entitiesJsonArray += entityIter->toJsonObject(); + if (entityIter != variantEntityData.begin()) { + root += ","; + } + root += "\n "; + // Convert to string and remove trailing LF. + root += QJsonDocument(entityIter->toJsonObject()).toJson().chopped(1); } - - root["Entities"] = entitiesJsonArray; + root += "]"; } PacketType OctreeUtils::RawEntityData::dataPacketType() const { return PacketType::EntityData; } diff --git a/libraries/octree/src/OctreeDataUtils.h b/libraries/octree/src/OctreeDataUtils.h index 20092cd451..642c00a675 100644 --- a/libraries/octree/src/OctreeDataUtils.h +++ b/libraries/octree/src/OctreeDataUtils.h @@ -33,9 +33,8 @@ public: virtual PacketType dataPacketType() const; - virtual void readSubclassData(const QJsonObject& root) { } virtual void readSubclassData(const QVariantMap& root) { } - virtual void writeSubclassData(QJsonObject& root) const { } + virtual void writeSubclassData(QByteArray& root) const { } void resetIdAndVersion(); QByteArray toByteArray(); @@ -43,18 +42,15 @@ public: bool readOctreeDataInfoFromData(QByteArray data); bool readOctreeDataInfoFromFile(QString path); - bool readOctreeDataInfoFromJSON(QJsonObject root); bool readOctreeDataInfoFromMap(const QVariantMap& map); }; class RawEntityData : public RawOctreeData { public: PacketType dataPacketType() const override; - void readSubclassData(const QJsonObject& root) override; void readSubclassData(const QVariantMap& root) override; - void writeSubclassData(QJsonObject& root) const override; + void writeSubclassData(QByteArray& root) const override; - QJsonArray entityData; QVariantList variantEntityData; }; From df2bc6ba5cbf1236f6ba89a2085eadca35df88de Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 13 Nov 2018 10:18:30 -0800 Subject: [PATCH 12/23] Remove unused function --- libraries/octree/src/OctreeDataUtils.cpp | 28 ------------------------ 1 file changed, 28 deletions(-) diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index 8c6e45ae66..f3eed084e0 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -19,34 +19,6 @@ #include #include -namespace { - // Reads octree file and parses it into a QJsonDocument. Handles both gzipped and non-gzipped files. - // Returns true if the file was successfully opened and parsed, otherwise false. - // Example failures: file does not exist, gzipped file cannot be unzipped, invalid JSON. - bool readOctreeFile(QString path, QJsonDocument* doc) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Cannot open json file for reading: " << path; - return false; - } - - QByteArray data = file.readAll(); - QByteArray jsonData; - - if (!gunzip(data, jsonData)) { - jsonData = data; - } - - QJsonParseError parserError; - *doc = QJsonDocument::fromJson(jsonData, &parserError); - if (parserError.error != QJsonParseError::NoError) { - qWarning() << "Error reading JSON file" << path << "-" << parserError.errorString(); - } - return !doc->isNull(); - } - -} // Anon namespace. - bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromMap(const QVariantMap& map) { if (map.contains("Id") && map.contains("DataVersion") && map.contains("Version")) { id = map["Id"].toUuid(); From a65466f1546ff42a35d11e8fcc0188dfe1ebb169 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 13 Nov 2018 13:33:36 -0800 Subject: [PATCH 13/23] Use traditional C strings rather than raw strings --- libraries/octree/src/Octree.cpp | 11 ++--------- libraries/octree/src/OctreeDataUtils.cpp | 9 ++------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index cfa207dbf8..c184fe122d 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -904,9 +904,7 @@ bool Octree::toJSONString(QString& jsonString, const OctreeElementPointer& eleme top = _rootElement; } - jsonString += QString(R"({ - "DataVersion": %1, - "Entities": [)").arg(_persistDataVersion); + jsonString += QString("{\n \"DataVersion\": %1,\n \"Entities\": [").arg(_persistDataVersion); writeToJSON(jsonString, top); @@ -914,12 +912,7 @@ bool Octree::toJSONString(QString& jsonString, const OctreeElementPointer& eleme PacketType expectedType = expectedDataPacketType(); PacketVersion expectedVersion = versionForPacketType(expectedType); - jsonString += QString(R"( - ], - "Id": "%1", - "Version": %2 -} -)").arg(_persistID.toString()).arg((int)expectedVersion); + jsonString += QString("\n ],\n \"Id\": \"%1\",\n \"Version\": %2\n}\n").arg(_persistID.toString()).arg((int)expectedVersion); return true; } diff --git a/libraries/octree/src/OctreeDataUtils.cpp b/libraries/octree/src/OctreeDataUtils.cpp index f3eed084e0..b861904255 100644 --- a/libraries/octree/src/OctreeDataUtils.cpp +++ b/libraries/octree/src/OctreeDataUtils.cpp @@ -63,16 +63,11 @@ bool OctreeUtils::RawOctreeData::readOctreeDataInfoFromFile(QString path) { QByteArray OctreeUtils::RawOctreeData::toByteArray() { QByteArray jsonString; - jsonString += QString(R"({ - "DataVersion": %1, -)").arg(dataVersion); + jsonString += QString("{\n \"DataVersion\": %1,\n").arg(dataVersion); writeSubclassData(jsonString); - jsonString += QString(R"(, - "Id": "%1", - "Version": %2 -})").arg(id.toString()).arg(version); + jsonString += QString(",\n \"Id\": \"%1\",\n \"Version\": %2\n}").arg(id.toString()).arg(version); return jsonString; } From e890228fc7d9c383e3943953242da5675b4a2372 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 3 Nov 2018 00:25:43 +0100 Subject: [PATCH 14/23] Make shortcuts work in edit windows --- scripts/system/edit.js | 82 ++++++++++++++-------- scripts/system/html/js/entityList.js | 49 +++++++++---- scripts/system/html/js/entityProperties.js | 27 ++++--- scripts/system/html/js/gridControls.js | 31 ++++---- scripts/system/libraries/entityList.js | 5 +- scripts/system/libraries/gridTool.js | 16 +++-- 6 files changed, 137 insertions(+), 73 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 711c459e5f..46520dbf60 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -113,7 +113,6 @@ selectionManager.addEventListener(function () { entityIconOverlayManager.updatePositions(); }); -var KEY_P = 80; //Key code for letter p used for Parenting hotkey. var DEGREES_TO_RADIANS = Math.PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / Math.PI; @@ -1964,14 +1963,6 @@ var keyReleaseEvent = function (event) { if (isActive) { cameraManager.keyReleaseEvent(event); } - // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items - if (event.key === KEY_P && event.isControl && !event.isAutoRepeat) { - if (event.isShifted) { - unparentSelectedEntities(); - } else { - parentSelectedEntities(); - } - } }; Controller.keyReleaseEvent.connect(keyReleaseEvent); Controller.keyPressEvent.connect(keyPressEvent); @@ -2365,10 +2356,6 @@ var PropertiesTool = function (opts) { } pushCommandForSelections(); selectionManager._update(false, this); - } else if (data.type === 'parent') { - parentSelectedEntities(); - } else if (data.type === 'unparent') { - unparentSelectedEntities(); } else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') { //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; @@ -2681,9 +2668,14 @@ function whenReleased(fn) { }; } +var isOnMacPlatform = Controller.getValue(Controller.Hardware.Application.PlatformMac); + var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); -mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey); -mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey); +if (isOnMacPlatform) { + mapping.from([Controller.Hardware.Keyboard.Backspace]).to(deleteKey); +} else { + mapping.from([Controller.Hardware.Keyboard.Delete]).to(deleteKey); +} mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); @@ -2706,6 +2698,51 @@ mapping.from([Controller.Hardware.Keyboard.Z]) .to(whenPressed(function() { undoHistory.redo() })); +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { unparentSelectedEntities(); })); + +mapping.from([Controller.Hardware.Keyboard.P]) + .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) + .to(whenReleased(function() { parentSelectedEntities(); })); + +function keyUpEventFromUIWindow(keyUpEvent) { + var WANT_DEBUG_MISSING_SHORTCUTS = false; + + var pressedValue = 0.0; + // Note: For some reason the keyboardEvent for delete does not show a code, using key instead: + if ((!isOnMacPlatform && keyUpEvent.key === "Delete") || (isOnMacPlatform && keyUpEvent.code === "Backspace")) { + deleteKey(pressedValue); + } else if (keyUpEvent.code === "KeyT") { + toggleKey(pressedValue); + } else if (keyUpEvent.code === "KeyF") { + focusKey(pressedValue); + } else if (keyUpEvent.code === "KeyG") { + gridKey(pressedValue); + } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyX") { + selectionManager.cutSelectedEntities(); + } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyC") { + selectionManager.copySelectedEntities(); + } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyV") { + selectionManager.pasteEntities(); + } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyD") { + selectionManager.duplicateSelection(); + } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.code === "KeyZ") { + undoHistory.undo(); + } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.code === "KeyP") { + parentSelectedEntities(); + } else if (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.code === "KeyP") { + unparentSelectedEntities(); + } else if ( + (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.code === "KeyZ") || + (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyY")) { + + undoHistory.redo(); + } else if (WANT_DEBUG_MISSING_SHORTCUTS) { + console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) + } +} + var propertyMenu = new PopupMenu(); propertyMenu.onSelectMenuItem = function (name) { @@ -2719,21 +2756,6 @@ var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); var propertiesTool = new PropertiesTool(); -entityListTool.webView.webEventReceived.connect(function(data) { - try { - data = JSON.parse(data); - } catch(e) { - print("edit.js: Error parsing JSON"); - return; - } - - if (data.type === 'parent') { - parentSelectedEntities(); - } else if (data.type === 'unparent') { - unparentSelectedEntities(); - } -}); - selectionDisplay.onSpaceModeChange = function(spaceMode) { entityListTool.setSpaceMode(spaceMode); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 82e64a6fbc..e4220ddd53 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -21,8 +21,6 @@ const MAX_LENGTH_RADIUS = 9; const MINIMUM_COLUMN_WIDTH = 24; const SCROLLBAR_WIDTH = 20; const RESIZER_WIDTH = 10; -const DELETE = 46; // Key code for the delete key. -const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const COLUMNS = { type: { @@ -1115,21 +1113,46 @@ function loaded() { } } - document.addEventListener("keydown", function (keyDownEvent) { - if (keyDownEvent.target.nodeName === "INPUT") { + document.addEventListener("keyup", function (keyUpEvent) { + if (keyUpEvent.target.nodeName === "INPUT") { return; } - let keyCode = keyDownEvent.keyCode; - if (keyCode === DELETE) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); - } - if (keyDownEvent.keyCode === KEY_P && keyDownEvent.ctrlKey) { - if (keyDownEvent.shiftKey) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); - } else { - EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + + if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyA") { + let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id); + let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => { + return selectedEntities.includes(visibleEntityID); + }); + + let selection = []; + + if (!selectionIncludesAllVisibleEntityIDs) { + selection = visibleEntityIDs; } + + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + + return; } + + + let {code, key, altKey, ctrlKey, shiftKey} = keyUpEvent; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + altKey, + ctrlKey, + shiftKey + } + })); }, false); if (window.EventBridge !== undefined) { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index e05ca3ee60..29dfb6a840 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1352,8 +1352,6 @@ const COLOR_MIN = 0; const COLOR_MAX = 255; const COLOR_STEP = 1; -const KEY_P = 80; // Key code for letter p used for Parenting hotkey. - const MATERIAL_PREFIX_STRING = "mat::"; const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; @@ -3494,16 +3492,23 @@ function loaded() { el.parentNode.removeChild(el); elDropdowns = document.getElementsByTagName("select"); } - - document.addEventListener("keydown", function (keyDown) { - if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { - if (keyDown.shiftKey) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); - } else { - EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); - } + + document.addEventListener("keyup", function (keyUpEvent) { + if (keyUpEvent.target.nodeName === "INPUT") { + return; } - }); + let {code, key, altKey, ctrlKey, shiftKey} = keyUpEvent; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + altKey, + ctrlKey, + shiftKey + } + })); + }, false); window.onblur = function() { // Fake a change event diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index 79a169400a..fa3d86a293 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -6,8 +6,6 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -const KEY_P = 80; //Key code for letter p used for Parenting hotkey. - function loaded() { openEventBridge(function() { elPosY = document.getElementById("horiz-y"); @@ -105,7 +103,7 @@ function loaded() { elColorBlue.value = blue; gridColor = { red: red, green: green, blue: blue }; emitUpdate(); - } + }; elColorRed.addEventListener('change', colorChangeFunction); elColorGreen.addEventListener('change', colorChangeFunction); @@ -131,15 +129,24 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); }); - document.addEventListener("keydown", function (keyDown) { - if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { - if (keyDown.shiftKey) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); - } else { - EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); - } - } - }) + + document.addEventListener("keyup", function (keyUpEvent) { + if (keyUpEvent.target.nodeName === "INPUT") { + return; + } + let {code, key, altKey, ctrlKey, shiftKey} = keyUpEvent; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'keyUpEvent', + keyUpEvent: { + code, + key, + altKey, + ctrlKey, + shiftKey + } + })); + }, false); + // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { event.preventDefault(); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 4ba1012a58..8942c84f33 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -9,7 +9,8 @@ // /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, - cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */ + cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible, + keyUpEventFromUIWindow */ var PROFILING_ENABLED = false; var profileIndent = ''; @@ -298,6 +299,8 @@ EntityListTool = function(shouldUseEditTabletApp) { SelectionManager._update(); } else if (data.type === "toggleSpaceMode") { SelectionDisplay.toggleSpaceMode(); + } else if (data.type === 'keyUpEvent') { + keyUpEventFromUIWindow(data.keyUpEvent); } }; diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 6f62742e8f..1fd3cbfbaa 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -1,3 +1,5 @@ +/* global keyUpEventFromUIWindow */ + var GRID_CONTROLS_HTML_URL = Script.resolvePath('../html/gridControls.html'); Grid = function() { @@ -270,24 +272,26 @@ GridTool = function(opts) { return; } - if (data.type == "init") { + if (data.type === "init") { horizontalGrid.emitUpdate(); - } else if (data.type == "update") { + } else if (data.type === "update") { horizontalGrid.update(data); for (var i = 0; i < listeners.length; i++) { listeners[i] && listeners[i](data); } - } else if (data.type == "action") { + } else if (data.type === "action") { var action = data.action; - if (action == "moveToAvatar") { + if (action === "moveToAvatar") { var position = MyAvatar.getJointPosition("LeftFoot"); - if (position.x == 0 && position.y == 0 && position.z == 0) { + if (position.x === 0 && position.y === 0 && position.z === 0) { position = MyAvatar.position; } horizontalGrid.setPosition(position); - } else if (action == "moveToSelection") { + } else if (action === "moveToSelection") { horizontalGrid.moveToSelection(); } + } else if (data.type === 'keyUpEvent') { + keyUpEventFromUIWindow(data.keyUpEvent); } }; From afd69d04dd8e05876d3721a3f1f9de0a396f1ad0 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 3 Nov 2018 00:27:03 +0100 Subject: [PATCH 15/23] Make ctrl+shift shortcuts work for create app --- interface/resources/qml/controls/FlickableWebViewCore.qml | 6 ++++-- interface/resources/qml/controls/WebView.qml | 1 + interface/resources/qml/hifi/tablet/EditEntityList.qml | 3 ++- interface/resources/qml/hifi/tablet/EditTabView.qml | 3 +++ interface/resources/qml/hifi/tablet/EditToolsTabView.qml | 2 ++ interface/resources/qml/hifi/tablet/EntityList.qml | 1 + scripts/system/libraries/EditEntityList.qml | 1 + 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index a89a0cf37b..823d0107a2 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -26,6 +26,8 @@ Item { property bool interactive: false + property bool blurOnCtrlShift: true + StylesUIt.HifiConstants { id: hifi } @@ -180,8 +182,8 @@ Item { } Keys.onPressed: { - if ((event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) { - webViewCore.focus = false; + if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) { + webViewCore.focus = false; } } } diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 6d72c529f6..24ea11a906 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -22,6 +22,7 @@ Item { property bool punctuationMode: false property bool passwordField: false property alias flickable: webroot.interactive + property alias blurOnCtrlShift: webroot.blurOnCtrlShift function stop() { webroot.stop(); diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/interface/resources/qml/hifi/tablet/EditEntityList.qml index d2fb99ea0a..b6305db388 100644 --- a/interface/resources/qml/hifi/tablet/EditEntityList.qml +++ b/interface/resources/qml/hifi/tablet/EditEntityList.qml @@ -10,6 +10,7 @@ import stylesUit 1.0 WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityList.html" + url: Paths.defaultScripts + "/system/html/entityListf.html" enabled: true + blurOnCtrlShift: false } diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index ff1c5a7c47..5959725a6a 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -245,6 +245,7 @@ TabBar { id: entityListToolWebView url: Paths.defaultScripts + "/system/html/entityList.html" enabled: true + blurOnCtrlShift: false } } } @@ -260,6 +261,7 @@ TabBar { id: entityPropertiesWebView url: Paths.defaultScripts + "/system/html/entityProperties.html" enabled: true + blurOnCtrlShift: false } } } @@ -275,6 +277,7 @@ TabBar { id: gridControlsWebView url: Paths.defaultScripts + "/system/html/gridControls.html" enabled: true + blurOnCtrlShift: false } } } diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml index 2b78576526..6b64520feb 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml @@ -251,6 +251,7 @@ TabBar { id: entityPropertiesWebView url: Paths.defaultScripts + "/system/html/entityProperties.html" enabled: true + blurOnCtrlShift: false } } } @@ -266,6 +267,7 @@ TabBar { id: gridControlsWebView url: Paths.defaultScripts + "/system/html/gridControls.html" enabled: true + blurOnCtrlShift: false } } } diff --git a/interface/resources/qml/hifi/tablet/EntityList.qml b/interface/resources/qml/hifi/tablet/EntityList.qml index f4b47c19bb..2f8a8863be 100644 --- a/interface/resources/qml/hifi/tablet/EntityList.qml +++ b/interface/resources/qml/hifi/tablet/EntityList.qml @@ -2,4 +2,5 @@ WebView { id: entityListToolWebView url: Paths.defaultScripts + "/system/html/entityList.html" enabled: true + blurOnCtrlShift: false } diff --git a/scripts/system/libraries/EditEntityList.qml b/scripts/system/libraries/EditEntityList.qml index d8099cb670..4fc5ff19ef 100644 --- a/scripts/system/libraries/EditEntityList.qml +++ b/scripts/system/libraries/EditEntityList.qml @@ -8,4 +8,5 @@ HifiControls.WebView { id: entityListToolWebView url: Qt.resolvedUrl("../html/entityList.html") enabled: true + blurOnCtrlShift: false } From cc207537a32901197034038d91a7703980af0b9f Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 3 Nov 2018 00:27:40 +0100 Subject: [PATCH 16/23] Fix for unintended snapshots while parenting/un-parenting --- interface/src/Application.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b085f0b4de..5256ef2c6a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4039,21 +4039,23 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_P: { - AudioInjectorOptions options; - options.localOnly = true; - options.stereo = true; - Setting::Handle notificationSounds{ MenuOption::NotificationSounds, true}; - Setting::Handle notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true}; - if (notificationSounds.get() && notificationSoundSnapshot.get()) { - if (_snapshotSoundInjector) { - _snapshotSoundInjector->setOptions(options); - _snapshotSoundInjector->restart(); - } else { - QByteArray samples = _snapshotSound->getByteArray(); - _snapshotSoundInjector = AudioInjector::playSound(samples, options); + if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { + AudioInjectorOptions options; + options.localOnly = true; + options.stereo = true; + Setting::Handle notificationSounds{ MenuOption::NotificationSounds, true }; + Setting::Handle notificationSoundSnapshot{ MenuOption::NotificationSoundsSnapshot, true }; + if (notificationSounds.get() && notificationSoundSnapshot.get()) { + if (_snapshotSoundInjector) { + _snapshotSoundInjector->setOptions(options); + _snapshotSoundInjector->restart(); + } else { + QByteArray samples = _snapshotSound->getByteArray(); + _snapshotSoundInjector = AudioInjector::playSound(samples, options); + } } + takeSnapshot(true); } - takeSnapshot(true); break; } From 0248323beedb340773c16d422e02858a7b26284e Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 3 Nov 2018 00:34:00 +0100 Subject: [PATCH 17/23] fix test path --- interface/resources/qml/hifi/tablet/EditEntityList.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/interface/resources/qml/hifi/tablet/EditEntityList.qml index b6305db388..2afaa8cd82 100644 --- a/interface/resources/qml/hifi/tablet/EditEntityList.qml +++ b/interface/resources/qml/hifi/tablet/EditEntityList.qml @@ -10,7 +10,7 @@ import stylesUit 1.0 WebView { id: entityListToolWebView - url: Paths.defaultScripts + "/system/html/entityListf.html" + url: Paths.defaultScripts + "/system/html/entityList.html" enabled: true blurOnCtrlShift: false } From 2c715bf59259a3724ce54be38fc8c800370a5c19 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 14 Nov 2018 00:48:16 +0100 Subject: [PATCH 18/23] make window shortcuts work on OSX --- scripts/system/edit.js | 38 ++++++++++++---------- scripts/system/html/js/entityList.js | 25 ++++++++++++-- scripts/system/html/js/entityProperties.js | 23 ++++++++++++- scripts/system/html/js/gridControls.js | 23 ++++++++++++- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 46520dbf60..3876b1b074 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,7 +12,8 @@ /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, - progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow */ + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow, + keyUpEventFromUIWindow:true */ (function() { // BEGIN LOCAL_SCOPE @@ -2706,42 +2707,44 @@ mapping.from([Controller.Hardware.Keyboard.P]) .when([Controller.Hardware.Keyboard.Control, !Controller.Hardware.Keyboard.Shift]) .to(whenReleased(function() { parentSelectedEntities(); })); -function keyUpEventFromUIWindow(keyUpEvent) { +keyUpEventFromUIWindow = function(keyUpEvent) { var WANT_DEBUG_MISSING_SHORTCUTS = false; var pressedValue = 0.0; - // Note: For some reason the keyboardEvent for delete does not show a code, using key instead: - if ((!isOnMacPlatform && keyUpEvent.key === "Delete") || (isOnMacPlatform && keyUpEvent.code === "Backspace")) { + + if ((!isOnMacPlatform && keyUpEvent.keyCodeString === "Delete") + || (isOnMacPlatform && keyUpEvent.keyCodeString === "Backspace")) { + deleteKey(pressedValue); - } else if (keyUpEvent.code === "KeyT") { + } else if (keyUpEvent.keyCodeString === "T") { toggleKey(pressedValue); - } else if (keyUpEvent.code === "KeyF") { + } else if (keyUpEvent.keyCodeString === "F") { focusKey(pressedValue); - } else if (keyUpEvent.code === "KeyG") { + } else if (keyUpEvent.keyCodeString === "G") { gridKey(pressedValue); - } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyX") { + } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "X") { selectionManager.cutSelectedEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyC") { + } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "C") { selectionManager.copySelectedEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyV") { + } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "V") { selectionManager.pasteEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyD") { + } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "D") { selectionManager.duplicateSelection(); - } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.code === "KeyZ") { + } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { undoHistory.undo(); - } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.code === "KeyP") { + } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { parentSelectedEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.code === "KeyP") { + } else if (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { unparentSelectedEntities(); } else if ( - (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.code === "KeyZ") || - (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyY")) { + (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || + (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "Y")) { undoHistory.redo(); } else if (WANT_DEBUG_MISSING_SHORTCUTS) { console.warn("unhandled key event: " + JSON.stringify(keyUpEvent)) } -} +}; var propertyMenu = new PopupMenu(); @@ -2756,7 +2759,6 @@ var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); var propertiesTool = new PropertiesTool(); - selectionDisplay.onSpaceModeChange = function(spaceMode) { entityListTool.setSpaceMode(spaceMode); propertiesTool.setSpaceMode(spaceMode); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index e4220ddd53..ea096be5e2 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -1112,13 +1112,33 @@ function loaded() { elToggleSpaceMode.innerText = "World"; } } + + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; document.addEventListener("keyup", function (keyUpEvent) { if (keyUpEvent.target.nodeName === "INPUT") { return; } - if (keyUpEvent.ctrlKey && keyUpEvent.code === "KeyA") { + let {code, key, keyCode, altKey, ctrlKey, shiftKey} = keyUpEvent; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + + if (ctrlKey && keyCodeString === "A") { let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id); let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => { return selectedEntities.includes(visibleEntityID); @@ -1142,12 +1162,13 @@ function loaded() { } - let {code, key, altKey, ctrlKey, shiftKey} = keyUpEvent; EventBridge.emitWebEvent(JSON.stringify({ type: 'keyUpEvent', keyUpEvent: { code, key, + keyCode, + keyCodeString, altKey, ctrlKey, shiftKey diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 29dfb6a840..9bb8a2c5a4 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3493,16 +3493,37 @@ function loaded() { elDropdowns = document.getElementsByTagName("select"); } + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + document.addEventListener("keyup", function (keyUpEvent) { if (keyUpEvent.target.nodeName === "INPUT") { return; } - let {code, key, altKey, ctrlKey, shiftKey} = keyUpEvent; + let {code, key, keyCode, altKey, ctrlKey, shiftKey} = keyUpEvent; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + EventBridge.emitWebEvent(JSON.stringify({ type: 'keyUpEvent', keyUpEvent: { code, key, + keyCode, + keyCodeString, altKey, ctrlKey, shiftKey diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index fa3d86a293..6f7024ccaa 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -130,16 +130,37 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); }); + const KEY_CODES = { + BACKSPACE: 8, + DELETE: 46 + }; + document.addEventListener("keyup", function (keyUpEvent) { if (keyUpEvent.target.nodeName === "INPUT") { return; } - let {code, key, altKey, ctrlKey, shiftKey} = keyUpEvent; + let {code, key, keyCode, altKey, ctrlKey, shiftKey} = keyUpEvent; + + let keyCodeString; + switch (keyCode) { + case KEY_CODES.DELETE: + keyCodeString = "Delete"; + break; + case KEY_CODES.BACKSPACE: + keyCodeString = "Backspace"; + break; + default: + keyCodeString = String.fromCharCode(keyUpEvent.keyCode); + break; + } + EventBridge.emitWebEvent(JSON.stringify({ type: 'keyUpEvent', keyUpEvent: { code, key, + keyCode, + keyCodeString, altKey, ctrlKey, shiftKey From 452c212c4f43fd912606b6da6c144308bcb52633 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 13 Nov 2018 17:58:12 -0800 Subject: [PATCH 19/23] Add Paths entry parsing to json parser --- .../octree/src/OctreeEntitiesFileParser.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index 72853947c6..873eaff0e1 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -117,6 +117,28 @@ bool OctreeEntitiesFileParser::parseEntities(QVariantMap& parsedEntities) { int versionValue = readInteger(); parsedEntities["Version"] = versionValue; gotVersion = true; + } else if (key == "Paths") { + // Serverless JSON has optional Paths entry. + if (nextToken() != '{') { + _errorString = "Paths item is not an object"; + return false; + } + + int matchingBrace = findMatchingBrace(); + if (matchingBrace < 0) { + _errorString = "Unterminated entity object"; + return false; + } + + QByteArray jsonObject = _entitiesContents.mid(_position - 1, matchingBrace - _position + 1); + QJsonDocument pathsObject = QJsonDocument::fromJson(jsonObject); + if (pathsObject.isNull()) { + _errorString = "Ill-formed paths entry"; + return false; + } + + parsedEntities["Paths"] = pathsObject.object(); + _position = matchingBrace; } else { _errorString = "Unrecognized key name: " + key; return false; From cd12dd8f6ddf2beda7e687597afbd9511ac116c7 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 14 Nov 2018 19:28:57 +0100 Subject: [PATCH 20/23] fix OSX shortcut inconsistencies --- scripts/system/edit.js | 18 +++++++++--------- scripts/system/html/js/entityList.js | 10 ++++++---- scripts/system/html/js/entityProperties.js | 8 +++++--- scripts/system/html/js/gridControls.js | 8 +++++--- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 3876b1b074..48fdb8e565 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2722,23 +2722,23 @@ keyUpEventFromUIWindow = function(keyUpEvent) { focusKey(pressedValue); } else if (keyUpEvent.keyCodeString === "G") { gridKey(pressedValue); - } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "X") { + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "X") { selectionManager.cutSelectedEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "C") { + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "C") { selectionManager.copySelectedEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "V") { + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "V") { selectionManager.pasteEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "D") { + } else if (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "D") { selectionManager.duplicateSelection(); - } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { + } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") { undoHistory.undo(); - } else if (keyUpEvent.ctrlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + } else if (keyUpEvent.controlKey && !keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { parentSelectedEntities(); - } else if (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { + } else if (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "P") { unparentSelectedEntities(); } else if ( - (keyUpEvent.ctrlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || - (keyUpEvent.ctrlKey && keyUpEvent.keyCodeString === "Y")) { + (keyUpEvent.controlKey && keyUpEvent.shiftKey && keyUpEvent.keyCodeString === "Z") || + (keyUpEvent.controlKey && keyUpEvent.keyCodeString === "Y")) { undoHistory.redo(); } else if (WANT_DEBUG_MISSING_SHORTCUTS) { diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index ea096be5e2..141055f2b7 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -1123,7 +1123,9 @@ function loaded() { return; } - let {code, key, keyCode, altKey, ctrlKey, shiftKey} = keyUpEvent; + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; let keyCodeString; switch (keyCode) { @@ -1138,7 +1140,7 @@ function loaded() { break; } - if (ctrlKey && keyCodeString === "A") { + if (controlKey && keyCodeString === "A") { let visibleEntityIDs = visibleEntities.map(visibleEntity => visibleEntity.id); let selectionIncludesAllVisibleEntityIDs = visibleEntityIDs.every(visibleEntityID => { return selectedEntities.includes(visibleEntityID); @@ -1170,8 +1172,8 @@ function loaded() { keyCode, keyCodeString, altKey, - ctrlKey, - shiftKey + controlKey, + shiftKey, } })); }, false); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 9bb8a2c5a4..847d583ca3 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3502,7 +3502,9 @@ function loaded() { if (keyUpEvent.target.nodeName === "INPUT") { return; } - let {code, key, keyCode, altKey, ctrlKey, shiftKey} = keyUpEvent; + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; let keyCodeString; switch (keyCode) { @@ -3525,8 +3527,8 @@ function loaded() { keyCode, keyCodeString, altKey, - ctrlKey, - shiftKey + controlKey, + shiftKey, } })); }, false); diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index 6f7024ccaa..70e91071fb 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -139,7 +139,9 @@ function loaded() { if (keyUpEvent.target.nodeName === "INPUT") { return; } - let {code, key, keyCode, altKey, ctrlKey, shiftKey} = keyUpEvent; + let {code, key, keyCode, altKey, ctrlKey, metaKey, shiftKey} = keyUpEvent; + + let controlKey = window.navigator.platform.startsWith("Mac") ? metaKey : ctrlKey; let keyCodeString; switch (keyCode) { @@ -162,8 +164,8 @@ function loaded() { keyCode, keyCodeString, altKey, - ctrlKey, - shiftKey + controlKey, + shiftKey, } })); }, false); From 70493e3f29f37c57fa8422487032825a0c6d5c87 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 14 Nov 2018 12:23:16 -0800 Subject: [PATCH 21/23] add polyline/polyvox to entity list/properties types --- scripts/system/html/js/entityList.js | 4 ++++ scripts/system/html/js/entityProperties.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 82e64a6fbc..0319012cf6 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -136,6 +136,8 @@ const FILTER_TYPES = [ "Web", "Material", "ParticleEffect", + "PolyLine", + "PolyVox", "Text", ]; @@ -148,6 +150,8 @@ const ICON_FOR_TYPE = { Web: "q", Material: "", ParticleEffect: "", + PolyLine: "", + PolyVox: "", Text: "l", }; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index e05ca3ee60..96f2323e7d 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1342,6 +1342,8 @@ const GROUPS_PER_TYPE = { Material: [ 'base', 'material', 'spatial', 'behavior' ], ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], + PolyLine: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + PolyVox: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], }; From 1593c1185aa0701d6bdf2754570cfbeebc62a45b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 14 Nov 2018 12:28:01 -0800 Subject: [PATCH 22/23] revert PR-14238 "TypeError: Cannot read property 'buttons'" until we can adjust it. --- interface/resources/qml/hifi/Desktop.qml | 4 ++-- interface/resources/qml/hifi/tablet/TabletHome.qml | 4 ++-- libraries/qml/src/qml/OffscreenSurface.cpp | 8 ++------ libraries/qml/src/qml/impl/SharedObject.cpp | 12 +----------- libraries/qml/src/qml/impl/SharedObject.h | 4 +--- 5 files changed, 8 insertions(+), 24 deletions(-) diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 354c8095a0..f57c612b98 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -70,8 +70,8 @@ OriginalDesktop.Desktop { anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined; // Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained. x: sysToolbar.x - buttonModel: tablet ? tablet.buttons : null; - shown: tablet ? tablet.toolbarMode : false; + buttonModel: tablet.buttons; + shown: tablet.toolbarMode; } Settings { diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index cbfb3c1337..f1f54e8419 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -115,9 +115,9 @@ Item { property int previousIndex: -1 Repeater { id: pageRepeater - model: tabletProxy != null ? Math.ceil(tabletProxy.buttons.rowCount() / TabletEnums.ButtonsOnPage) : 0 + model: Math.ceil(tabletProxy.buttons.rowCount() / TabletEnums.ButtonsOnPage) onItemAdded: { - item.proxyModel.sourceModel = tabletProxy != null ? tabletProxy.buttons : null; + item.proxyModel.sourceModel = tabletProxy.buttons; item.proxyModel.pageIndex = index; } diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 7edd4e481c..91532534e3 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -387,12 +387,8 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, if (!parent) { parent = getRootItem(); } - // manually control children items lifetime - QQmlEngine::setObjectOwnership(newObject, QQmlEngine::CppOwnership); - - // add object to the manual deletion list - _sharedObject->addToDeletionList(newObject); - + // Allow child windows to be destroyed from JS + QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership); newObject->setParent(parent); newItem->setParentItem(parent); } else { diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 1f41fefb02..259defdb48 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -15,7 +15,6 @@ #include #include -#include #include #include @@ -82,6 +81,7 @@ SharedObject::SharedObject() { SharedObject::~SharedObject() { // After destroy returns, the rendering thread should be gone destroy(); + // _renderTimer is created with `this` as the parent, so need no explicit destruction #ifndef DISABLE_QML // Destroy the event hand @@ -96,11 +96,6 @@ SharedObject::~SharedObject() { } #endif - // already deleted objects will be reset to null by QPointer so it should be safe just iterate here - for (auto qmlObject : _deletionList) { - delete qmlObject; - } - if (_rootItem) { delete _rootItem; _rootItem = nullptr; @@ -417,11 +412,6 @@ bool SharedObject::fetchTexture(TextureAndFence& textureAndFence) { return true; } -void hifi::qml::impl::SharedObject::addToDeletionList(QObject * object) -{ - _deletionList.append(QPointer(object)); -} - void SharedObject::setProxyWindow(QWindow* window) { #ifndef DISABLE_QML _proxyWindow = window; diff --git a/libraries/qml/src/qml/impl/SharedObject.h b/libraries/qml/src/qml/impl/SharedObject.h index ce9fcd46d2..002679c44d 100644 --- a/libraries/qml/src/qml/impl/SharedObject.h +++ b/libraries/qml/src/qml/impl/SharedObject.h @@ -66,7 +66,7 @@ public: void resume(); bool isPaused() const; bool fetchTexture(TextureAndFence& textureAndFence); - void addToDeletionList(QObject* object); + private: bool event(QEvent* e) override; @@ -91,8 +91,6 @@ private: void onAboutToQuit(); void updateTextureAndFence(const TextureAndFence& newTextureAndFence); - QList> _deletionList; - // Texture management TextureAndFence _latestTextureAndFence{ 0, 0 }; QQuickItem* _item{ nullptr }; From b7c567af08c88e16acfb7797502d9dafb2ef4162 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 14 Nov 2018 15:28:22 -0800 Subject: [PATCH 23/23] Fix procedurals using the iGlobalTime input --- libraries/procedural/src/procedural/ProceduralCommon.slh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index 64a4fdd1f9..d515a79e22 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -32,8 +32,10 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer { vec4 date; // Offset 16, acts as vec4 for alignment purposes vec3 worldPosition; - // Offset 32, acts as vec4 for alignment purposes + // Offset 32, acts as vec4 for alignment purposes (but not packing purposes) vec3 worldScale; + // We need this float here to keep globalTime from getting pulled to offset 44 + float _spare0; // Offset 48 float globalTime; // Offset 52