579 lines · 21.0 KB · C
RawDownload
 ////  ResourceCache.h//  libraries/shared/src////  Created by Andrzej Kapolka on 2/27/14.//  Copyright 2014 High Fidelity, Inc.//  Copyright 2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0// #ifndef hifi_ResourceCache_h#define hifi_ResourceCache_h #include <atomic>#include <mutex>#include <math.h> #include <QtCore/QHash>#include <QtCore/QList>#include <QtCore/QObject>#include <QtCore/QPointer>#include <QtCore/QSharedPointer>#include <QtCore/QUrl>#include <QtCore/QWeakPointer>#include <QtCore/QReadWriteLock>#include <QtCore/QQueue> #include <QtNetwork/QNetworkReply>#include <QtNetwork/QNetworkRequest> #include <DependencyManager.h> #include "ResourceManager.h" Q_DECLARE_METATYPE(size_t) class QNetworkReply;class QTimer; class Resource; static const qint64 BYTES_PER_MEGABYTES = 1024 * 1024;static const qint64 BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES;static const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES;  // 10GB // Windows can have troubles allocating that much memory in ram sometimes// so default cache size at 100 MB on windows (1GB otherwise)#ifdef Q_OS_WIN32static const qint64 DEFAULT_UNUSED_MAX_SIZE = 100 * BYTES_PER_MEGABYTES;#elsestatic const qint64 DEFAULT_UNUSED_MAX_SIZE = 1024 * BYTES_PER_MEGABYTES;#endifstatic const qint64 MIN_UNUSED_MAX_SIZE = 0;static const qint64 MAX_UNUSED_MAX_SIZE = MAXIMUM_CACHE_SIZE; // We need to make sure that these items are available for all instances of// ResourceCache derived classes. Since we can't count on the ordering of// static members destruction, we need to use this Dependency manager implemented// object insteadclass ResourceCacheSharedItems : public Dependency  {    SINGLETON_DEPENDENCY     using Mutex = std::recursive_mutex;    using Lock = std::unique_lock<Mutex>; public:    bool appendRequest(QWeakPointer<Resource> newRequest, float priority);    void removeRequest(QWeakPointer<Resource> doneRequest);    void setRequestLimit(uint32_t limit);    uint32_t getRequestLimit() const;    QList<QSharedPointer<Resource>> getPendingRequests() const;    std::pair<QSharedPointer<Resource>, float> getHighestPendingRequest();    uint32_t getPendingRequestsCount() const;    QList<std::pair<QSharedPointer<Resource>, float>> getLoadingRequests() const;    uint32_t getLoadingRequestsCount() const;    void clear(); private:    ResourceCacheSharedItems() = default;     mutable Mutex _mutex;    QList<QWeakPointer<Resource>> _pendingRequests;    QList<std::pair<QWeakPointer<Resource>, float>> _loadingRequests;    const uint32_t DEFAULT_REQUEST_LIMIT = 10;    uint32_t _requestLimit { DEFAULT_REQUEST_LIMIT };}; /// Wrapper to expose resources to JS/QMLclass ScriptableResource : public QObject {     /*@jsdoc     * Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link MaterialCache.prefetch},     * {@link ModelCache.prefetch}, {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}.     *     * @class ResourceObject     * @hideconstructor     *     * @hifi-interface     * @hifi-client-entity     * @hifi-avatar     * @hifi-server-entity     * @hifi-assignment-client     *     * @property {string} url - URL of the resource. <em>Read-only.</em>     * @property {Resource.State} state - Current loading state. <em>Read-only.</em>     */    Q_OBJECT    Q_PROPERTY(QUrl url READ getURL)    Q_PROPERTY(int state READ getState NOTIFY stateChanged) public:     /*@jsdoc     * The loading state of a resource.     * @typedef {object} Resource.State     * @property {number} QUEUED - The resource is queued up, waiting to be loaded.     * @property {number} LOADING - The resource is downloading.     * @property {number} LOADED - The resource has finished downloading but is not complete.     * @property {number} FINISHED - The resource has completely finished loading and is ready.     * @property {number} FAILED - The resource has failed to download.     */    enum State {        QUEUED,        LOADING,        LOADED,        FINISHED,        FAILED,    };    Q_ENUM(State)     ScriptableResource(const QUrl& url);    virtual ~ScriptableResource() = default;     /*@jsdoc      * Releases the resource.      * @function ResourceObject#release      */    Q_INVOKABLE void release();     const QUrl& getURL() const { return _url; }    int getState() const { return (int)_state; }    const QSharedPointer<Resource>& getResource() const { return _resource; }     bool isInScript() const;    void setInScript(bool isInScript); signals:     /*@jsdoc     * Triggered when the resource's download progress changes.     * @function ResourceObject#progressChanged     * @param {number} bytesReceived - Bytes downloaded so far.     * @param {number} bytesTotal - Total number of bytes in the resource.     * @returns {Signal}     */    void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal);     /*@jsdoc     * Triggered when the resource's loading state changes.     * @function ResourceObject#stateChanged     * @param {Resource.State} state - New state.     * @returns {Signal}     */    void stateChanged(int state); protected:    void setState(State state) { _state = state; emit stateChanged(_state); } private slots:    void loadingChanged();    void loadedChanged();    void finished(bool success); private:    void disconnectHelper();     friend class ResourceCache;     // Holds a ref to the resource to keep it in scope    QSharedPointer<Resource> _resource;     QMetaObject::Connection _progressConnection;    QMetaObject::Connection _loadingConnection;    QMetaObject::Connection _loadedConnection;    QMetaObject::Connection _finishedConnection;     QUrl _url;    State _state{ QUEUED };}; Q_DECLARE_METATYPE(ScriptableResource*); /// Base class for resource caches.class ResourceCache : public QObject {    Q_OBJECT     Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty)    Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty)    Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)    Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty) public:     size_t getNumTotalResources() const { return _numTotalResources; }    size_t getSizeTotalResources() const { return _totalResourcesSize; }    size_t getNumCachedResources() const { return _numUnusedResources; }    size_t getSizeCachedResources() const { return _unusedResourcesSize; }     Q_INVOKABLE QVariantList getResourceList();     static void setRequestLimit(uint32_t limit);    static uint32_t getRequestLimit() { return DependencyManager::get<ResourceCacheSharedItems>()->getRequestLimit(); }     void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize);    qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; }     static QList<std::pair<QSharedPointer<Resource>, float>> getLoadingRequests();    static uint32_t getPendingRequestCount();    static uint32_t getLoadingRequestCount();     ResourceCache(QObject* parent = nullptr);    virtual ~ResourceCache();     void refreshAll();    void clearUnusedResources(); signals:     void dirty(); protected slots:     void updateTotalSize(const qint64& deltaSize);     // Prefetches a resource to be held by the ScriptEngine.    // Left as a protected member so subclasses can overload prefetch    // and delegate to it (see TextureCache::prefetch(const QUrl&, int).    ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash);     // FIXME: The return type is not recognized by JavaScript.    /// Loads a resource from the specified URL and returns it.    /// If the caller is on a different thread than the ResourceCache,    /// returns an empty smart pointer and loads its asynchronously.    /// \param fallback a fallback URL to load if the desired one is unavailable    // FIXME: std::numeric_limits<size_t>::max() could be a valid extraHash    QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl()) { return getResource(url, fallback, nullptr, std::numeric_limits<size_t>::max()); }    QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash); private slots:    void clearATPAssets(); protected:    // Prefetches a resource to be held by the ScriptEngine.    // Pointers created through this method should be owned by the caller,    // which should be a ScriptEngine with ScriptableResource registered, so that    // the ScriptEngine will delete the pointer when it is garbage collected.    // JSDoc is provided on more general function signature.    Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits<size_t>::max()); }     // Helper function used to move ScriptResource to the script engine thread to avoid causing issues with deleting it    // when corresponding script value gets deleted by script engine.    // It's automatically called by `prefetch`.    Q_INVOKABLE ScriptableResource* prefetchAndMoveToThread(const QUrl& url, void* extra, size_t extraHash, QThread *scriptThread);     /// Creates a new resource.    virtual QSharedPointer<Resource> createResource(const QUrl& url) = 0;    virtual QSharedPointer<Resource> createResourceCopy(const QSharedPointer<Resource>& resource) = 0;     void addUnusedResource(const QSharedPointer<Resource>& resource);    void removeUnusedResource(const QSharedPointer<Resource>& resource);     /// Attempt to load a resource if requests are below the limit, otherwise queue the resource for loading    /// \return true if the resource began loading, otherwise false if the resource is in the pending queue    static bool attemptRequest(QSharedPointer<Resource> resource, float priority = NAN);    static void requestCompleted(QWeakPointer<Resource> resource);    static bool attemptHighestPriorityRequest(); private:    friend class Resource;    friend class ScriptableResourceCache;     void reserveUnusedResource(qint64 resourceSize);    void removeResource(const QUrl& url, size_t extraHash, qint64 size = 0);     void resetTotalResourceCounter();    void resetUnusedResourceCounter();    void resetResourceCounters();     // Resources    QHash<QUrl, QMultiHash<size_t, QWeakPointer<Resource>>> _resources;    QReadWriteLock _resourcesLock { QReadWriteLock::Recursive };    int _lastLRUKey = 0;     std::atomic<size_t> _numTotalResources { 0 };    std::atomic<qint64> _totalResourcesSize { 0 };     // Cached resources    QMap<int, QSharedPointer<Resource>> _unusedResources;    QReadWriteLock _unusedResourcesLock { QReadWriteLock::Recursive };    qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE;     std::atomic<size_t> _numUnusedResources { 0 };    std::atomic<qint64> _unusedResourcesSize { 0 };}; /// Wrapper to expose resource caches to JS/QMLclass ScriptableResourceCache : public QObject {    Q_OBJECT     // JSDoc 3.5.5 doesn't augment name spaces with @property definitions so the following properties JSDoc is copied to the    // different exposed cache classes.     /*@jsdoc     * @property {number} numTotal - Total number of total resources. <em>Read-only.</em>     * @property {number} numCached - Total number of cached resource. <em>Read-only.</em>     * @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>     * @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>     */    Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty)    Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty)    Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)    Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty)     /*@jsdoc     * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).     *     <em>Read-only.</em>     * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).     *     <em>Read-only.</em>     */    Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty)    Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty) public:    ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache);     /*@jsdoc     * Gets the URLs of all resources in the cache.     * @function ResourceCache.getResourceList     * @returns {string[]} The URLs of all resources in the cache.     * @example <caption>Report cached resources.</caption>     * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate.     *     * var cachedResources = AnimationCache.getResourceList();     * print("Cached resources: " + JSON.stringify(cachedResources));     */    Q_INVOKABLE QVariantList getResourceList();     /*@jsdoc     * @function ResourceCache.updateTotalSize     * @param {number} deltaSize - Delta size.     * @deprecated This function is deprecated and will be removed.     */    Q_INVOKABLE void updateTotalSize(const qint64& deltaSize);     /*@jsdoc     * Prefetches a resource.     * @function ResourceCache.prefetch     * @param {string} url - The URL of the resource to prefetch.     * @returns {ResourceObject} A resource object.     * @example <caption>Prefetch a resource and wait until it has loaded.</caption>     * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate.     * // TextureCache has its own version of this function.     *     * var resourceURL = "https://apidocs.overte.org/examples/Silly%20Dancing.fbx";     * var resourceObject = AnimationCache.prefetch(resourceURL);     *     * function checkIfResourceLoaded(state) {     *     if (state === Resource.State.FINISHED) {     *         print("Resource loaded and ready.");     *     } else if (state === Resource.State.FAILED) {     *         print("Resource not loaded.");     *     }     * }     *     * // Resource may have already been loaded.     * print("Resource state: " + resourceObject.state);     * checkIfResourceLoaded(resourceObject.state);     *     * // Resource may still be loading.     * resourceObject.stateChanged.connect(function (state) {     *     print("Resource state changed to: " + state);     *     checkIfResourceLoaded(state);     * });     */    Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits<size_t>::max()); }     // FIXME: This function variation shouldn't be in the API.    Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); signals:     /*@jsdoc     * Triggered when the cache content has changed.     * @function ResourceCache.dirty     * @returns {Signal}     */    void dirty(); private:    QSharedPointer<ResourceCache> _resourceCache;     size_t getNumTotalResources() const { return _resourceCache->getNumTotalResources(); }    size_t getSizeTotalResources() const { return _resourceCache->getSizeTotalResources(); }    size_t getNumCachedResources() const { return _resourceCache->getNumCachedResources(); }    size_t getSizeCachedResources() const { return _resourceCache->getSizeCachedResources(); }     size_t getNumGlobalQueriesPending() const { return ResourceCache::getPendingRequestCount(); }    size_t getNumGlobalQueriesLoading() const { return ResourceCache::getLoadingRequestCount(); }}; /// Base class for resources.class Resource : public QObject {    Q_OBJECT public:    Resource() : QObject(), _loaded(true) {}    Resource(const Resource& other);    Resource(const QUrl& url);    virtual ~Resource();     virtual QString getType() const { return "Resource"; }     /// Returns the key last used to identify this resource in the unused map.    int getLRUKey() const { return _lruKey; }     /// Makes sure that the resource has started loading.    void ensureLoading();     /// Sets the load priority for one owner.    virtual void setLoadPriorityOperator(const QPointer<QObject>& owner, std::function<float()> priorityOperator);     /// Returns the highest load priority across all owners.    float getLoadPriority();     /// Checks whether the resource has loaded.    virtual bool isLoaded() const { return _loaded; }     /// Checks whether the resource has failed to download.    virtual bool isFailed() const { return _failedToLoad; }     /// For loading resources, returns the number of bytes received.    qint64 getBytesReceived() const { return _bytesReceived; }     /// For loading resources, returns the number of total bytes (<= zero if unknown).    qint64 getBytesTotal() const { return _bytesTotal; }     /// For loaded resources, returns the number of actual bytes (defaults to total bytes if not explicitly set).    qint64 getBytes() const { return _bytes; }     /// For loading resources, returns the load progress.    float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : ((float)_bytesReceived / _bytesTotal); }     /// Refreshes the resource.    virtual void refresh();     void setSelf(const QWeakPointer<Resource>& self) { _self = self; }     void setCache(ResourceCache* cache) { _cache = cache; }     virtual void deleter() { allReferencesCleared(); }     const QUrl& getURL() const { return _url; }     unsigned int getDownloadAttempts() { return _attempts; }    unsigned int getDownloadAttemptsRemaining() { return _attemptsRemaining; }     virtual void setExtra(void* extra) {};    void setExtraHash(size_t extraHash) { _extraHash = extraHash; }    size_t getExtraHash() const { return _extraHash; } signals:    /// Fired when the resource begins downloading.    void loading();     /// Fired when the resource has been downloaded.    /// This can be used instead of downloadFinished to access data before it is processed.    void loaded(const QByteArray request);     /// Fired when the resource has finished loading.    void finished(bool success);     /// Fired when the resource failed to load.    void failed(QNetworkReply::NetworkError error);     /// Fired when the resource is refreshed.    void onRefresh();     /// Fired on progress updates.    void onProgress(uint64_t bytesReceived, uint64_t bytesTotal);     /// Fired when the size changes (through setSize).    void updateSize(qint64 deltaSize); protected slots:    void attemptRequest(); protected:

Lines 1–500 of 579.