//// 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. Load more
// // 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_WIN32 static const qint64 DEFAULT_UNUSED_MAX_SIZE = 100 * BYTES_PER_MEGABYTES; #else static const qint64 DEFAULT_UNUSED_MAX_SIZE = 1024 * BYTES_PER_MEGABYTES; #endif static 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 instead class 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/QML class 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/QML class 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: