Merge pull request #2136 from ey6es/master

Factored out common resource cache functionality, added limits to simultaneous downloading (prioritized by distance).  Closes #2122.
This commit is contained in:
Philip Rosedale 2014-02-27 20:29:00 -08:00
commit 7a73ecf98e
11 changed files with 465 additions and 293 deletions

View file

@ -58,6 +58,7 @@
#include <PacketHeaders.h>
#include <ParticlesScriptingInterface.h>
#include <PerfStat.h>
#include <ResourceCache.h>
#include <UUID.h>
#include <VoxelSceneStats.h>
@ -275,6 +276,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
_networkAccessManager->setCache(cache);
ResourceCache::setNetworkAccessManager(_networkAccessManager);
ResourceCache::setRequestLimit(3);
_window->setCentralWidget(_glWidget);
restoreSizeAndPosition();

View file

@ -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();

View file

@ -8,7 +8,6 @@
#include <cmath>
#include <QNetworkReply>
#include <QTimer>
#include "Application.h"
#include "GeometryCache.h"
@ -287,53 +286,25 @@ void GeometryCache::renderGrid(int xDivisions, int yDivisions) {
}
QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, const QUrl& fallback, bool delayLoad) {
if (!url.isValid() && fallback.isValid()) {
return getGeometry(fallback, QUrl(), delayLoad);
}
QSharedPointer<NetworkGeometry> geometry = _networkGeometry.value(url);
if (geometry.isNull()) {
geometry = QSharedPointer<NetworkGeometry>(new NetworkGeometry(url, fallback.isValid() ?
getGeometry(fallback, QUrl(), true) : QSharedPointer<NetworkGeometry>(), delayLoad));
geometry->setLODParent(geometry);
_networkGeometry.insert(url, geometry);
}
return geometry;
return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>();
}
QSharedPointer<Resource> GeometryCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
QSharedPointer<NetworkGeometry> geometry(new NetworkGeometry(url, fallback.staticCast<NetworkGeometry>(), delayLoad));
geometry->setLODParent(geometry);
return geometry.staticCast<Resource>();
}
const float NetworkGeometry::NO_HYSTERESIS = -1.0f;
NetworkGeometry::NetworkGeometry(const QUrl& url, const QSharedPointer<NetworkGeometry>& 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> NetworkGeometry::getLODOrFallback(float distance, float& hysteresis, bool delayLoad) const {
@ -406,24 +377,60 @@ 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);
void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
Resource::setLoadPriority(owner, priority);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->setLoadPriority(owner, priority);
}
if (part.normalTexture) {
part.normalTexture->setLoadPriority(owner, priority);
}
}
}
}
void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (!_reply->isFinished()) {
return;
}
void NetworkGeometry::setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities) {
Resource::setLoadPriorities(priorities);
QUrl url = _reply->url();
QByteArray data = _reply->readAll();
_reply->disconnect(this);
_reply->deleteLater();
_reply = NULL;
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->setLoadPriorities(priorities);
}
if (part.normalTexture) {
part.normalTexture->setLoadPriorities(priorities);
}
}
}
}
void NetworkGeometry::clearLoadPriority(const QPointer<QObject>& owner) {
Resource::clearLoadPriority(owner);
for (int i = 0; i < _meshes.size(); i++) {
NetworkMesh& mesh = _meshes[i];
for (int j = 0; j < mesh.parts.size(); j++) {
NetworkMeshPart& part = mesh.parts[j];
if (part.diffuseTexture) {
part.diffuseTexture->clearLoadPriority(owner);
}
if (part.normalTexture) {
part.normalTexture->clearLoadPriority(owner);
}
}
}
}
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 +460,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;
@ -477,10 +484,12 @@ void NetworkGeometry::handleDownloadProgress(qint64 bytesReceived, qint64 bytesT
if (!part.diffuseFilename.isEmpty()) {
networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye);
networkPart.diffuseTexture->setLoadPriorities(_loadPriorities);
}
if (!part.normalFilename.isEmpty()) {
networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture(
_textureBase.resolved(QUrl(part.normalFilename)), true);
networkPart.normalTexture->setLoadPriorities(_loadPriorities);
}
networkMesh.parts.append(networkPart);
@ -560,42 +569,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();
}

View file

@ -12,24 +12,19 @@
// include this before QOpenGLBuffer, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include <QHash>
#include <QMap>
#include <QNetworkRequest>
#include <QObject>
#include <QOpenGLBuffer>
#include <QSharedPointer>
#include <QWeakPointer>
#include <ResourceCache.h>
#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<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
private:
typedef QPair<int, int> 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<NetworkGeometry>& 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<NetworkGeometry> getLODOrFallback(float distance, float& hysteresis, bool delayLoad = false) const;
@ -86,11 +82,13 @@ public:
/// Returns the average color of all meshes in the geometry.
glm::vec4 computeAverageColor() const;
private slots:
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
virtual void clearLoadPriority(const QPointer<QObject>& owner);
void makeRequest();
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleReplyError();
protected:
virtual void downloadFinished(QNetworkReply* reply);
private:
@ -98,15 +96,10 @@ private:
void setLODParent(const QWeakPointer<NetworkGeometry>& lodParent) { _lodParent = lodParent; }
QNetworkRequest _request;
QNetworkReply* _reply;
QVariantHash _mapping;
QUrl _textureBase;
QSharedPointer<NetworkGeometry> _fallback;
bool _startedLoading;
bool _failedToLoad;
int _attempts;
QMap<float, QSharedPointer<NetworkGeometry> > _lods;
FBXGeometry _geometry;
QVector<NetworkMesh> _meshes;

View file

