From ca38b9fe34a10468af92478ab38efc49abef941d Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 16 Mar 2018 10:53:59 -0700 Subject: [PATCH 1/8] Fix invalid QML surface cache interaction --- interface/src/ui/overlays/Web3DOverlay.cpp | 36 ++++++-- interface/src/ui/overlays/Web3DOverlay.h | 1 + scripts/developer/tests/webOverlayTool.js | 102 +++++++++++++++++++++ 3 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 scripts/developer/tests/webOverlayTool.js diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index b324bf39c4..97d4a09f91 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -63,6 +63,19 @@ static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; + +static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { + AbstractViewStateInterface::instance()->postLambdaEvent([surface] { + if (AbstractViewStateInterface::instance()->isAboutToQuit()) { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete surface; + } else { + surface->deleteLater(); + } + }); +}; + Web3DOverlay::Web3DOverlay() { _touchDevice.setCapabilities(QTouchDevice::Position); _touchDevice.setType(QTouchDevice::TouchScreen); @@ -75,7 +88,8 @@ Web3DOverlay::Web3DOverlay() { connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface); //need to be intialized before Tablet 1st open - _webSurface = DependencyManager::get()->acquire(_url); + _webSurface = DependencyManager::get()->acquire(QML); + _cachedWebSurface = true; _webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED @@ -114,6 +128,7 @@ void Web3DOverlay::destroyWebSurface() { if (!_webSurface) { return; } + QQuickItem* rootItem = _webSurface->getRootItem(); if (rootItem && rootItem->objectName() == "tabletRoot") { @@ -135,10 +150,15 @@ void Web3DOverlay::destroyWebSurface() { QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent); QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); - auto offscreenCache = DependencyManager::get(); - // FIXME prevents crash on shutdown, but we shoudln't have to do this check - if (offscreenCache) { - offscreenCache->release(QML, _webSurface); + + // If the web surface was fetched out of the cache, release it back into the cache + if (_cachedWebSurface) { + auto offscreenCache = DependencyManager::get(); + // FIXME prevents crash on shutdown, but we shoudln't have to do this check + if (offscreenCache) { + offscreenCache->release(QML, _webSurface); + } + _cachedWebSurface = false; } _webSurface.reset(); } @@ -147,6 +167,8 @@ void Web3DOverlay::buildWebSurface() { if (_webSurface) { return; } + // FIXME the context save here is most likely unecessary since the QML surfaces now render + // off the main thread, and all GL context work is done off the main thread (I *think*) gl::withSavedContext([&] { // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) @@ -156,10 +178,12 @@ void Web3DOverlay::buildWebSurface() { if (isWebContent()) { _webSurface = DependencyManager::get()->acquire(QML); + _cachedWebSurface = true; _webSurface->getRootItem()->setProperty("url", _url); _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); } else { - _webSurface = DependencyManager::get()->acquire(_url); + _webSurface = _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); + _cachedWebSurface = false; setupQmlSurface(); } _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 4098e98488..d888424cbc 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -88,6 +88,7 @@ private: InputMode _inputMode { Touch }; QSharedPointer _webSurface; + bool _cachedWebSurface{ false }; gpu::TexturePointer _texture; QString _url; QString _scriptURL; diff --git a/scripts/developer/tests/webOverlayTool.js b/scripts/developer/tests/webOverlayTool.js new file mode 100644 index 0000000000..1a3aa35205 --- /dev/null +++ b/scripts/developer/tests/webOverlayTool.js @@ -0,0 +1,102 @@ +// webSpawnTool.js +// +// Stress tests the rendering of web surfaces over time +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +SPAWNER = function (properties) { + properties = properties || {}; + var RADIUS = properties.radius || 5.0; // Spawn within this radius (square) + var SPAWN_COUNT = properties.count || 10000; // number of entities to spawn + var SPAWN_LIMIT = properties.spawnLimit || 1; + var SPAWN_INTERVAL = properties.spawnInterval || properties.interval || 2; + var SPAWN_LIFETIME = properties.lifetime || 10; // Entity timeout (when/if we crash, we need the entities to delete themselves) + + function randomPositionXZ(center, radius) { + return { + x: center.x + (Math.random() * radius * 2.0) - radius, + y: center.y, + z: center.z + (Math.random() * radius * 2.0) - radius + }; + } + + function makeObject(properties) { + + var overlay = Overlays.addOverlay("web3d", { + name: "Web", + url: "https://www.reddit.com/r/random", + localPosition: randomPositionXZ( { x: 0, y: 0, z: -1 }, 1), + localRotation: Quat.angleAxis(180, Vec3.Y_AXIS), + dimensions: {x: .8, y: .45, z: 0.1}, + color: { red: 255, green: 255, blue: 255 }, + alpha: 1.0, + showKeyboardFocusHighlight: false, + visible: true + }); + + var now = Date.now(); + + return { + destroy: function () { + Overlays.deleteOverlay(overlay) + }, + getAge: function () { + return (Date.now() - now) / 1000.0; + } + }; + } + + + var items = []; + var toCreate = 0; + var spawned = 0; + var spawnTimer = 0.0; + var keepAliveTimer = 0.0; + + function clear () { + } + + function create() { + toCreate = SPAWN_COUNT; + Script.update.connect(spawn); + } + + function spawn(dt) { + if (toCreate <= 0) { + Script.update.disconnect(spawn); + print("Finished spawning"); + } + else if ((spawnTimer -= dt) < 0.0){ + spawnTimer = SPAWN_INTERVAL; + + var n = Math.min(toCreate, SPAWN_LIMIT); + print("Spawning " + n + " items (" + (spawned += n) + ")"); + + toCreate -= n; + for (; n > 0; --n) { + items.push(makeObject()); + } + } + } + + function despawn() { + print("despawning"); + items.forEach(function (item) { + item.destroy(); + }); + item = []; + } + + function init () { + Script.update.disconnect(init); + Script.scriptEnding.connect(despawn); + clear(); + create(); + } + + Script.update.connect(init); +}; + +SPAWNER(); From 1dfde86680bbcb47fc51575e9fc8b8501baba6f5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 16 Mar 2018 11:11:08 -0700 Subject: [PATCH 2/8] Fix typo --- interface/src/ui/overlays/Web3DOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 97d4a09f91..d460ff44e8 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -182,7 +182,7 @@ void Web3DOverlay::buildWebSurface() { _webSurface->getRootItem()->setProperty("url", _url); _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); } else { - _webSurface = _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); _cachedWebSurface = false; setupQmlSurface(); } From 1e89debc4e85ceccce3f72582bc422b904098e95 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 16 Mar 2018 13:38:45 -0700 Subject: [PATCH 3/8] model behaves correctly if missing material --- libraries/render-utils/src/MeshPartPayload.cpp | 14 ++++++++------ libraries/render-utils/src/MeshPartPayload.h | 9 ++++++--- libraries/render-utils/src/Model.cpp | 3 ++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 2637d24d67..a03a531ec9 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -47,6 +47,8 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr } } +const graphics::MaterialPointer MeshPartPayload::DEFAULT_MATERIAL = std::make_shared(); + MeshPartPayload::MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material) { updateMeshPart(mesh, partIndex); addMaterial(graphics::MaterialLayer(material, 0)); @@ -95,7 +97,7 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, builder.withSubMetaCulled(); } - if (_drawMaterials.top().material) { + if (topMaterialExists()) { auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); @@ -115,7 +117,7 @@ Item::Bound MeshPartPayload::getBound() const { ShapeKey MeshPartPayload::getShapeKey() const { graphics::MaterialKey drawMaterialKey; - if (_drawMaterials.top().material) { + if (topMaterialExists()) { drawMaterialKey = _drawMaterials.top().material->getKey(); } @@ -170,7 +172,7 @@ void MeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); + RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! @@ -350,7 +352,7 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withDeformed(); } - if (_drawMaterials.top().material) { + if (topMaterialExists()) { auto matKey = _drawMaterials.top().material->getKey(); if (matKey.isTranslucent()) { builder.withTransparent(); @@ -381,7 +383,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe } graphics::MaterialKey drawMaterialKey; - if (_drawMaterials.top().material) { + if (topMaterialExists()) { drawMaterialKey = _drawMaterials.top().material->getKey(); } @@ -467,7 +469,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { bindMesh(batch); // apply material properties - RenderPipelines::bindMaterial(_drawMaterials.top().material, batch, args->_enableTexturing); + RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); args->_details._materialSwitches++; // Draw! diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 3ad222d90c..7440f55e7e 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -65,15 +65,18 @@ public: graphics::Mesh::Part _drawPart; size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } - size_t getMaterialTextureSize() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureSize() : 0; } - int getMaterialTextureCount() { return _drawMaterials.top().material ? _drawMaterials.top().material->getTextureCount() : 0; } - bool hasTextureInfo() const { return _drawMaterials.top().material ? _drawMaterials.top().material->hasTextureInfo() : false; } + size_t getMaterialTextureSize() { return topMaterialExists() ? _drawMaterials.top().material->getTextureSize() : 0; } + int getMaterialTextureCount() { return topMaterialExists() ? _drawMaterials.top().material->getTextureCount() : 0; } + bool hasTextureInfo() const { return topMaterialExists() ? _drawMaterials.top().material->hasTextureInfo() : false; } void addMaterial(graphics::MaterialLayer material); void removeMaterial(graphics::MaterialPointer material); protected: + static const graphics::MaterialPointer DEFAULT_MATERIAL; render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() }; + + bool topMaterialExists() const { return !_drawMaterials.empty() && _drawMaterials.top().material; } }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 12d6659849..5784101f63 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1573,7 +1573,8 @@ void Model::createVisibleRenderItemSet() { int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); - _modelMeshMaterialNames.push_back(getGeometry()->getShapeMaterial(shapeID)->getName()); + auto material = getGeometry()->getShapeMaterial(shapeID); + _modelMeshMaterialNames.push_back(material ? material->getName() : ""); _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); shapeID++; } From 3a98cf284fa98d6adffd02f2f66711c6ce0521f8 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Mar 2018 14:18:42 -0700 Subject: [PATCH 4/8] Fix DS not handling lowercase header keys --- domain-server/src/DomainServer.cpp | 10 +++++----- libraries/embedded-webserver/src/HTTPConnection.cpp | 8 ++++---- libraries/embedded-webserver/src/HTTPConnection.h | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a048b113da..f5ed0f6899 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2209,7 +2209,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES"; const QString ASSIGNMENT_POOL_HEADER = "ASSIGNMENT-POOL"; - QByteArray assignmentInstancesValue = connection->requestHeaders().value(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit()); + QByteArray assignmentInstancesValue = connection->requestHeader(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit()); int numInstances = 1; @@ -2221,7 +2221,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } QString assignmentPool = emptyPool; - QByteArray assignmentPoolValue = connection->requestHeaders().value(ASSIGNMENT_POOL_HEADER.toLocal8Bit()); + QByteArray assignmentPoolValue = connection->requestHeader(ASSIGNMENT_POOL_HEADER.toLocal8Bit()); if (!assignmentPoolValue.isEmpty()) { // specific pool requested, set that on the created assignment @@ -2619,7 +2619,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl if (!_oauthProviderURL.isEmpty() && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) { - QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY); + QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY); const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING); @@ -2664,7 +2664,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With"; static const QString XML_REQUESTED_WITH = "XMLHttpRequest"; - if (connection->requestHeaders().value(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) { + if (connection->requestHeader(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) { // unauthorized XHR requests get a 401 and not a 302, since there isn't an XHR // path to OAuth authorize connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY); @@ -2695,7 +2695,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization"; // check if a username and password have been provided with the request - QString basicAuthString = connection->requestHeaders().value(BASIC_AUTH_HEADER_KEY); + QString basicAuthString = connection->requestHeader(BASIC_AUTH_HEADER_KEY); if (!basicAuthString.isEmpty()) { QStringList splitAuthString = basicAuthString.split(' '); diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 00879e1380..845822cdf7 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -55,7 +55,7 @@ HTTPConnection::~HTTPConnection() { QHash HTTPConnection::parseUrlEncodedForm() { // make sure we have the correct MIME type - QList elements = _requestHeaders.value("Content-Type").split(';'); + QList elements = requestHeader("Content-Type").split(';'); QString contentType = elements.at(0).trimmed(); if (contentType != "application/x-www-form-urlencoded") { @@ -75,7 +75,7 @@ QHash HTTPConnection::parseUrlEncodedForm() { QList HTTPConnection::parseFormData() const { // make sure we have the correct MIME type - QList elements = _requestHeaders.value("Content-Type").split(';'); + QList elements = requestHeader("Content-Type").split(';'); QString contentType = elements.at(0).trimmed(); @@ -251,7 +251,7 @@ void HTTPConnection::readHeaders() { if (trimmed.isEmpty()) { _socket->disconnect(this, SLOT(readHeaders())); - QByteArray clength = _requestHeaders.value("Content-Length"); + QByteArray clength = _requestHeaders.value("content-length"); if (clength.isEmpty()) { _parentManager->handleHTTPRequest(this, _requestUrl); @@ -275,7 +275,7 @@ void HTTPConnection::readHeaders() { respond("400 Bad Request", "The header was malformed."); return; } - _lastRequestHeader = trimmed.left(idx); + _lastRequestHeader = trimmed.left(idx).toLower(); QByteArray& value = _requestHeaders[_lastRequestHeader]; if (!value.isEmpty()) { value.append(", "); diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index 60408d4325..e4d23e3c90 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -72,8 +72,8 @@ public: /// Returns a reference to the request URL. const QUrl& requestUrl () const { return _requestUrl; } - /// Returns a reference to the request headers. - const Headers& requestHeaders () const { return _requestHeaders; } + /// Returns a copy of the request header value. If it does not exist, it will return a default constructed QByteArray. + QByteArray requestHeader(const QString& key) const { return _requestHeaders.value(key.toLower().toLocal8Bit()); } /// Returns a reference to the request content. const QByteArray& requestContent () const { return _requestContent; } From debea3791de4d9aa6ac22b329c705e1bcc365063 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Mar 2018 14:22:46 -0700 Subject: [PATCH 5/8] Fix DS content backups not loading on IE IE does not support Object.assign --- domain-server/resources/web/js/shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 84bba4de56..1647da045f 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -2,7 +2,7 @@ if (typeof Settings === "undefined") { Settings = {}; } -Object.assign(Settings, { +$.extend(Settings, { DEPRECATED_CLASS: 'deprecated-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', From 2f5928305393a6ddf16c75cb31877b959e4c18e2 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 16 Mar 2018 14:48:08 -0700 Subject: [PATCH 6/8] Fix initialization of QML overlay surfaces --- interface/src/ui/overlays/Web3DOverlay.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index d460ff44e8..6bf5370ca5 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -183,6 +183,7 @@ void Web3DOverlay::buildWebSurface() { _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); } else { _webSurface = QSharedPointer(new OffscreenQmlSurface(), qmlSurfaceDeleter); + _webSurface->load(_url); _cachedWebSurface = false; setupQmlSurface(); } From fdca6e083253d88db1c09c3c45c31671bfbb6035 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 16 Mar 2018 15:21:36 -0700 Subject: [PATCH 7/8] Update direct _requestHeaders access to be consistent with other reads --- libraries/embedded-webserver/src/HTTPConnection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 845822cdf7..280b44cec0 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -251,7 +251,7 @@ void HTTPConnection::readHeaders() { if (trimmed.isEmpty()) { _socket->disconnect(this, SLOT(readHeaders())); - QByteArray clength = _requestHeaders.value("content-length"); + QByteArray clength = requestHeader("Content-Length"); if (clength.isEmpty()) { _parentManager->handleHTTPRequest(this, _requestUrl); From 3f2a49d2435815cbe7c9fe7faa18bdc71604a4b0 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 16 Mar 2018 16:57:54 -0700 Subject: [PATCH 8/8] fix list of tablet IDs ray cast checks in edit.js --- scripts/system/edit.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 0e1718ac45..1f442c09b0 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -776,9 +776,12 @@ function findClickedEntity(event) { } var pickRay = Camera.computePickRay(event.x, event.y); - var overlayResult = Overlays.findRayIntersection(pickRay, true, getMainTabletIDs()); - if (overlayResult.intersects) { - return null; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length > 0) { + var overlayResult = Overlays.findRayIntersection(pickRay, true, tabletIDs); + if (overlayResult.intersects) { + return null; + } } var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking @@ -967,8 +970,13 @@ function mouseReleaseEvent(event) { function wasTabletClicked(event) { var rayPick = Camera.computePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs()); - return result.intersects; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.length === 0) { + return false; + } else { + var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs()); + return result.intersects; + } } function mouseClickEvent(event) {