diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 3ff7eafd6b..1955b8f0c8 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -55,13 +55,7 @@ Agent::Agent(ReceivedMessage& message) : { DependencyManager::get()->setPacketSender(&_entityEditSender); - auto assetClient = DependencyManager::set(); - - QThread* assetThread = new QThread; - assetThread->setObjectName("Asset Thread"); - assetClient->moveToThread(assetThread); - connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init); - assetThread->start(); + ResourceManager::init(); DependencyManager::registerInheritance(); @@ -471,11 +465,7 @@ void Agent::aboutToFinish() { // our entity tree is going to go away so tell that to the EntityScriptingInterface DependencyManager::get()->setEntityTree(nullptr); - // cleanup the AssetClient thread - QThread* assetThread = DependencyManager::get()->thread(); - DependencyManager::destroy(); - assetThread->quit(); - assetThread->wait(); + ResourceManager::cleanup(); // cleanup the AudioInjectorManager (and any still running injectors) DependencyManager::destroy(); diff --git a/examples/away.js b/examples/away.js index 18a3fddfce..8d561f2623 100644 --- a/examples/away.js +++ b/examples/away.js @@ -13,13 +13,30 @@ // // Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key. // See MAIN CONTROL, below, for what "paused" actually does. -var OVERLAY_RATIO = 1920 / 1080; +var OVERLAY_WIDTH = 1920; +var OVERLAY_HEIGHT = 1080; +var OVERLAY_RATIO = OVERLAY_WIDTH / OVERLAY_HEIGHT; var OVERLAY_DATA = { + width: OVERLAY_WIDTH, + height: OVERLAY_HEIGHT, imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", color: {red: 255, green: 255, blue: 255}, alpha: 1 }; +var lastOverlayPosition = { x: 0, y: 0, z: 0}; +var OVERLAY_DATA_HMD = { + position: lastOverlayPosition, + width: OVERLAY_WIDTH, + height: OVERLAY_HEIGHT, + url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", + color: {red: 255, green: 255, blue: 255}, + alpha: 1, + scale: 2, + isFacingAvatar: true, + drawInFront: true +}; + // ANIMATION // We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect // using an animation graph with a state that we turn on and off through the animation var defined with that state. @@ -64,29 +81,74 @@ function stopAwayAnimation() { // OVERLAY var overlay = Overlays.addOverlay("image", OVERLAY_DATA); +var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD); + +function moveCloserToCamera(positionAtHUD) { + // we don't actually want to render at the slerped look at... instead, we want to render + // slightly closer to the camera than that. + var MOVE_CLOSER_TO_CAMERA_BY = -0.25; + var cameraFront = Quat.getFront(Camera.orientation); + var closerToCamera = Vec3.multiply(cameraFront, MOVE_CLOSER_TO_CAMERA_BY); // slightly closer to camera + var slightlyCloserPosition = Vec3.sum(positionAtHUD, closerToCamera); + + return slightlyCloserPosition; +} + function showOverlay() { - var properties = {visible: true}, - // Update for current screen size, keeping overlay proportions constant. - screen = Controller.getViewportDimensions(), - screenRatio = screen.x / screen.y; - if (screenRatio < OVERLAY_RATIO) { - properties.width = screen.x; - properties.height = screen.x / OVERLAY_RATIO; - properties.x = 0; - properties.y = (screen.y - properties.height) / 2; + var properties = {visible: true}; + + if (HMD.active) { + // make sure desktop version is hidden + Overlays.editOverlay(overlay, { visible: false }); + + lastOverlayPosition = HMD.getHUDLookAtPosition3D(); + var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition); + Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon }); } else { - properties.height = screen.y; - properties.width = screen.y * OVERLAY_RATIO; - properties.y = 0; - properties.x = (screen.x - properties.width) / 2; + // make sure HMD is hidden + Overlays.editOverlay(overlayHMD, { visible: false }); + + // Update for current screen size, keeping overlay proportions constant. + var screen = Controller.getViewportDimensions(); + + // keep the overlay it's natural size and always center it... + Overlays.editOverlay(overlay, { visible: true, + x: ((screen.x - OVERLAY_WIDTH) / 2), + y: ((screen.y - OVERLAY_HEIGHT) / 2) }); } - Overlays.editOverlay(overlay, properties); } function hideOverlay() { Overlays.editOverlay(overlay, {visible: false}); + Overlays.editOverlay(overlayHMD, {visible: false}); } hideOverlay(); +function maybeMoveOverlay() { + if (isAway) { + // if we switched from HMD to Desktop, make sure to hide our HUD overlay and show the + // desktop overlay + if (!HMD.active) { + showOverlay(); // this will also recenter appropriately + } + + if (HMD.active) { + // Note: instead of moving it directly to the lookAt, we will move it slightly toward the + // new look at. This will result in a more subtle slerp toward the look at and reduce jerkiness + var EASE_BY_RATIO = 0.1; + var lookAt = HMD.getHUDLookAtPosition3D(); + var lookAtChange = Vec3.subtract(lookAt, lastOverlayPosition); + var halfWayBetweenOldAndLookAt = Vec3.multiply(lookAtChange, EASE_BY_RATIO); + var newOverlayPosition = Vec3.sum(lastOverlayPosition, halfWayBetweenOldAndLookAt); + lastOverlayPosition = newOverlayPosition; + + var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition); + Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon }); + + // make sure desktop version is hidden + Overlays.editOverlay(overlay, { visible: false }); + } + } +} // MAIN CONTROL var wasMuted, isAway; @@ -106,6 +168,12 @@ function goAway() { MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view playAwayAnimation(); // animation is still seen by others showOverlay(); + + // tell the Reticle, we want to stop capturing the mouse until we come back + Reticle.allowMouseCapture = false; + if (HMD.active) { + Reticle.visible = false; + } } function goActive() { if (!isAway) { @@ -119,13 +187,20 @@ function goActive() { MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting. stopAwayAnimation(); hideOverlay(); + + // tell the Reticle, we are ready to capture the mouse again and it should be visible + Reticle.allowMouseCapture = true; + Reticle.visible = true; + if (HMD.active) { + Reticle.position = HMD.getHUDLookAtPosition2D(); + } } function maybeGoActive(event) { if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it) return; } - if (!isAway && (event.text === '.')) { + if (!isAway && (event.text == 'ESC')) { goAway(); } else { goActive(); @@ -141,10 +216,8 @@ function maybeGoAway() { } } - // If the mouse has gone from captured, to non-captured state, - // then it likely means the person is still in the HMD, but has - // tabbed away from the application (meaning they don't have mouse - // control) and they likely want to go into an away state + // If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD, but + // tabbed away from the application (meaning they don't have mouse control) and they likely want to go into an away state if (Reticle.mouseCaptured !== wasMouseCaptured) { wasMouseCaptured = !wasMouseCaptured; if (!wasMouseCaptured) { @@ -153,6 +226,8 @@ function maybeGoAway() { } } +Script.update.connect(maybeMoveOverlay); + Script.update.connect(maybeGoAway); Controller.mousePressEvent.connect(goActive); Controller.keyPressEvent.connect(maybeGoActive); diff --git a/examples/depthReticle.js b/examples/depthReticle.js index 14a5ba5ff3..14215bff3d 100644 --- a/examples/depthReticle.js +++ b/examples/depthReticle.js @@ -35,8 +35,8 @@ var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50; Controller.mouseMoveEvent.connect(function(mouseEvent) { var now = Date.now(); - // if the reticle is hidden, show it... - if (!Reticle.visible) { + // if the reticle is hidden, and we're not in away mode... + if (!Reticle.visible && Reticle.allowMouseCapture) { Reticle.visible = true; if (HMD.active) { shouldSeekToLookAt = true; @@ -44,7 +44,7 @@ Controller.mouseMoveEvent.connect(function(mouseEvent) { } else { // even if the reticle is visible, if we're in HMD mode, and the person is moving their mouse quickly (shaking it) // then they are probably looking for it, and we should move into seekToLookAt mode - if (HMD.active && !shouldSeekToLookAt) { + if (HMD.active && !shouldSeekToLookAt && Reticle.allowMouseCapture) { var dx = Reticle.position.x - lastMouseX; var dy = Reticle.position.y - lastMouseY; var dt = Math.max(1, (now - lastMouseMove)); // mSecs since last mouse move diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 82e44fa907..a34fb3a38e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -370,7 +370,6 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -528,13 +527,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : audioThread->start(); - // Setup AssetClient - auto assetClient = DependencyManager::get(); - QThread* assetThread = new QThread; - assetThread->setObjectName("Asset Thread"); - assetClient->moveToThread(assetThread); - connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init); - assetThread->start(); + ResourceManager::init(); // Setup MessagesClient auto messagesClient = DependencyManager::get(); @@ -547,7 +540,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); - connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); @@ -644,13 +637,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket); identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); - QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkDiskCache* cache = new QNetworkDiskCache(); - cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); - cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); - networkAccessManager.setCache(cache); - ResourceCache::setRequestLimit(3); _glWidget = new GLCanvas(); @@ -661,15 +647,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); + #ifdef Q_OS_MAC - // OSX doesn't seem to provide for hiding the cursor only on the GL widget - _window->setCursor(Qt::BlankCursor); + auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget #else - // On windows and linux, hiding the top level cursor also means it's invisible - // when hovering over the window menu, which is a pain, so only hide it for - // the GL surface - _glWidget->setCursor(Qt::BlankCursor); + // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the + // window menu, which is a pain, so only hide it for the GL surface + auto cursorTarget = _glWidget; #endif + cursorTarget->setCursor(Qt::BlankCursor); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); @@ -981,6 +967,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _idleTimer->start(0); } + +void Application::checkChangeCursor() { + QMutexLocker locker(&_changeCursorLock); + if (_cursorNeedsChanging) { +#ifdef Q_OS_MAC + auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget +#else + // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the + // window menu, which is a pain, so only hide it for the GL surface + auto cursorTarget = _glWidget; +#endif + cursorTarget->setCursor(_desiredCursor); + + _cursorNeedsChanging = false; + } +} + +void Application::showCursor(const QCursor& cursor) { + QMutexLocker locker(&_changeCursorLock); + _desiredCursor = cursor; + _cursorNeedsChanging = true; +} + void Application::aboutToQuit() { emit beforeAboutToQuit(); @@ -1062,13 +1071,6 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); } -void Application::emptyLocalCache() { - if (auto cache = NetworkAccessManager::getInstance().cache()) { - qDebug() << "DiskCacheEditor::clear(): Clearing disk cache."; - cache->clear(); - } -} - Application::~Application() { EntityTreePointer tree = getEntities()->getTree(); tree->setSimulation(NULL); @@ -1106,11 +1108,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); - // cleanup the AssetClient thread - QThread* assetThread = DependencyManager::get()->thread(); - DependencyManager::destroy(); - assetThread->quit(); - assetThread->wait(); + ResourceManager::cleanup(); QThread* nodeThread = DependencyManager::get()->thread(); @@ -2431,6 +2429,9 @@ void Application::idle(uint64_t now) { return; // bail early, nothing to do here. } + + checkChangeCursor(); + Stats::getInstance()->updateStats(); AvatarInputs::getInstance()->update(); @@ -3055,7 +3056,7 @@ void Application::reloadResourceCaches() { _viewFrustum.setOrientation(glm::quat()); queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); - emptyLocalCache(); + DependencyManager::get()->clearCache(); DependencyManager::get()->refreshAll(); DependencyManager::get()->refreshAll(); @@ -3978,13 +3979,8 @@ void Application::handleDomainConnectionDeniedPacket(QSharedPointer()->getDomainHandler().getUUID(); - - if (accountManager.isLoggedIn() && !domainID.isNull()) { - _notifiedPacketVersionMismatchThisDomain = false; - } +void Application::resettingDomain() { + _notifiedPacketVersionMismatchThisDomain = false; } void Application::nodeAdded(SharedNodePointer node) { diff --git a/interface/src/Application.h b/interface/src/Application.h index d205ce8041..852e3e86a6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -120,6 +120,8 @@ public: QSize getDeviceSize() const; bool hasFocus() const; + void showCursor(const QCursor& cursor); + bool isThrottleRendering() const; Camera* getCamera() { return &_myCamera; } @@ -288,7 +290,7 @@ private slots: void idle(uint64_t now); void aboutToQuit(); - void connectedToDomain(const QString& hostname); + void resettingDomain(); void audioMuteToggled(); void faceTrackerMuteToggled(); @@ -328,8 +330,6 @@ private: void cleanupBeforeQuit(); - void emptyLocalCache(); - void update(float deltaTime); void setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue); @@ -515,6 +515,11 @@ private: QTimer* _idleTimer { nullptr }; bool _fakedMouseEvent { false }; + + void checkChangeCursor(); + mutable QMutex _changeCursorLock { QMutex::Recursive }; + QCursor _desiredCursor{ Qt::BlankCursor }; + bool _cursorNeedsChanging { false }; }; #endif // hifi_Application_h diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index e8e6a0a956..efd69c7859 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -337,9 +337,21 @@ QPointF ApplicationCompositor::getMouseEventPosition(QMouseEvent* event) { bool ApplicationCompositor::shouldCaptureMouse() const { // if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu - return qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown(); + return _allowMouseCapture && qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown(); } +void ApplicationCompositor::setAllowMouseCapture(bool capture) { + if (qApp->isHMDMode()) { + if (capture) { + qApp->showCursor(Qt::BlankCursor); + } else { + qApp->showCursor(Qt::ArrowCursor); + } + } + _allowMouseCapture = capture; +} + + void ApplicationCompositor::handleLeaveEvent() { if (shouldCaptureMouse()) { diff --git a/interface/src/ui/ApplicationCompositor.h b/interface/src/ui/ApplicationCompositor.h index 32835ace5a..324250deb1 100644 --- a/interface/src/ui/ApplicationCompositor.h +++ b/interface/src/ui/ApplicationCompositor.h @@ -106,6 +106,9 @@ public: bool shouldCaptureMouse() const; + bool getAllowMouseCapture() const { return _allowMouseCapture; } + void setAllowMouseCapture(bool capture); + /// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false bool getReticleOverDesktop() const; void setReticleOverDesktop(bool value) { _isOverDesktop = value; } @@ -162,6 +165,8 @@ private: bool _reticleOverQml { false }; + bool _allowMouseCapture { true }; + ReticleInterface* _reticleInterface; }; @@ -173,12 +178,17 @@ class ReticleInterface : public QObject { Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) + Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture) Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay) public: ReticleInterface(ApplicationCompositor* outer) : QObject(outer), _compositor(outer) {} Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); } + + Q_INVOKABLE bool getAllowMouseCapture() { return _compositor->getAllowMouseCapture(); } + Q_INVOKABLE void setAllowMouseCapture(bool value) { return _compositor->setAllowMouseCapture(value); } + Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); } Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); } diff --git a/interface/src/ui/DiskCacheEditor.cpp b/interface/src/ui/DiskCacheEditor.cpp index d3a40e6439..ed85b87d63 100644 --- a/interface/src/ui/DiskCacheEditor.cpp +++ b/interface/src/ui/DiskCacheEditor.cpp @@ -9,23 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "DiskCacheEditor.h" #include #include #include #include #include -#include +#include #include -#include +#include -#include "DiskCacheEditor.h" #include "OffscreenUi.h" DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) { - } QWindow* DiskCacheEditor::windowHandle() { @@ -33,7 +31,6 @@ QWindow* DiskCacheEditor::windowHandle() { } void DiskCacheEditor::toggle() { - qDebug() << "DiskCacheEditor::toggle()"; if (!_dialog) { makeDialog(); } @@ -88,17 +85,17 @@ void DiskCacheEditor::makeDialog() { Q_CHECK_PTR(_maxSize); _maxSize->setAlignment(Qt::AlignLeft); layout->addWidget(_maxSize, 2, 1, 1, 3); - + refresh(); - - - QPushButton* refreshCacheButton = new QPushButton(_dialog); - Q_CHECK_PTR(refreshCacheButton); - refreshCacheButton->setText("Refresh"); - refreshCacheButton->setToolTip("Reload the cache stats."); - connect(refreshCacheButton, SIGNAL(clicked()), SLOT(refresh())); - layout->addWidget(refreshCacheButton, 3, 2); - + + + static const int REFRESH_INTERVAL = 100; // msec + _refreshTimer = new QTimer(_dialog); + _refreshTimer->setInterval(REFRESH_INTERVAL); + _refreshTimer->setSingleShot(false); + QObject::connect(_refreshTimer.data(), &QTimer::timeout, this, &DiskCacheEditor::refresh); + _refreshTimer->start(); + QPushButton* clearCacheButton = new QPushButton(_dialog); Q_CHECK_PTR(clearCacheButton); clearCacheButton->setText("Clear"); @@ -108,7 +105,11 @@ void DiskCacheEditor::makeDialog() { } void DiskCacheEditor::refresh() { - static const std::function stringify = [](qint64 number) { + DependencyManager::get()->cacheInfoRequest(this, "cacheInfoCallback"); +} + +void DiskCacheEditor::cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize) { + static const auto stringify = [](qint64 number) { static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB"; static const qint64 CHUNK = 1024; QString unit; @@ -122,30 +123,24 @@ void DiskCacheEditor::refresh() { } return QString("%0 %1").arg(number).arg(UNITS[i]); }; - QNetworkDiskCache* cache = qobject_cast(NetworkAccessManager::getInstance().cache()); if (_path) { - _path->setText(cache->cacheDirectory()); + _path->setText(cacheDirectory); } if (_size) { - _size->setText(stringify(cache->cacheSize())); + _size->setText(stringify(cacheSize)); } if (_maxSize) { - _maxSize->setText(stringify(cache->maximumCacheSize())); + _maxSize->setText(stringify(maximumCacheSize)); } } void DiskCacheEditor::clear() { - QMessageBox::StandardButton buttonClicked = - OffscreenUi::question(_dialog, "Clearing disk cache", - "You are about to erase all the content of the disk cache, " - "are you sure you want to do that?", - QMessageBox::Ok | QMessageBox::Cancel); + auto buttonClicked = OffscreenUi::question(_dialog, "Clearing disk cache", + "You are about to erase all the content of the disk cache, " + "are you sure you want to do that?", + QMessageBox::Ok | QMessageBox::Cancel); if (buttonClicked == QMessageBox::Ok) { - if (auto cache = NetworkAccessManager::getInstance().cache()) { - qDebug() << "DiskCacheEditor::clear(): Clearing disk cache."; - cache->clear(); - } + DependencyManager::get()->clearCache(); } - refresh(); } diff --git a/interface/src/ui/DiskCacheEditor.h b/interface/src/ui/DiskCacheEditor.h index 5d673c4285..3f8fa1a883 100644 --- a/interface/src/ui/DiskCacheEditor.h +++ b/interface/src/ui/DiskCacheEditor.h @@ -18,6 +18,7 @@ class QDialog; class QLabel; class QWindow; +class QTimer; class DiskCacheEditor : public QObject { Q_OBJECT @@ -32,8 +33,9 @@ public slots: private slots: void refresh(); + void cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize); void clear(); - + private: void makeDialog(); @@ -41,6 +43,7 @@ private: QPointer _path; QPointer _size; QPointer _maxSize; + QPointer _refreshTimer; }; #endif // hifi_DiskCacheEditor_h \ No newline at end of file diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 85cbbbd328..44c3146b41 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -322,7 +322,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getKeyLightProperties().getAmbientURL(), CUBE_TEXTURE); - if (_ambientTexture->getGPUTexture()) { + if (_ambientTexture && _ambientTexture->isLoaded() && _ambientTexture->getGPUTexture()) { _pendingAmbientTexture = false; if (_ambientTexture->getGPUTexture()->getIrradiance()) { sceneKeyLight->setAmbientSphere(_ambientTexture->getGPUTexture()->getIrradiance()); @@ -356,7 +356,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE); - if (_skyboxTexture->getGPUTexture()) { + if (_skyboxTexture && _skyboxTexture->isLoaded() && _skyboxTexture->getGPUTexture()) { auto texture = _skyboxTexture->getGPUTexture(); skybox->setCubemap(texture); _pendingSkyboxTexture = false; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index f795472137..21eed5f59f 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -338,105 +338,84 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas return networkMesh; } -static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FBXMaterial& material, const QUrl& textureBaseUrl) { - auto textureCache = DependencyManager::get(); - NetworkMaterial* networkMaterial = new NetworkMaterial(); +static model::TextureMapPointer setupNetworkTextureMap(NetworkGeometry* geometry, const QUrl& textureBaseUrl, + const FBXTexture& texture, TextureType type, + NetworkTexturePointer& networkTexture, QString& networkTextureName) { + auto textureCache = DependencyManager::get(); + + // If content is inline, cache it under the fbx file, not its base url + const auto baseUrl = texture.content.isEmpty() ? textureBaseUrl : QUrl(textureBaseUrl.url() + "/"); + const auto filename = baseUrl.resolved(QUrl(texture.filename)); + + networkTexture = textureCache->getTexture(filename, type, texture.content); + QObject::connect(networkTexture.data(), &NetworkTexture::networkTextureCreated, geometry, &NetworkGeometry::textureLoaded); + networkTextureName = texture.name; + + auto map = std::make_shared(); + map->setTextureSource(networkTexture->_textureSource); + return map; +} + +static NetworkMaterial* buildNetworkMaterial(NetworkGeometry* geometry, const FBXMaterial& material, const QUrl& textureBaseUrl) { + NetworkMaterial* networkMaterial = new NetworkMaterial(); networkMaterial->_material = material._material; if (!material.albedoTexture.filename.isEmpty()) { - networkMaterial->albedoTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.albedoTexture.filename)), DEFAULT_TEXTURE, material.albedoTexture.content); - QObject::connect(networkMaterial->albedoTexture.data(), &NetworkTexture::networkTextureCreated, - geometry, &NetworkGeometry::textureLoaded); - - networkMaterial->albedoTextureName = material.albedoTexture.name; - - auto albedoMap = model::TextureMapPointer(new model::TextureMap()); - albedoMap->setTextureSource(networkMaterial->albedoTexture->_textureSource); + auto albedoMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.albedoTexture, DEFAULT_TEXTURE, + networkMaterial->albedoTexture, networkMaterial->albedoTextureName); albedoMap->setTextureTransform(material.albedoTexture.transform); - material._material->setTextureMap(model::MaterialKey::ALBEDO_MAP, albedoMap); } if (!material.normalTexture.filename.isEmpty()) { - networkMaterial->normalTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.normalTexture.filename)), (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE), material.normalTexture.content); - networkMaterial->normalTextureName = material.normalTexture.name; - - auto normalMap = model::TextureMapPointer(new model::TextureMap()); - normalMap->setTextureSource(networkMaterial->normalTexture->_textureSource); - - material._material->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap); + auto normalMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.normalTexture, + (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE), + networkMaterial->normalTexture, networkMaterial->normalTextureName); + networkMaterial->_material->setTextureMap(model::MaterialKey::NORMAL_MAP, normalMap); } // Roughness first or gloss maybe if (!material.roughnessTexture.filename.isEmpty()) { - networkMaterial->roughnessTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.roughnessTexture.filename)), ROUGHNESS_TEXTURE, material.roughnessTexture.content); - networkMaterial->roughnessTextureName = material.roughnessTexture.name; - - auto roughnessMap = model::TextureMapPointer(new model::TextureMap()); - roughnessMap->setTextureSource(networkMaterial->roughnessTexture->_textureSource); - + auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE, + networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName); material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap); } else if (!material.glossTexture.filename.isEmpty()) { - networkMaterial->roughnessTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.glossTexture.filename)), GLOSS_TEXTURE, material.glossTexture.content); - networkMaterial->roughnessTextureName = material.roughnessTexture.name; - - auto roughnessMap = model::TextureMapPointer(new model::TextureMap()); - roughnessMap->setTextureSource(networkMaterial->roughnessTexture->_textureSource); - + auto roughnessMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.glossTexture, GLOSS_TEXTURE, + networkMaterial->roughnessTexture, networkMaterial->roughnessTextureName); material._material->setTextureMap(model::MaterialKey::ROUGHNESS_MAP, roughnessMap); } // Metallic first or specular maybe if (!material.metallicTexture.filename.isEmpty()) { - networkMaterial->metallicTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.metallicTexture.filename)), METALLIC_TEXTURE, material.metallicTexture.content); - networkMaterial->metallicTextureName = material.metallicTexture.name; - - auto metallicMap = model::TextureMapPointer(new model::TextureMap()); - metallicMap->setTextureSource(networkMaterial->metallicTexture->_textureSource); - + auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE, + networkMaterial->metallicTexture, networkMaterial->metallicTextureName); material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap); } else if (!material.specularTexture.filename.isEmpty()) { - networkMaterial->metallicTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.specularTexture.filename)), SPECULAR_TEXTURE, material.specularTexture.content); - networkMaterial->metallicTextureName = material.specularTexture.name; - - auto metallicMap = model::TextureMapPointer(new model::TextureMap()); - metallicMap->setTextureSource(networkMaterial->metallicTexture->_textureSource); + auto metallicMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE, + networkMaterial->metallicTexture, networkMaterial->metallicTextureName); material._material->setTextureMap(model::MaterialKey::METALLIC_MAP, metallicMap); } if (!material.occlusionTexture.filename.isEmpty()) { - networkMaterial->occlusionTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.occlusionTexture.filename)), OCCLUSION_TEXTURE, material.occlusionTexture.content); - networkMaterial->occlusionTextureName = material.occlusionTexture.name; - - auto occlusionMap = model::TextureMapPointer(new model::TextureMap()); - occlusionMap->setTextureSource(networkMaterial->occlusionTexture->_textureSource); - + auto occlusionMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE, + networkMaterial->occlusionTexture, networkMaterial->occlusionTextureName); material._material->setTextureMap(model::MaterialKey::OCCLUSION_MAP, occlusionMap); } if (!material.emissiveTexture.filename.isEmpty()) { - networkMaterial->emissiveTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.emissiveTexture.filename)), EMISSIVE_TEXTURE, material.emissiveTexture.content); - networkMaterial->emissiveTextureName = material.emissiveTexture.name; - - auto emissiveMap = model::TextureMapPointer(new model::TextureMap()); - emissiveMap->setTextureSource(networkMaterial->emissiveTexture->_textureSource); - + auto emissiveMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE, + networkMaterial->emissiveTexture, networkMaterial->emissiveTextureName); material._material->setTextureMap(model::MaterialKey::EMISSIVE_MAP, emissiveMap); } if (!material.lightmapTexture.filename.isEmpty()) { - networkMaterial->lightmapTexture = textureCache->getTexture(textureBaseUrl.resolved(QUrl(material.lightmapTexture.filename)), LIGHTMAP_TEXTURE, material.lightmapTexture.content); - networkMaterial->lightmapTextureName = material.lightmapTexture.name; - - - auto lightmapMap = model::TextureMapPointer(new model::TextureMap()); - lightmapMap->setTextureSource(networkMaterial->lightmapTexture->_textureSource); + auto lightmapMap = setupNetworkTextureMap(geometry, textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE, + networkMaterial->lightmapTexture, networkMaterial->lightmapTextureName); lightmapMap->setTextureTransform(material.lightmapTexture.transform); lightmapMap->setLightmapOffsetScale(material.lightmapParams.x, material.lightmapParams.y); - material._material->setTextureMap(model::MaterialKey::LIGHTMAP_MAP, lightmapMap); } diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 4e7e7f24ba..9591828fef 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -47,22 +47,53 @@ AssetClient::AssetClient() { } void AssetClient::init() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection); - } - + Q_ASSERT(QThread::currentThread() == thread()); + // Setup disk cache if not already - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + auto& networkAccessManager = NetworkAccessManager::getInstance(); if (!networkAccessManager.cache()) { QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); cachePath = !cachePath.isEmpty() ? cachePath : "interfaceCache"; - + QNetworkDiskCache* cache = new QNetworkDiskCache(); cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); cache->setCacheDirectory(cachePath); networkAccessManager.setCache(cache); - qCDebug(asset_client) << "AssetClient disk cache setup at" << cachePath - << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; + qDebug() << "ResourceManager disk cache setup at" << cachePath + << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; + } +} + + +void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "cacheInfoRequest", Qt::QueuedConnection, + Q_ARG(QObject*, reciever), Q_ARG(QString, slot)); + return; + } + + + if (auto* cache = qobject_cast(NetworkAccessManager::getInstance().cache())) { + QMetaObject::invokeMethod(reciever, slot.toStdString().data(), Qt::QueuedConnection, + Q_ARG(QString, cache->cacheDirectory()), + Q_ARG(qint64, cache->cacheSize()), + Q_ARG(qint64, cache->maximumCacheSize())); + } else { + qCWarning(asset_client) << "No disk cache to get info from."; + } +} + +void AssetClient::clearCache() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearCache", Qt::QueuedConnection); + return; + } + + if (auto cache = NetworkAccessManager::getInstance().cache()) { + qDebug() << "AssetClient::clearCache(): Clearing disk cache."; + cache->clear(); + } else { + qCWarning(asset_client) << "No disk cache to clear."; } } diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 58790ef926..f66fe8adcc 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -43,13 +43,17 @@ class AssetClient : public QObject, public Dependency { Q_OBJECT public: AssetClient(); - - Q_INVOKABLE void init(); Q_INVOKABLE AssetRequest* createRequest(const QString& hash, const QString& extension); Q_INVOKABLE AssetUpload* createUpload(const QString& filename); Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data, const QString& extension); +public slots: + void init(); + + void cacheInfoRequest(QObject* reciever, QString slot); + void clearCache(); + private slots: void handleAssetGetInfoReply(QSharedPointer message, SharedNodePointer senderNode); void handleAssetGetReply(QSharedPointer message, SharedNodePointer senderNode); diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 0024426be0..4d2ea56ca4 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -20,7 +20,7 @@ class AssetResourceRequest : public ResourceRequest { Q_OBJECT public: - AssetResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + AssetResourceRequest(const QUrl& url) : ResourceRequest(url) { } ~AssetResourceRequest(); protected: diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index db775983e1..bf27789c54 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -98,6 +98,8 @@ void DomainHandler::softReset() { } void DomainHandler::hardReset() { + emit resetting(); + softReset(); qCDebug(networking) << "Hard reset in NodeList DomainHandler."; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index f60ac2fbe6..957f52144b 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -104,6 +104,7 @@ signals: // It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on void completedSocketDiscovery(); + void resetting(); void connectedToDomain(const QString& hostname); void disconnectedFromDomain(); diff --git a/libraries/networking/src/FileResourceRequest.h b/libraries/networking/src/FileResourceRequest.h index 4ff0d2ecf2..547b754cb5 100644 --- a/libraries/networking/src/FileResourceRequest.h +++ b/libraries/networking/src/FileResourceRequest.h @@ -19,7 +19,7 @@ class FileResourceRequest : public ResourceRequest { Q_OBJECT public: - FileResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + FileResourceRequest(const QUrl& url) : ResourceRequest(url) { } protected: virtual void doSend() override; diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index 34c9c1dad2..11ab436933 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -28,6 +28,25 @@ HTTPResourceRequest::~HTTPResourceRequest() { } } +void HTTPResourceRequest::setupTimer() { + Q_ASSERT(!_sendTimer); + static const int TIMEOUT_MS = 10000; + + _sendTimer = new QTimer(); + connect(this, &QObject::destroyed, _sendTimer, &QTimer::deleteLater); + connect(_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout); + + _sendTimer->setSingleShot(true); + _sendTimer->start(TIMEOUT_MS); +} + +void HTTPResourceRequest::cleanupTimer() { + Q_ASSERT(_sendTimer); + _sendTimer->disconnect(this); + _sendTimer->deleteLater(); + _sendTimer = nullptr; +} + void HTTPResourceRequest::doSend() { QNetworkRequest networkRequest(_url); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); @@ -42,18 +61,15 @@ void HTTPResourceRequest::doSend() { connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished); connect(_reply, &QNetworkReply::downloadProgress, this, &HTTPResourceRequest::onDownloadProgress); - connect(&_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout); - static const int TIMEOUT_MS = 10000; - _sendTimer.setSingleShot(true); - _sendTimer.start(TIMEOUT_MS); + setupTimer(); } void HTTPResourceRequest::onRequestFinished() { Q_ASSERT(_state == InProgress); Q_ASSERT(_reply); - _sendTimer.stop(); + cleanupTimer(); switch(_reply->error()) { case QNetworkReply::NoError: @@ -80,7 +96,7 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT Q_ASSERT(_state == InProgress); // We've received data, so reset the timer - _sendTimer.start(); + _sendTimer->start(); emit progress(bytesReceived, bytesTotal); } @@ -91,6 +107,8 @@ void HTTPResourceRequest::onTimeout() { _reply->abort(); _reply->deleteLater(); _reply = nullptr; + + cleanupTimer(); _result = Timeout; _state = Finished; diff --git a/libraries/networking/src/HTTPResourceRequest.h b/libraries/networking/src/HTTPResourceRequest.h index f0d3bb82d9..cc628d8855 100644 --- a/libraries/networking/src/HTTPResourceRequest.h +++ b/libraries/networking/src/HTTPResourceRequest.h @@ -21,7 +21,7 @@ class HTTPResourceRequest : public ResourceRequest { Q_OBJECT public: - HTTPResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + HTTPResourceRequest(const QUrl& url) : ResourceRequest(url) { } ~HTTPResourceRequest(); protected: @@ -33,7 +33,10 @@ private slots: void onRequestFinished(); private: - QTimer _sendTimer; + void setupTimer(); + void cleanupTimer(); + + QTimer* _sendTimer { nullptr }; QNetworkReply* _reply { nullptr }; }; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index f195011290..1d41266f3a 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -11,12 +11,20 @@ #include "ResourceManager.h" -#include "AssetResourceRequest.h" -#include "FileResourceRequest.h" -#include "HTTPResourceRequest.h" +#include +#include +#include #include + +#include "AssetResourceRequest.h" +#include "FileResourceRequest.h" +#include "HTTPResourceRequest.h" +#include "NetworkAccessManager.h" + + +QThread ResourceManager::_thread; ResourceManager::PrefixMap ResourceManager::_prefixMap; QMutex ResourceManager::_prefixMapLock; @@ -67,18 +75,41 @@ QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) { return url; } +void ResourceManager::init() { + _thread.setObjectName("Ressource Manager Thread"); + + auto assetClient = DependencyManager::set(); + assetClient->moveToThread(&_thread); + QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::init); + + _thread.start(); +} + +void ResourceManager::cleanup() { + // cleanup the AssetClient thread + DependencyManager::destroy(); + _thread.quit(); + _thread.wait(); +} + ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { auto normalizedURL = normalizeURL(url); auto scheme = normalizedURL.scheme(); + + ResourceRequest* request = nullptr; + if (scheme == URL_SCHEME_FILE) { - return new FileResourceRequest(parent, normalizedURL); + request = new FileResourceRequest(normalizedURL); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { - return new HTTPResourceRequest(parent, normalizedURL); + request = new HTTPResourceRequest(normalizedURL); } else if (scheme == URL_SCHEME_ATP) { - return new AssetResourceRequest(parent, normalizedURL); + request = new AssetResourceRequest(normalizedURL); + } else { + qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); + return nullptr; } + Q_ASSERT(request); - qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); - - return nullptr; + request->moveToThread(&_thread); + return request; } diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index c010c67f9b..162892abaf 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -29,8 +29,15 @@ public: static void setUrlPrefixOverride(const QString& prefix, const QString& replacement); static QString normalizeURL(const QString& urlString); static QUrl normalizeURL(const QUrl& url); + static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); + + static void init(); + static void cleanup(); + private: + static QThread _thread; + using PrefixMap = std::map; static PrefixMap _prefixMap; diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index c6880636ea..e6402d6b25 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -11,12 +11,15 @@ #include "ResourceRequest.h" -ResourceRequest::ResourceRequest(QObject* parent, const QUrl& url) : - QObject(parent), - _url(url) { -} +#include + +ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { } void ResourceRequest::send() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); + return; + } Q_ASSERT(_state == NotStarted); _state = InProgress; diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index b229951a49..f940221d9d 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -20,7 +20,7 @@ class ResourceRequest : public QObject { Q_OBJECT public: - ResourceRequest(QObject* parent, const QUrl& url); + ResourceRequest(const QUrl& url); enum State { NotStarted = 0, @@ -38,7 +38,6 @@ public: NotFound }; - void send(); QByteArray getData() { return _data; } State getState() const { return _state; } Result getResult() const { return _result; } @@ -47,8 +46,11 @@ public: void setCacheEnabled(bool value) { _cacheEnabled = value; } +public slots: + void send(); + signals: - void progress(uint64_t bytesReceived, uint64_t bytesTotal); + void progress(qint64 bytesReceived, qint64 bytesTotal); void finished(); protected: diff --git a/libraries/networking/src/udt/BasePacket.cpp b/libraries/networking/src/udt/BasePacket.cpp index 54342d9cba..000573d241 100644 --- a/libraries/networking/src/udt/BasePacket.cpp +++ b/libraries/networking/src/udt/BasePacket.cpp @@ -58,7 +58,7 @@ BasePacket::BasePacket(qint64 size) { Q_ASSERT(size >= 0 || size < maxPayload); _packetSize = size; - _packet.reset(new char[_packetSize]); + _packet.reset(new char[_packetSize]()); _payloadCapacity = _packetSize; _payloadSize = 0; _payloadStart = _packet.get(); diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 3b5b26d05d..e6a15aa6a0 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -87,7 +87,8 @@ SendQueue& Connection::getSendQueue() { // receiver is getting the sequence numbers it expects (given that the connection must still be active) // Lasily create send queue - _sendQueue = SendQueue::create(_parentSocket, _destination, _inactiveSendQueueSequenceNumber); + _sendQueue = SendQueue::create(_parentSocket, _destination); + _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Created SendQueue for connection to" << _destination; @@ -109,10 +110,6 @@ SendQueue& Connection::getSendQueue() { } void Connection::queueInactive() { - // get the current sequence number from the send queue, this is to be re-used if the send - // queue is re-activated for this connection - _inactiveSendQueueSequenceNumber = _sendQueue->getCurrentSequenceNumber(); - // tell our current send queue to go down and reset our ptr to it to null stopSendQueue(); @@ -728,15 +725,28 @@ void Connection::processNAK(std::unique_ptr controlPacket) { } void Connection::processHandshake(std::unique_ptr controlPacket) { + SequenceNumber initialSequenceNumber; + controlPacket->readPrimitive(&initialSequenceNumber); - if (!_hasReceivedHandshake || _isReceivingData) { + if (!_hasReceivedHandshake || initialSequenceNumber != _initialReceiveSequenceNumber) { // server sent us a handshake - we need to assume this means state should be reset // as long as we haven't received a handshake yet or we have and we've received some data + +#ifdef UDT_CONNECTION_DEBUG + if (initialSequenceNumber != _initialReceiveSequenceNumber) { + qCDebug(networking) << "Resetting receive state, received a new initial sequence number in handshake"; + } +#endif resetReceiveState(); + _initialReceiveSequenceNumber = initialSequenceNumber; + _lastReceivedSequenceNumber = initialSequenceNumber - 1; + _lastSentACK = initialSequenceNumber - 1; } // immediately respond with a handshake ACK - static auto handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, 0); + static auto handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, sizeof(SequenceNumber)); + handshakeACK->seek(0); + handshakeACK->writePrimitive(initialSequenceNumber); _parentSocket->writeBasePacket(*handshakeACK, _destination); // indicate that handshake has been received @@ -746,8 +756,11 @@ void Connection::processHandshake(std::unique_ptr controlPacket) void Connection::processHandshakeACK(std::unique_ptr controlPacket) { // if we've decided to clean up the send queue then this handshake ACK should be ignored, it's useless if (_sendQueue) { + SequenceNumber initialSequenceNumber; + controlPacket->readPrimitive(&initialSequenceNumber); + // hand off this handshake ACK to the send queue so it knows it can start sending - getSendQueue().handshakeACK(); + getSendQueue().handshakeACK(initialSequenceNumber); // indicate that handshake ACK was received _hasReceivedHandshakeACK = true; diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index b58b7ec570..bf56a468aa 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -130,7 +130,9 @@ private: bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection bool _isActive { true }; // flag used for inactivity of connection - + + SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer SendQueue on creation, identifies connection during re-connect requests + LossList _lossList; // List of all missing packets SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer SequenceNumber _lastReceivedACK; // The last ACK received @@ -140,8 +142,6 @@ private: SequenceNumber _lastSentACK; // The last sent ACK SequenceNumber _lastSentACK2; // The last sent ACK sub-sequence number in an ACK2 - SequenceNumber _inactiveSendQueueSequenceNumber { 0 }; - int _acksDuringSYN { 1 }; // The number of non-SYN ACKs sent during SYN int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 88fabf1a5a..27b74f270c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -16,6 +16,10 @@ #include #include + +Q_DECLARE_METATYPE(PacketType); +static int packetTypeMetaTypeId = qRegisterMetaType(); + const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::OctreeDataNack << PacketType::EntityEditNack @@ -38,6 +42,8 @@ const QSet RELIABLE_PACKETS = QSet(); PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { + case PacketType::DomainList: + return 18; case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 18269d1d43..5c6db5adf3 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -12,6 +12,7 @@ #include "SendQueue.h" #include +#include #include #include @@ -53,10 +54,10 @@ private: Mutex2& _mutex2; }; -std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber) { +std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); - auto queue = std::unique_ptr(new SendQueue(socket, destination, currentSequenceNumber)); + auto queue = std::unique_ptr(new SendQueue(socket, destination)); // Setup queue private thread QThread* thread = new QThread; @@ -75,12 +76,23 @@ std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destin return queue; } -SendQueue::SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber) : +SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : _socket(socket), - _destination(dest), - _currentSequenceNumber(currentSequenceNumber) + _destination(dest) { - + + // setup psuedo-random number generation for all instances of SendQueue + static std::random_device rd; + static std::mt19937 generator(rd()); + static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); + + // randomize the intial sequence number + _initialSequenceNumber = SequenceNumber(distribution(generator)); + + // set our member variables from randomized initial number + _currentSequenceNumber = _initialSequenceNumber - 1; + _atomicCurrentSequenceNumber = uint32_t(_currentSequenceNumber); + _lastACKSequenceNumber = uint32_t(_currentSequenceNumber) - 1; } void SendQueue::queuePacket(std::unique_ptr packet) { @@ -190,7 +202,11 @@ void SendQueue::sendHandshake() { std::unique_lock handshakeLock { _handshakeMutex }; if (!_hasReceivedHandshakeACK) { // we haven't received a handshake ACK from the client, send another now - static const auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0); + static const auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, sizeof(SequenceNumber)); + + handshakePacket->seek(0); + + handshakePacket->writePrimitive(_initialSequenceNumber); _socket->writeBasePacket(*handshakePacket, _destination); // we wait for the ACK or the re-send interval to expire @@ -199,14 +215,16 @@ void SendQueue::sendHandshake() { } } -void SendQueue::handshakeACK() { - { - std::lock_guard locker { _handshakeMutex }; - _hasReceivedHandshakeACK = true; +void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) { + if (initialSequenceNumber == _initialSequenceNumber) { + { + std::lock_guard locker { _handshakeMutex }; + _hasReceivedHandshakeACK = true; + } + + // Notify on the handshake ACK condition + _handshakeACKCondition.notify_one(); } - - // Notify on the handshake ACK condition - _handshakeACKCondition.notify_one(); } SequenceNumber SendQueue::getNextSequenceNumber() { diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index da8f5e6c3f..0390f2ff1f 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -50,8 +50,7 @@ public: Stopped }; - static std::unique_ptr create(Socket* socket, HifiSockAddr destination, - SequenceNumber currentSequenceNumber = SequenceNumber()); + static std::unique_ptr create(Socket* socket, HifiSockAddr destination); void queuePacket(std::unique_ptr packet); void queuePacketList(std::unique_ptr packetList); @@ -72,7 +71,7 @@ public slots: void ack(SequenceNumber ack); void nak(SequenceNumber start, SequenceNumber end); void overrideNAKListFromPacket(ControlPacket& packet); - void handshakeACK(); + void handshakeACK(SequenceNumber initialSequenceNumber); signals: void packetSent(int dataSize, int payloadSize); @@ -84,7 +83,7 @@ private slots: void run(); private: - SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber); + SendQueue(Socket* socket, HifiSockAddr dest); SendQueue(SendQueue& other) = delete; SendQueue(SendQueue&& other) = delete; @@ -106,6 +105,8 @@ private: Socket* _socket { nullptr }; // Socket to send packet on HifiSockAddr _destination; // Destination addr + + SequenceNumber _initialSequenceNumber; // Randomized on SendQueue creation, identifies connection during re-connect requests std::atomic _lastACKSequenceNumber { 0 }; // Last ACKed sequence number diff --git a/libraries/render-utils/src/DeferredLighting.slh b/libraries/render-utils/src/DeferredLighting.slh index b33371b10d..95c77ef2f0 100755 --- a/libraries/render-utils/src/DeferredLighting.slh +++ b/libraries/render-utils/src/DeferredLighting.slh @@ -19,13 +19,20 @@ vec3 fresnelSchlick(vec3 fresnelColor, vec3 lightDir, vec3 halfDir) { } float specularDistribution(float roughness, vec3 normal, vec3 halfDir) { - float gloss = (1.0 - roughness) * 128.0; - gloss *= gloss; + float ndoth = clamp(dot(halfDir, normal), 0.0, 1.0); + float gloss2 = pow(roughness, 4); + float denom = (ndoth * ndoth*(gloss2 - 1) + 1); + float power = gloss2 / (3.14159 * denom * denom); + return power; +} +/* +float specularDistribution(float roughness, vec3 normal, vec3 halfDir) { + float gloss = exp2(10 * (1.0 - roughness) + 1); float power = pow(clamp(dot(halfDir, normal), 0.0, 1.0), gloss); power *= (gloss * 0.125 + 0.25); return power; } - +*/ // Frag Shading returns the diffuse amount as W and the specular rgb as xyz vec4 evalPBRShading(vec3 fragNormal, vec3 fragLightDir, vec3 fragEyeDir, float metallic, vec3 fresnel, float roughness) { // Diffuse Lighting