@ -139,6 +139,7 @@ void Model::simulate(float deltaTime, bool delayLoad) {
_geometry = geometry;
}
if (!delayLoad) {
_geometry->setLoadPriority(this, -_lodDistance);
_geometry->ensureLoading();
}
}
@ -833,6 +834,10 @@ void Model::deleteGeometry() {
_blendedVertexBufferIDs.clear();
_jointStates.clear();
_meshStates.clear();
if (_geometry) {
_geometry->clearLoadPriority(this);
}
}
void Model::renderMeshes(float alpha, bool translucent) {

View file

@ -11,7 +11,6 @@
#include <QGLWidget>
#include <QNetworkReply>
#include <QOpenGLFramebufferObject>
#include <QTimer>
#include <glm/gtc/random.hpp>
@ -124,19 +123,13 @@ GLuint TextureCache::getFileTextureID(const QString& filename) {
}
QSharedPointer<NetworkTexture> TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) {
QSharedPointer<NetworkTexture> texture;
if (dilatable) {
texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url, normalMap));
_dilatableNetworkTextures.insert(url, texture);
}
} else {
texture = _networkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new NetworkTexture(url, normalMap));
_networkTextures.insert(url, texture);
}
if (!dilatable) {
return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast<NetworkTexture>();
}
QSharedPointer<NetworkTexture> texture = _dilatableNetworkTextures.value(url);
if (texture.isNull()) {
texture = QSharedPointer<NetworkTexture>(new DilatableNetworkTexture(url));
_dilatableNetworkTextures.insert(url, texture);
}
return texture;
}
@ -233,6 +226,11 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) {
return false;
}
QSharedPointer<Resource> TextureCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(new NetworkTexture(url, *(const bool*)extra));
}
QOpenGLFramebufferObject* TextureCache::createFramebufferObject() {
QOpenGLFramebufferObject* fbo = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size());
Application::getInstance()->getGLWidget()->installEventFilter(this);
@ -254,9 +252,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 +261,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 +268,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,27 +306,12 @@ 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) :
NetworkTexture(url, normalMap),
DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) :
NetworkTexture(url, false),
_innerRadius(0),
_outerRadius(0)
{

View file

@ -9,23 +9,19 @@
#ifndef __interface__TextureCache__
#define __interface__TextureCache__
#include <QHash>
#include <QImage>
#include <QMap>
#include <QNetworkRequest>
#include <QObject>
#include <QSharedPointer>
#include <QWeakPointer>
#include <ResourceCache.h>
#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<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
private:
QOpenGLFramebufferObject* createFramebufferObject();
@ -83,7 +84,6 @@ private:
QHash<QString, GLuint> _fileTextureIDs;
QHash<QUrl, QWeakPointer<NetworkTexture> > _networkTextures;
QHash<QUrl, QWeakPointer<NetworkTexture> > _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;
@ -153,7 +144,7 @@ class DilatableNetworkTexture : public NetworkTexture {
public:
DilatableNetworkTexture(const QUrl& url, bool normalMap);
DilatableNetworkTexture(const QUrl& url);
/// Returns a pointer to a texture with the requested amount of dilation.
QSharedPointer<Texture> getDilatedTexture(float dilation);

View file

@ -11,8 +11,6 @@
#include <QNetworkReply>
#include <QScriptEngine>
#include <QTextStream>
#include <QTimer>
#include <QtDebug>
#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<NetworkProgram> ScriptCache::getProgram(const QUrl& url) {
QSharedPointer<NetworkProgram> program = _networkPrograms.value(url);
if (program.isNull()) {
program = QSharedPointer<NetworkProgram>(new NetworkProgram(this, url));
_networkPrograms.insert(url, program);
}
return program;
}
QSharedPointer<NetworkValue> ScriptCache::getValue(const ParameterizedURL& url) {
QSharedPointer<NetworkValue> value = _networkValues.value(url);
if (value.isNull()) {
@ -61,65 +49,21 @@ QSharedPointer<NetworkValue> ScriptCache::getValue(const ParameterizedURL& url)
return value;
}
QSharedPointer<Resource> ScriptCache::createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) {
return QSharedPointer<Resource>(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() {
}

View file

@ -9,26 +9,20 @@
#ifndef __interface__ScriptCache__
#define __interface__ScriptCache__
#include <QHash>
#include <QList>
#include <QNetworkRequest>
#include <QObject>
#include <QScriptProgram>
#include <QScriptValue>
#include <QSharedPointer>
#include <QWeakPointer>
#include <ResourceCache.h>
#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<NetworkProgram> getProgram(const QUrl& url);
QSharedPointer<NetworkProgram> getProgram(const QUrl& url) { return getResource(url).staticCast<NetworkProgram>(); }
/// Loads a script value from the specified URL.
QSharedPointer<NetworkValue> getValue(const ParameterizedURL& url);
@ -55,11 +46,14 @@ public:
const QScriptString& getTypeString() const { return _typeString; }
const QScriptString& getGeneratorString() const { return _generatorString; }
protected:
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra);
private:
QNetworkAccessManager* _networkAccessManager;
QScriptEngine* _engine;
QHash<QUrl, QWeakPointer<NetworkProgram> > _networkPrograms;
QHash<ParameterizedURL, QWeakPointer<NetworkValue> > _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;
};

View file

@ -0,0 +1,199 @@
//
// ResourceCache.cpp
// shared
//
// Created by Andrzej Kapolka on 2/27/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <cfloat>
#include <cmath>
#include <QNetworkReply>
#include <QTimer>
#include <QtDebug>
#include "ResourceCache.h"
ResourceCache::ResourceCache(QObject* parent) :
QObject(parent) {
}
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) {
if (!url.isValid() && fallback.isValid()) {
return getResource(fallback, QUrl(), delayLoad);
}
QSharedPointer<Resource> resource = _resources.value(url);
if (resource.isNull()) {
resource = createResource(url, fallback.isValid() ?
getResource(fallback, QUrl(), true) : QSharedPointer<Resource>(), 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<QPointer<Resource> > 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();
}
}
void Resource::setLoadPriority(const QPointer<QObject>& owner, float priority) {
_loadPriorities.insert(owner, priority);
}
void Resource::setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities) {
for (QHash<QPointer<QObject>, float>::const_iterator it = priorities.constBegin();
it != priorities.constEnd(); it++) {
_loadPriorities.insert(it.key(), it.value());
}
}
void Resource::clearLoadPriority(const QPointer<QObject>& owner) {
_loadPriorities.remove(owner);
}
float Resource::getLoadPriority() {
float highestPriority = -FLT_MAX;
for (QHash<QPointer<QObject>, 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<QObject>& value, uint seed) {
return qHash(value.data(), seed);
}

View file

@ -0,0 +1,121 @@
//
// 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 <QHash>
#include <QList>
#include <QNetworkRequest>
#include <QObject>
#include <QPointer>
#include <QSharedPointer>
#include <QUrl>
#include <QWeakPointer>
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<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(),
bool delayLoad = false, void* extra = NULL);
/// Creates a new resource.
virtual QSharedPointer<Resource> createResource(const QUrl& url,
const QSharedPointer<Resource>& fallback, bool delayLoad, const void* extra) = 0;
static void attemptRequest(Resource* resource);
static void requestCompleted();
private:
friend class Resource;
QHash<QUrl, QWeakPointer<Resource> > _resources;
static QNetworkAccessManager* _networkAccessManager;
static int _requestLimit;
static QList<QPointer<Resource> > _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.
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
/// Sets a set of priorities at once.
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
/// Clears the load priority for one owner.
virtual void clearLoadPriority(const QPointer<QObject>& 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;
QHash<QPointer<QObject>, float> _loadPriorities;
private slots:
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void handleReplyError();
private:
void makeRequest();
friend class ResourceCache;
QNetworkReply* _reply;
int _attempts;
};
uint qHash(const QPointer<QObject>& value, uint seed = 0);
#endif /* defined(__shared__ResourceCache__) */