887 lines · 27.7 KB · C++
RawDownload
////  ResourceCache.cpp//  libraries/shared/src////  Created by Andrzej Kapolka on 2/27/14.//  Copyright 2014 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "ResourceCache.h"#include "ResourceRequestObserver.h" #include <cfloat>#include <cmath>#include <assert.h> #include <QtCore/QMetaMethod>#include <QtCore/QThread>#include <QtCore/QTimer> #include <SharedUtil.h>#include <shared/QtHelpers.h>#include <Trace.h>#include <Profile.h>#include <QUrlQuery> #include "NetworkAccessManager.h"#include "NetworkLogging.h"#include "NodeList.h" bool ResourceCacheSharedItems::appendRequest(QWeakPointer<Resource> resource, float priority) {    Lock lock(_mutex);    if ((uint32_t)_loadingRequests.size() < _requestLimit) {        _loadingRequests.append({ resource, priority });        return true;    } else {        _pendingRequests.append(resource);        return false;    }} void ResourceCacheSharedItems::setRequestLimit(uint32_t limit) {    Lock lock(_mutex);    _requestLimit = limit;} uint32_t ResourceCacheSharedItems::getRequestLimit() const {    Lock lock(_mutex);    return _requestLimit;} QList<QSharedPointer<Resource>> ResourceCacheSharedItems::getPendingRequests() const {    QList<QSharedPointer<Resource>> result;    Lock lock(_mutex);     foreach (QWeakPointer<Resource> resource, _pendingRequests) {        auto locked = resource.lock();        if (locked) {            result.append(locked);        }    }     return result;} uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const {    Lock lock(_mutex);    return _pendingRequests.size();} QList<std::pair<QSharedPointer<Resource>, float>> ResourceCacheSharedItems::getLoadingRequests() const {    QList<std::pair<QSharedPointer<Resource>, float>> result;    Lock lock(_mutex);     foreach(auto resourcePair, _loadingRequests) {        auto locked = resourcePair.first.lock();        if (locked) {            result.append({ locked, resourcePair.second });        }    }     return result;} uint32_t ResourceCacheSharedItems::getLoadingRequestsCount() const {    Lock lock(_mutex);    return _loadingRequests.size();} void ResourceCacheSharedItems::removeRequest(QWeakPointer<Resource> resource) {    Lock lock(_mutex);     // resource can only be removed if it still has a ref-count, as    // QWeakPointer has no operator== implementation for two weak ptrs, so    // manually loop in case resource has been freed.    for (int i = 0; i < _loadingRequests.size();) {        auto request = _loadingRequests.at(i).first;        // Clear our resource and any freed resources        if (!request || request.toStrongRef().data() == resource.toStrongRef().data()) {            _loadingRequests.removeAt(i);            continue;        }        i++;    }} std::pair<QSharedPointer<Resource>, float> ResourceCacheSharedItems::getHighestPendingRequest() {    // look for the highest priority pending request    int highestIndex = -1;    float highestPriority = -FLT_MAX;    QSharedPointer<Resource> highestResource;    Lock lock(_mutex);     bool currentHighestIsFile = false;     for (int i = 0; i < _pendingRequests.size();) {        // Clear any freed resources        auto resource = _pendingRequests.at(i).lock();        if (!resource) {            _pendingRequests.removeAt(i);            continue;        }         // Check load priority        float priority = resource->getLoadPriority();        bool isFile = resource->getURL().scheme() == HIFI_URL_SCHEME_FILE;        if (priority >= highestPriority && (isFile || !currentHighestIsFile)) {            highestPriority = priority;            highestIndex = i;            highestResource = resource;            currentHighestIsFile = isFile;        }        i++;    }     if (highestIndex >= 0) {        _pendingRequests.takeAt(highestIndex);    }     return { highestResource, highestPriority };} void ResourceCacheSharedItems::clear() {    Lock lock(_mutex);    _pendingRequests.clear();    _loadingRequests.clear();} ScriptableResourceCache::ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache) {    _resourceCache = resourceCache;    connect(&(*_resourceCache), &ResourceCache::dirty,        this, &ScriptableResourceCache::dirty, Qt::DirectConnection);} QVariantList ScriptableResourceCache::getResourceList() {    return _resourceCache->getResourceList();} void ScriptableResourceCache::updateTotalSize(const qint64& deltaSize) {    _resourceCache->updateTotalSize(deltaSize);} ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) {    return _resourceCache->prefetch(url, extra, extraHash);}  ScriptableResource::ScriptableResource(const QUrl& url) :    QObject(nullptr),    _url(url) { } void ScriptableResource::release() {    disconnectHelper();    _resource.reset();} bool ScriptableResource::isInScript() const {    return _resource && _resource->isInScript();} void ScriptableResource::setInScript(bool isInScript) {    if (_resource) {        _resource->setInScript(isInScript);    }} void ScriptableResource::loadingChanged() {    setState(LOADING);} void ScriptableResource::loadedChanged() {    setState(LOADED);} void ScriptableResource::finished(bool success) {    disconnectHelper();     setState(success ? FINISHED : FAILED);} void ScriptableResource::disconnectHelper() {    if (_progressConnection) {        disconnect(_progressConnection);    }    if (_loadingConnection) {        disconnect(_loadingConnection);    }    if (_loadedConnection) {        disconnect(_loadedConnection);    }    if (_finishedConnection) {        disconnect(_finishedConnection);    }} ScriptableResource* ResourceCache::prefetchAndMoveToThread(const QUrl& url, void* extra, size_t extraHash, QThread *scriptThread) {    auto result = prefetch(url, extra, extraHash);    result->moveToThread(scriptThread);    return result;} ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) {    ScriptableResource* result = nullptr;     if (QThread::currentThread() != thread()) {        // Must be called in thread to ensure getResource returns a valid pointer        BLOCKING_INVOKE_METHOD(this, "prefetchAndMoveToThread",            Q_RETURN_ARG(ScriptableResource*, result),            Q_ARG(QUrl, url), Q_ARG(void*, extra), Q_ARG(size_t, extraHash), Q_ARG(QThread*, QThread::currentThread()));        return result;    }     result = new ScriptableResource(url);     auto resource = getResource(url, QUrl(), extra, extraHash);    result->_resource = resource;    result->setObjectName(url.toString());     result->_resource = resource;    if (resource->isLoaded() || resource->_failedToLoad) {        result->finished(!resource->_failedToLoad);    } else {        result->_progressConnection = connect(            resource.data(), &Resource::onProgress,            result, &ScriptableResource::progressChanged);        result->_loadingConnection = connect(            resource.data(), &Resource::loading,            result, &ScriptableResource::loadingChanged);        result->_loadedConnection = connect(            resource.data(), &Resource::loaded,            result, &ScriptableResource::loadedChanged);        result->_finishedConnection = connect(            resource.data(), &Resource::finished,            result, &ScriptableResource::finished);    }     return result;} ResourceCache::ResourceCache(QObject* parent) : QObject(parent) {    if (DependencyManager::isSet<NodeList>()) {        auto nodeList = DependencyManager::get<NodeList>();        auto& domainHandler = nodeList->getDomainHandler();        connect(&domainHandler, &DomainHandler::disconnectedFromDomain,            this, &ResourceCache::clearATPAssets, Qt::DirectConnection);    }} ResourceCache::~ResourceCache() {    clearUnusedResources();} void ResourceCache::clearATPAssets() {    {        QWriteLocker locker(&_resourcesLock);        QList<QUrl> urls = _resources.keys();        for (auto& url : urls) {            // If this is an ATP resource            if (url.scheme() == URL_SCHEME_ATP) {                auto resourcesWithExtraHash = _resources.take(url);                for (auto& resource : resourcesWithExtraHash) {                    if (auto strongRef = resource.lock()) {                        // Make sure the resource won't reinsert itself                        strongRef->setCache(nullptr);                        _totalResourcesSize -= strongRef->getBytes();                    }                }            }        }    }    {        QWriteLocker locker(&_unusedResourcesLock);        for (auto& resource : _unusedResources.values()) {            if (resource->getURL().scheme() == URL_SCHEME_ATP) {                _unusedResources.remove(resource->getLRUKey());                _unusedResourcesSize -= resource->getBytes();            }        }    }     resetResourceCounters();} void ResourceCache::refreshAll() {    // Clear all unused resources so we don't have to reload them    clearUnusedResources();    resetUnusedResourceCounter();     QHash<QUrl, QMultiHash<size_t, QWeakPointer<Resource>>> allResources;    {        QReadLocker locker(&_resourcesLock);        allResources = _resources;    }     // Refresh all remaining resources in use    // FIXME: this will trigger multiple refreshes for the same resource if they have different hashes    for (auto& resourcesWithExtraHash : allResources) {        for (auto& resourceWeak : resourcesWithExtraHash) {            auto resource = resourceWeak.lock();            if (resource) {                resource->refresh();            }        }    }} QVariantList ResourceCache::getResourceList() {    QVariantList list;    if (QThread::currentThread() != thread()) {        // NOTE: invokeMethod does not allow a const QObject*        BLOCKING_INVOKE_METHOD(this, "getResourceList",            Q_RETURN_ARG(QVariantList, list));    } else {        QList<QUrl> resources;        {            QReadLocker locker(&_resourcesLock);            resources = _resources.keys();        }        list.reserve(resources.size());        for (auto& resource : resources) {            list << resource;        }    }     return list;} void ResourceCache::setRequestLimit(uint32_t limit) {    auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();    sharedItems->setRequestLimit(limit);     // Now go fill any new request spots    while (sharedItems->getLoadingRequestsCount() < limit && sharedItems->getPendingRequestsCount() > 0) {        attemptHighestPriorityRequest();    }} QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash) {    QSharedPointer<Resource> resource;    {        QWriteLocker locker(&_resourcesLock);        auto& resourcesWithExtraHash = _resources[url];        auto resourcesWithExtraHashIter = resourcesWithExtraHash.find(extraHash);        if (resourcesWithExtraHashIter != resourcesWithExtraHash.end()) {            // We've seen this extra info before            resource = resourcesWithExtraHashIter.value().lock();        } else if (resourcesWithExtraHash.size() > 0.0f) {            auto oldResource = resourcesWithExtraHash.begin().value().lock();            if (oldResource) {                // We haven't seen this extra info before, but we've already downloaded the resource.  We need a new copy of this object (with any old hash).                resource = createResourceCopy(oldResource);                resource->setExtra(extra);                resource->setExtraHash(extraHash);                resource->setSelf(resource);                resource->setCache(this);                resource->moveToThread(qApp->thread());                connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);                resourcesWithExtraHash.insert(extraHash, resource);                resource->ensureLoading();            }        }    }    if (resource) {        removeUnusedResource(resource);    }     if (!resource && (!url.isValid() || url.isEmpty()) && fallback.isValid()) {        resource = getResource(fallback, QUrl(), extra, extraHash);    }     if (!resource) {        resource = createResource(url);        resource->setExtra(extra);        resource->setExtraHash(extraHash);        resource->setSelf(resource);        resource->setCache(this);        resource->moveToThread(qApp->thread());        connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);        {            QWriteLocker locker(&_resourcesLock);            _resources[url].insert(extraHash, resource);        }        removeUnusedResource(resource);        resource->ensureLoading();    }     DependencyManager::get<ResourceRequestObserver>()->update(resource->getURL(), -1, "ResourceCache::getResource");    return resource;} void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) {    _unusedResourcesMaxSize = glm::clamp(unusedResourcesMaxSize, MIN_UNUSED_MAX_SIZE, MAX_UNUSED_MAX_SIZE);    reserveUnusedResource(0);    resetUnusedResourceCounter();} void ResourceCache::addUnusedResource(const QSharedPointer<Resource>& resource) {    // If it doesn't fit or its size is unknown, remove it from the cache.    if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) {        resource->setCache(nullptr);        removeResource(resource->getURL(), resource->getExtraHash(), resource->getBytes());        resetTotalResourceCounter();        return;    }    reserveUnusedResource(resource->getBytes());     resource->setLRUKey(++_lastLRUKey);     {        QWriteLocker locker(&_unusedResourcesLock);        _unusedResources.insert(resource->getLRUKey(), resource);        _unusedResourcesSize += resource->getBytes();    }     resetUnusedResourceCounter();} void ResourceCache::removeUnusedResource(const QSharedPointer<Resource>& resource) {    QWriteLocker locker(&_unusedResourcesLock);    if (_unusedResources.contains(resource->getLRUKey())) {        _unusedResources.remove(resource->getLRUKey());        _unusedResourcesSize -= resource->getBytes();         locker.unlock();        resetUnusedResourceCounter();    }} void ResourceCache::reserveUnusedResource(qint64 resourceSize) {    QWriteLocker locker(&_unusedResourcesLock);    while (!_unusedResources.empty() &&           _unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) {        // unload the oldest resource        QMap<int, QSharedPointer<Resource> >::iterator it = _unusedResources.begin();         it.value()->setCache(nullptr);        auto size = it.value()->getBytes();         locker.unlock();        removeResource(it.value()->getURL(), it.value()->getExtraHash(), size);        locker.relock();         _unusedResourcesSize -= size;        _unusedResources.erase(it);    }} void ResourceCache::clearUnusedResources() {    // the unused resources may themselves reference resources that will be added to the unused    // list on destruction, so keep clearing until there are no references left    QWriteLocker locker(&_unusedResourcesLock);    while (!_unusedResources.isEmpty()) {        foreach (const QSharedPointer<Resource>& resource, _unusedResources) {            resource->setCache(nullptr);        }        _unusedResources.clear();    }    _unusedResourcesSize = 0;} void ResourceCache::resetTotalResourceCounter() {    {        QReadLocker locker(&_resourcesLock);        _numTotalResources = _resources.size();    }     emit dirty();} void ResourceCache::resetUnusedResourceCounter() {    {        QReadLocker locker(&_unusedResourcesLock);        _numUnusedResources = _unusedResources.size();    }     emit dirty();} 

Lines 1–500 of 887.