diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 84dd307792..7feeff114e 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -119,6 +119,57 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { return highestResource; } +ScriptableResource::ScriptableResource(const QSharedPointer& resource) : + QObject(nullptr), + _resource(resource) {} + +void ScriptableResource::finished(bool success) { + if (_progressConnection) { + disconnect(_progressConnection); + } + if (_finishedConnection) { + disconnect(_finishedConnection); + } + + _isLoaded = true; + _isFailed = !success; + + if (_isFailed) { + emit failedChanged(_isFailed); + } + emit loadedChanged(_isLoaded); +} + +ScriptableResource* ResourceCache::prefetch(const QUrl& url) { + auto result = new ScriptableResource(); + + if (QThread::currentThread() != thread()) { + // Must be called in thread to ensure getResource returns a valid pointer + QMetaObject::invokeMethod(this, "prefetch", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(ScriptableResource*, result), Q_ARG(QUrl, url)); + return result; + } + + + auto resource = getResource(url); + result->_resource = resource; + result->setObjectName(url.toString()); + + result->_resource = resource; + if (resource->isLoaded()) { + result->finished(!resource->_failedToLoad); + } else { + result->_progressConnection = connect( + resource.data(), &Resource::handleDownloadProgress, + result, &ScriptableResource::progressChanged); + result->_finishedConnection = connect( + resource.data(), &Resource::finished, + result, &ScriptableResource::finished); + } + + return result; +} + ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { auto& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, @@ -169,11 +220,6 @@ void ResourceCache::clearATPAssets() { } void ResourceCache::refreshAll() { - // Remove refs to prefetching resources - _prefetchingResourcesLock.lock(); - _prefetchingResources.clear(); - _prefetchingResourcesLock.unlock(); - // Clear all unused resources so we don't have to reload them clearUnusedResource(); resetResourceCounters(); @@ -221,24 +267,7 @@ QVariantList ResourceCache::getResourceList() { return list; } - -void ResourceCache::prefetch(const QUrl& url) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "prefetch", Q_ARG(QUrl, url)); - } else { - auto resource = getResource(url); - // If it is not loaded, hold a ref until it is - if (!resource->isLoaded()) { - QMutexLocker lock(&_prefetchingResourcesLock); - _prefetchingResources.insert(url, resource); - connect(resource.data(), &Resource::finishedLoading, [this, url]{ - QMutexLocker lock(&_prefetchingResourcesLock); - this->_prefetchingResources.remove(url); - }); - } - } -} - + void ResourceCache::setRequestLimit(int limit) { _requestLimit = limit; diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index e284e24860..3cd749ebc9 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -78,6 +78,40 @@ private: QList> _loadingRequests; }; +/// Wrapper to expose resources to JS/QML +class ScriptableResource : public QObject { + Q_OBJECT + Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged) + Q_PROPERTY(bool failed READ isFailed NOTIFY failedChanged) +public: + ScriptableResource(const QSharedPointer& resource = QSharedPointer()); + virtual ~ScriptableResource() = default; + + bool isLoaded() const { return _isLoaded; } + bool isFailed() const { return _isFailed; } + +signals: + void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); + void loadedChanged(bool loaded); // analogous to &Resource::finished + void failedChanged(bool failed); + +private slots: + void finished(bool success); + +private: + friend class ResourceCache; + + // Holds a ref to the resource to keep it in scope + QSharedPointer _resource; + + QMetaObject::Connection _progressConnection; + QMetaObject::Connection _finishedConnection; + + bool _isLoaded{ false }; + bool _isFailed{ false }; +}; + +Q_DECLARE_METATYPE(ScriptableResource*); /// Base class for resource caches. class ResourceCache : public QObject { @@ -96,7 +130,8 @@ public: Q_INVOKABLE QVariantList getResourceList(); - Q_INVOKABLE void prefetch(const QUrl& url); + // This must be exposed as a ptr so the ScriptEngine may take ownership + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url); static void setRequestLimit(int limit); static int getRequestLimit() { return _requestLimit; } @@ -177,9 +212,6 @@ private: qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE; QReadWriteLock _unusedResourcesLock { QReadWriteLock::Recursive }; QMap> _unusedResources; - - QMutex _prefetchingResourcesLock{ QMutex::Recursive }; - QMap> _prefetchingResources; }; /// Base class for resources. @@ -292,6 +324,7 @@ private: void reinsert(); friend class ResourceCache; + friend class ScriptableResource; ResourceRequest* _request = nullptr; int _lruKey = 0; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 15f3ebb985..4c6a988f36 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -270,6 +270,20 @@ static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantR assert(false); } +// Templated qScriptRegisterMetaType fails to compile with raw pointers +using ScriptableResourceRawPtr = ScriptableResource*; + +static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, const ScriptableResourceRawPtr& resource) { + auto object = engine->newQObject( + const_cast(resource), + QScriptEngine::ScriptOwnership); + return object; +} + +static void scriptableResourceFromScriptValue(const QScriptValue& value, ScriptableResourceRawPtr& resource) { + resource = static_cast(value.toQObject()); +} + void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once @@ -332,6 +346,8 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue); qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue); + qScriptRegisterMetaType(this, scriptableResourceToScriptValue, scriptableResourceFromScriptValue); + // constants globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));