From 29f7954d200923501eada323d99c27c060767a08 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Thu, 27 Feb 2014 18:13:40 -0800 Subject: [PATCH] Factored out bits common to resource caches, added global limit on number of resources being requested at any one time. --- interface/src/Application.cpp | 3 + interface/src/MetavoxelSystem.cpp | 3 - interface/src/renderer/GeometryCache.cpp | 110 ++------------ interface/src/renderer/GeometryCache.h | 35 ++--- interface/src/renderer/TextureCache.cpp | 88 +++-------- interface/src/renderer/TextureCache.h | 31 ++-- libraries/metavoxels/src/ScriptCache.cpp | 74 ++------- libraries/metavoxels/src/ScriptCache.h | 40 ++--- libraries/shared/src/ResourceCache.cpp | 184 +++++++++++++++++++++++ libraries/shared/src/ResourceCache.h | 119 +++++++++++++++ 10 files changed, 390 insertions(+), 297 deletions(-) create mode 100644 libraries/shared/src/ResourceCache.cpp create mode 100644 libraries/shared/src/ResourceCache.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e8a7d0dcbc..ba623a4d04 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,6 +58,7 @@ #include #include #include +#include #include #include @@ -275,6 +276,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache"); _networkAccessManager->setCache(cache); + ResourceCache::setNetworkAccessManager(_networkAccessManager); + _window->setCentralWidget(_glWidget); restoreSizeAndPosition(); diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 59a714ece5..eeb92be5ec 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -35,9 +35,6 @@ void MetavoxelSystem::init() { _program.link(); _pointScaleLocation = _program.uniformLocation("pointScale"); - - // let the script cache know to use our common access manager - ScriptCache::getInstance()->setNetworkAccessManager(Application::getInstance()->getNetworkAccessManager()); } _buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); _buffer.create(); diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index 13563be200..e391e7414b 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -8,7 +8,6 @@ #include #include -#include #include "Application.h" #include "GeometryCache.h" @@ -287,53 +286,25 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) { } QSharedPointer GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) { - if (!url.isValid() && fallback.isValid()) { - return getGeometry(fallback, QUrl(), delayLoad); - } - QSharedPointer geometry = _networkGeometry.value(url); - if (geometry.isNull()) { - geometry = QSharedPointer(new NetworkGeometry(url, fallback.isValid() ? - getGeometry(fallback, QUrl(), true) : QSharedPointer(), delayLoad)); - geometry->setLODParent(geometry); - _networkGeometry.insert(url, geometry); - } - return geometry; + return getResource(url, fallback, delayLoad).staticCast(); +} + +QSharedPointer GeometryCache::createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, void* extra) { + + QSharedPointer geometry(new NetworkGeometry(url, fallback.staticCast(), delayLoad)); + geometry->setLODParent(geometry); + return geometry.staticCast(); } const float NetworkGeometry::NO_HYSTERESIS = -1.0f; NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBase) : - _request(url), - _reply(NULL), + Resource(url, delayLoad), _mapping(mapping), _textureBase(textureBase.isValid() ? textureBase : url), - _fallback(fallback), - _startedLoading(false), - _failedToLoad(false), - _attempts(0) { - - if (!url.isValid()) { - return; - } - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - - // start loading immediately unless instructed otherwise - if (!delayLoad) { - makeRequest(); - } -} - -NetworkGeometry::~NetworkGeometry() { - if (_reply != NULL) { - delete _reply; - } -} - -void NetworkGeometry::ensureLoading() { - if (!_startedLoading) { - makeRequest(); - } + _fallback(fallback) { } QSharedPointer NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const { @@ -406,24 +377,9 @@ glm::vec4 NetworkGeometry::computeAverageColor() const { return (totalTriangles == 0) ? glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) : totalColor / totalTriangles; } -void NetworkGeometry::makeRequest() { - _startedLoading = true; - _reply = Application::getInstance()->getNetworkAccessManager()->get(_request); - - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); -} - -void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (!_reply->isFinished()) { - return; - } - - QUrl url = _reply->url(); - QByteArray data = _reply->readAll(); - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; +void NetworkGeometry::downloadFinished(QNetworkReply* reply) { + QUrl url = reply->url(); + QByteArray data = reply->readAll(); if (url.path().toLower().endsWith(".fst")) { // it's a mapping file; parse it and get the mesh filename @@ -453,7 +409,7 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT // make the request immediately only if we have no LODs to switch between _startedLoading = false; if (_lods.isEmpty()) { - makeRequest(); + attemptRequest(); } } return; @@ -560,42 +516,6 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT } } -void NetworkGeometry::handleReplyError() { - QDebug debug = qDebug() << _reply->errorString(); - - QNetworkReply::NetworkError error = _reply->error(); - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - - // retry for certain types of failures - switch (error) { - case QNetworkReply::RemoteHostClosedError: - case QNetworkReply::TimeoutError: - case QNetworkReply::TemporaryNetworkFailureError: - case QNetworkReply::ProxyConnectionClosedError: - case QNetworkReply::ProxyTimeoutError: - case QNetworkReply::UnknownNetworkError: - case QNetworkReply::UnknownProxyError: - case QNetworkReply::UnknownContentError: - case QNetworkReply::ProtocolFailure: { - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); - debug << " -- retrying..."; - return; - } - // fall through to final failure - } - default: - _failedToLoad = true; - break; - } - -} - bool NetworkMeshPart::isTranslucent() const { return diffuseTexture && diffuseTexture->isTranslucent(); } diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index a1d159be31..bdd050d892 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -12,24 +12,19 @@ // include this before QOpenGLBuffer, which includes an earlier version of OpenGL #include "InterfaceConfig.h" -#include #include -#include -#include #include -#include -#include + +#include #include "FBXReader.h" -class QNetworkReply; - class NetworkGeometry; class NetworkMesh; class NetworkTexture; /// Stores cached geometry. -class GeometryCache { +class GeometryCache : public ResourceCache { public: ~GeometryCache(); @@ -44,6 +39,11 @@ public: /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); +protected: + + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, void* extra); + private: typedef QPair IntPair; @@ -58,7 +58,7 @@ private: }; /// Geometry loaded from the network. -class NetworkGeometry : public QObject { +class NetworkGeometry : public Resource { Q_OBJECT public: @@ -68,14 +68,10 @@ public: NetworkGeometry(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBase = QUrl()); - ~NetworkGeometry(); /// Checks whether the geometry is fulled loaded. bool isLoaded() const { return !_geometry.joints.isEmpty(); } - /// Makes sure that the geometry has started loading. - void ensureLoading(); - /// Returns a pointer to the geometry appropriate for the specified distance. /// \param hysteresis a hysteresis parameter that prevents rapid model switching QSharedPointer getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const; @@ -86,11 +82,9 @@ public: /// Returns the average color of all meshes in the geometry. glm::vec4 computeAverageColor() const; -private slots: - - void makeRequest(); - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); +protected: + + virtual void downloadFinished(QNetworkReply* reply); private: @@ -98,15 +92,10 @@ private: void setLODParent(const QWeakPointer& lodParent) { _lodParent = lodParent; } - QNetworkRequest _request; - QNetworkReply* _reply; QVariantHash _mapping; QUrl _textureBase; QSharedPointer _fallback; - bool _startedLoading; - bool _failedToLoad; - int _attempts; QMap > _lods; FBXGeometry _geometry; QVector _meshes; diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 8bfef5a742..69eef755c1 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include @@ -123,22 +122,15 @@ GLuint TextureCache::getFileTextureID(const QString& filename) { return id; } +class TextureExtra { +public: + bool normalMap; + bool dilatable; +}; + QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) { - QSharedPointer texture; - if (dilatable) { - texture = _dilatableNetworkTextures.value(url); - if (texture.isNull()) { - texture = QSharedPointer(new DilatableNetworkTexture(url, normalMap)); - _dilatableNetworkTextures.insert(url, texture); - } - } else { - texture = _networkTextures.value(url); - if (texture.isNull()) { - texture = QSharedPointer(new NetworkTexture(url, normalMap)); - _networkTextures.insert(url, texture); - } - } - return texture; + TextureExtra extra = { normalMap, dilatable }; + return getResource(url, QUrl(), false, &extra).staticCast(); } QOpenGLFramebufferObject* TextureCache::getPrimaryFramebufferObject() { @@ -233,6 +225,13 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) { return false; } +QSharedPointer TextureCache::createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, void* extra) { + TextureExtra* textureExtra = static_cast(extra); + return QSharedPointer(textureExtra->dilatable ? new DilatableNetworkTexture(url, textureExtra->normalMap) : + new NetworkTexture(url, textureExtra->normalMap)); +} + QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size()); Application::getInstance()->getGLWidget()->installEventFilter(this); @@ -254,9 +253,7 @@ Texture::~Texture() { } NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : - _request(url), - _reply(NULL), - _attempts(0), + Resource(url), _averageColor(1.0f, 1.0f, 1.0f, 1.0f), _translucent(false), _loaded(false) { @@ -265,8 +262,6 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : _loaded = true; return; } - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - makeRequest(); // default to white/blue glBindTexture(GL_TEXTURE_2D, getID()); @@ -274,35 +269,13 @@ NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : glBindTexture(GL_TEXTURE_2D, 0); } -NetworkTexture::~NetworkTexture() { - if (_reply != NULL) { - delete _reply; - } -} - -void NetworkTexture::imageLoaded(const QImage& image) { - // nothing by default -} - -void NetworkTexture::makeRequest() { - _reply = Application::getInstance()->getNetworkAccessManager()->get(_request); - - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); -} - -void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (bytesReceived < bytesTotal && !_reply->isFinished()) { - return; - } - - QByteArray entirety = _reply->readAll(); - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; +void NetworkTexture::downloadFinished(QNetworkReply* reply) { _loaded = true; - QImage image = QImage::fromData(entirety).convertToFormat(QImage::Format_ARGB32); + QImage image = QImage::fromData(reply->readAll()); + if (image.format() != QImage::Format_ARGB32) { + image = image.convertToFormat(QImage::Format_ARGB32); + } // sum up the colors for the average and check for translucency glm::vec4 accumulated; @@ -334,23 +307,8 @@ void NetworkTexture::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTo glBindTexture(GL_TEXTURE_2D, 0); } -void NetworkTexture::handleReplyError() { - QDebug debug = qDebug() << _reply->errorString(); - - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); - debug << " -- retrying..."; - - } else { - _loaded = true; - } +void NetworkTexture::imageLoaded(const QImage& image) { + // nothing by default } DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, bool normalMap) : diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index ca7bf67a32..b735ef58cd 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -9,23 +9,19 @@ #ifndef __interface__TextureCache__ #define __interface__TextureCache__ -#include #include #include -#include -#include -#include -#include + +#include #include "InterfaceConfig.h" -class QNetworkReply; class QOpenGLFramebufferObject; class NetworkTexture; /// Stores cached textures, including render-to-texture targets. -class TextureCache : public QObject { +class TextureCache : public ResourceCache { Q_OBJECT public: @@ -73,6 +69,11 @@ public: virtual bool eventFilter(QObject* watched, QEvent* event); +protected: + + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, void* extra); + private: QOpenGLFramebufferObject* createFramebufferObject(); @@ -83,7 +84,6 @@ private: QHash _fileTextureIDs; - QHash > _networkTextures; QHash > _dilatableNetworkTextures; GLuint _primaryDepthTextureID; @@ -110,13 +110,12 @@ private: }; /// A texture loaded from the network. -class NetworkTexture : public QObject, public Texture { +class NetworkTexture : public Resource, public Texture { Q_OBJECT public: NetworkTexture(const QUrl& url, bool normalMap); - ~NetworkTexture(); bool isLoaded() const { return _loaded; } @@ -129,19 +128,11 @@ public: protected: - virtual void imageLoaded(const QImage& image); - -private slots: - - void makeRequest(); - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); + virtual void downloadFinished(QNetworkReply* reply); + virtual void imageLoaded(const QImage& image); private: - QNetworkRequest _request; - QNetworkReply* _reply; - int _attempts; glm::vec4 _averageColor; bool _translucent; bool _loaded; diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index e7610038f5..ffa429a36f 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -11,8 +11,6 @@ #include #include #include -#include -#include #include "AttributeRegistry.h" #include "ScriptCache.h" @@ -23,7 +21,6 @@ ScriptCache* ScriptCache::getInstance() { } ScriptCache::ScriptCache() : - _networkAccessManager(NULL), _engine(NULL) { setEngine(new QScriptEngine(this)); @@ -41,15 +38,6 @@ void ScriptCache::setEngine(QScriptEngine* engine) { _generatorString = engine->toStringHandle("generator"); } -QSharedPointer ScriptCache::getProgram(const QUrl& url) { - QSharedPointer program = _networkPrograms.value(url); - if (program.isNull()) { - program = QSharedPointer(new NetworkProgram(this, url)); - _networkPrograms.insert(url, program); - } - return program; -} - QSharedPointer ScriptCache::getValue(const ParameterizedURL& url) { QSharedPointer value = _networkValues.value(url); if (value.isNull()) { @@ -61,65 +49,21 @@ QSharedPointer ScriptCache::getValue(const ParameterizedURL& url) return value; } +QSharedPointer ScriptCache::createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, void* extra) { + return QSharedPointer(new NetworkProgram(this, url)); +} + NetworkProgram::NetworkProgram(ScriptCache* cache, const QUrl& url) : - _cache(cache), - _request(url), - _reply(NULL), - _attempts(0) { - - if (!url.isValid()) { - return; - } - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - makeRequest(); + Resource(url), + _cache(cache) { } -NetworkProgram::~NetworkProgram() { - if (_reply != NULL) { - delete _reply; - } -} - -void NetworkProgram::makeRequest() { - QNetworkAccessManager* manager = _cache->getNetworkAccessManager(); - if (manager == NULL) { - return; - } - _reply = manager->get(_request); - - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); -} - -void NetworkProgram::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (bytesReceived < bytesTotal && !_reply->isFinished()) { - return; - } - _program = QScriptProgram(QTextStream(_reply).readAll(), _reply->url().toString()); - - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - +void NetworkProgram::downloadFinished(QNetworkReply* reply) { + _program = QScriptProgram(QTextStream(reply).readAll(), reply->url().toString()); emit loaded(); } -void NetworkProgram::handleReplyError() { - QDebug debug = qDebug() << _reply->errorString(); - - _reply->disconnect(this); - _reply->deleteLater(); - _reply = NULL; - - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); - debug << " -- retrying..."; - } -} - NetworkValue::~NetworkValue() { } diff --git a/libraries/metavoxels/src/ScriptCache.h b/libraries/metavoxels/src/ScriptCache.h index 3ce525d979..20905ecc5f 100644 --- a/libraries/metavoxels/src/ScriptCache.h +++ b/libraries/metavoxels/src/ScriptCache.h @@ -9,26 +9,20 @@ #ifndef __interface__ScriptCache__ #define __interface__ScriptCache__ -#include -#include -#include -#include #include #include -#include -#include + +#include #include "MetavoxelUtil.h" -class QNetworkAccessManager; -class QNetworkReply; class QScriptEngine; class NetworkProgram; class NetworkValue; /// Maintains a cache of loaded scripts. -class ScriptCache : public QObject { +class ScriptCache : public ResourceCache { Q_OBJECT public: @@ -37,14 +31,11 @@ public: ScriptCache(); - void setNetworkAccessManager(QNetworkAccessManager* manager) { _networkAccessManager = manager; } - QNetworkAccessManager* getNetworkAccessManager() const { return _networkAccessManager; } - void setEngine(QScriptEngine* engine); QScriptEngine* getEngine() const { return _engine; } /// Loads a script program from the specified URL. - QSharedPointer getProgram(const QUrl& url); + QSharedPointer getProgram(const QUrl& url) { return getResource(url).staticCast(); } /// Loads a script value from the specified URL. QSharedPointer getValue(const ParameterizedURL& url); @@ -55,11 +46,14 @@ public: const QScriptString& getTypeString() const { return _typeString; } const QScriptString& getGeneratorString() const { return _generatorString; } +protected: + + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, void* extra); + private: - QNetworkAccessManager* _networkAccessManager; QScriptEngine* _engine; - QHash > _networkPrograms; QHash > _networkValues; QScriptString _parametersString; QScriptString _lengthString; @@ -69,13 +63,12 @@ private: }; /// A program loaded from the network. -class NetworkProgram : public QObject { +class NetworkProgram : public Resource { Q_OBJECT public: NetworkProgram(ScriptCache* cache, const QUrl& url); - ~NetworkProgram(); ScriptCache* getCache() const { return _cache; } @@ -87,18 +80,13 @@ signals: void loaded(); -private slots: - - void makeRequest(); - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); - +protected: + + virtual void downloadFinished(QNetworkReply* reply); + private: ScriptCache* _cache; - QNetworkRequest _request; - QNetworkReply* _reply; - int _attempts; QScriptProgram _program; }; diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp new file mode 100644 index 0000000000..100e1b9469 --- /dev/null +++ b/libraries/shared/src/ResourceCache.cpp @@ -0,0 +1,184 @@ +// +// ResourceCache.cpp +// shared +// +// Created by Andrzej Kapolka on 2/27/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include +#include + +#include +#include +#include + +#include "ResourceCache.h" + +ResourceCache::ResourceCache(QObject* parent) : + QObject(parent) { +} + +QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) { + if (!url.isValid() && fallback.isValid()) { + return getResource(fallback, QUrl(), delayLoad); + } + QSharedPointer resource = _resources.value(url); + if (resource.isNull()) { + resource = createResource(url, fallback.isValid() ? + getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); + _resources.insert(url, resource); + } + return resource; +} + +void ResourceCache::attemptRequest(Resource* resource) { + if (_requestLimit <= 0) { + // wait until a slot becomes available + _pendingRequests.append(resource); + return; + } + _requestLimit--; + resource->makeRequest(); +} + +void ResourceCache::requestCompleted() { + _requestLimit++; + + // look for the highest priority pending request + int highestIndex = -1; + float highestPriority = -FLT_MAX; + for (int i = 0; i < _pendingRequests.size(); ) { + Resource* resource = _pendingRequests.at(i).data(); + if (!resource) { + _pendingRequests.removeAt(i); + continue; + } + float priority = resource->getLoadPriority(); + if (priority >= highestPriority) { + highestPriority = priority; + highestIndex = i; + } + i++; + } + if (highestIndex >= 0) { + attemptRequest(_pendingRequests.takeAt(highestIndex)); + } +} + +QNetworkAccessManager* ResourceCache::_networkAccessManager = NULL; + +const int DEFAULT_REQUEST_LIMIT = 10; +int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT; + +QList > ResourceCache::_pendingRequests; + +Resource::Resource(const QUrl& url, bool delayLoad) : + _request(url), + _startedLoading(false), + _failedToLoad(false), + _attempts(0), + _reply(NULL) { + + if (!url.isValid()) { + _startedLoading = _failedToLoad = true; + return; + } + _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + + // start loading immediately unless instructed otherwise + if (!delayLoad) { + attemptRequest(); + } +} + +Resource::~Resource() { + if (_reply) { + ResourceCache::requestCompleted(); + delete _reply; + } +} + +void Resource::ensureLoading() { + if (!_startedLoading) { + attemptRequest(); + } +} + +float Resource::getLoadPriority() { + float highestPriority = -FLT_MAX; + for (QHash, float>::iterator it = _loadPriorities.begin(); it != _loadPriorities.end(); ) { + if (it.key().isNull()) { + it = _loadPriorities.erase(it); + continue; + } + highestPriority = qMax(highestPriority, it.value()); + it++; + } + return highestPriority; +} + +void Resource::attemptRequest() { + _startedLoading = true; + ResourceCache::attemptRequest(this); +} + +void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (!_reply->isFinished()) { + return; + } + _reply->disconnect(this); + _reply->deleteLater(); + QNetworkReply* reply = _reply; + _reply = NULL; + ResourceCache::requestCompleted(); + + downloadFinished(reply); +} + +void Resource::handleReplyError() { + QDebug debug = qDebug() << _reply->errorString(); + + QNetworkReply::NetworkError error = _reply->error(); + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; + ResourceCache::requestCompleted(); + + // retry for certain types of failures + switch (error) { + case QNetworkReply::RemoteHostClosedError: + case QNetworkReply::TimeoutError: + case QNetworkReply::TemporaryNetworkFailureError: + case QNetworkReply::ProxyConnectionClosedError: + case QNetworkReply::ProxyTimeoutError: + case QNetworkReply::UnknownNetworkError: + case QNetworkReply::UnknownProxyError: + case QNetworkReply::UnknownContentError: + case QNetworkReply::ProtocolFailure: { + // retry with increasing delays + const int MAX_ATTEMPTS = 8; + const int BASE_DELAY_MS = 1000; + if (++_attempts < MAX_ATTEMPTS) { + QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest())); + debug << " -- retrying..."; + return; + } + // fall through to final failure + } + default: + _failedToLoad = true; + break; + } +} + +void Resource::makeRequest() { + _reply = ResourceCache::getNetworkAccessManager()->get(_request); + + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); + connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); +} + +uint qHash(const QPointer& value, uint seed) { + return qHash(value.data(), seed); +} diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h new file mode 100644 index 0000000000..571d76b844 --- /dev/null +++ b/libraries/shared/src/ResourceCache.h @@ -0,0 +1,119 @@ +// +// ResourceCache.h +// shared +// +// Created by Andrzej Kapolka on 2/27/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __shared__ResourceCache__ +#define __shared__ResourceCache__ + +#include +#include +#include +#include +#include +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +class Resource; + +/// Base class for resource caches. +class ResourceCache : public QObject { + Q_OBJECT + +public: + + static void setNetworkAccessManager(QNetworkAccessManager* manager) { _networkAccessManager = manager; } + static QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; } + + static void setRequestLimit(int limit) { _requestLimit = limit; } + static int getRequestLimit() { return _requestLimit; } + + ResourceCache(QObject* parent = NULL); + +protected: + + /// Loads a resource from the specified URL. + /// \param fallback a fallback URL to load if the desired one is unavailable + /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested + /// \param extra extra data to pass to the creator, if appropriate + QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), + bool delayLoad = false, void* extra = NULL); + + /// Creates a new resource. + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, void* extra) = 0; + + static void attemptRequest(Resource* resource); + static void requestCompleted(); + +private: + + friend class Resource; + + QHash > _resources; + + static QNetworkAccessManager* _networkAccessManager; + static int _requestLimit; + static QList > _pendingRequests; +}; + +/// Base class for resources. +class Resource : public QObject { + Q_OBJECT + +public: + + Resource(const QUrl& url, bool delayLoad = false); + ~Resource(); + + /// Makes sure that the resource has started loading. + void ensureLoading(); + + /// Sets the load priority for one owner. + void setLoadPriority(const QPointer& owner, float priority) { _loadPriorities.insert(owner, priority); } + + /// Clears the load priority for one owner. + void clearLoadPriority(const QPointer& owner) { _loadPriorities.remove(owner); } + + /// Returns the highest load priority across all owners. + float getLoadPriority(); + +protected slots: + + void attemptRequest(); + +protected: + + virtual void downloadFinished(QNetworkReply* reply) = 0; + + QNetworkRequest _request; + bool _startedLoading; + bool _failedToLoad; + +private slots: + + void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void handleReplyError(); + +private: + + void makeRequest(); + + friend class ResourceCache; + + QNetworkReply* _reply; + int _attempts; + + QHash, float> _loadPriorities; +}; + +uint qHash(const QPointer& value, uint seed = 0); + +#endif /* defined(__shared__ResourceCache__) */