Overte C++ Documentation
ResourceCache.h
1  //
2 // ResourceCache.h
3 // libraries/shared/src
4 //
5 // Created by Andrzej Kapolka on 2/27/14.
6 // Copyright 2014 High Fidelity, Inc.
7 // Copyright 2023 Overte e.V.
8 //
9 // Distributed under the Apache License, Version 2.0.
10 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
11 // SPDX-License-Identifier: Apache-2.0
12 //
13 
14 #ifndef hifi_ResourceCache_h
15 #define hifi_ResourceCache_h
16 
17 #include <atomic>
18 #include <mutex>
19 #include <math.h>
20 
21 #include <QtCore/QHash>
22 #include <QtCore/QList>
23 #include <QtCore/QObject>
24 #include <QtCore/QPointer>
25 #include <QtCore/QSharedPointer>
26 #include <QtCore/QUrl>
27 #include <QtCore/QWeakPointer>
28 #include <QtCore/QReadWriteLock>
29 #include <QtCore/QQueue>
30 
31 #include <QtNetwork/QNetworkReply>
32 #include <QtNetwork/QNetworkRequest>
33 
34 #include <DependencyManager.h>
35 
36 #include "ResourceManager.h"
37 
38 Q_DECLARE_METATYPE(size_t)
39 
40 class QNetworkReply;
41 class QTimer;
42 
43 class Resource;
44 
45 static const qint64 BYTES_PER_MEGABYTES = 1024 * 1024;
46 static const qint64 BYTES_PER_GIGABYTES = 1024 * BYTES_PER_MEGABYTES;
47 static const qint64 MAXIMUM_CACHE_SIZE = 10 * BYTES_PER_GIGABYTES; // 10GB
48 
49 // Windows can have troubles allocating that much memory in ram sometimes
50 // so default cache size at 100 MB on windows (1GB otherwise)
51 #ifdef Q_OS_WIN32
52 static const qint64 DEFAULT_UNUSED_MAX_SIZE = 100 * BYTES_PER_MEGABYTES;
53 #else
54 static const qint64 DEFAULT_UNUSED_MAX_SIZE = 1024 * BYTES_PER_MEGABYTES;
55 #endif
56 static const qint64 MIN_UNUSED_MAX_SIZE = 0;
57 static const qint64 MAX_UNUSED_MAX_SIZE = MAXIMUM_CACHE_SIZE;
58 
59 // We need to make sure that these items are available for all instances of
60 // ResourceCache derived classes. Since we can't count on the ordering of
61 // static members destruction, we need to use this Dependency manager implemented
62 // object instead
63 class ResourceCacheSharedItems : public Dependency {
64  SINGLETON_DEPENDENCY
65 
66  using Mutex = std::recursive_mutex;
67  using Lock = std::unique_lock<Mutex>;
68 
69 public:
70  bool appendRequest(QWeakPointer<Resource> newRequest, float priority);
71  void removeRequest(QWeakPointer<Resource> doneRequest);
72  void setRequestLimit(uint32_t limit);
73  uint32_t getRequestLimit() const;
74  QList<QSharedPointer<Resource>> getPendingRequests() const;
75  std::pair<QSharedPointer<Resource>, float> getHighestPendingRequest();
76  uint32_t getPendingRequestsCount() const;
77  QList<std::pair<QSharedPointer<Resource>, float>> getLoadingRequests() const;
78  uint32_t getLoadingRequestsCount() const;
79  void clear();
80 
81 private:
82  ResourceCacheSharedItems() = default;
83 
84  mutable Mutex _mutex;
85  QList<QWeakPointer<Resource>> _pendingRequests;
86  QList<std::pair<QWeakPointer<Resource>, float>> _loadingRequests;
87  const uint32_t DEFAULT_REQUEST_LIMIT = 10;
88  uint32_t _requestLimit { DEFAULT_REQUEST_LIMIT };
89 };
90 
92 class ScriptableResource : public QObject {
93 
94  /*@jsdoc
95  * Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link MaterialCache.prefetch},
96  * {@link ModelCache.prefetch}, {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}.
97  *
98  * @class ResourceObject
99  * @hideconstructor
100  *
101  * @hifi-interface
102  * @hifi-client-entity
103  * @hifi-avatar
104  * @hifi-server-entity
105  * @hifi-assignment-client
106  *
107  * @property {string} url - URL of the resource. <em>Read-only.</em>
108  * @property {Resource.State} state - Current loading state. <em>Read-only.</em>
109  */
110  Q_OBJECT
111  Q_PROPERTY(QUrl url READ getURL)
112  Q_PROPERTY(int state READ getState NOTIFY stateChanged)
113 
114 public:
115 
116  /*@jsdoc
117  * The loading state of a resource.
118  * @typedef {object} Resource.State
119  * @property {number} QUEUED - The resource is queued up, waiting to be loaded.
120  * @property {number} LOADING - The resource is downloading.
121  * @property {number} LOADED - The resource has finished downloading but is not complete.
122  * @property {number} FINISHED - The resource has completely finished loading and is ready.
123  * @property {number} FAILED - The resource has failed to download.
124  */
125  enum State {
126  QUEUED,
127  LOADING,
128  LOADED,
129  FINISHED,
130  FAILED,
131  };
132  Q_ENUM(State)
133 
134  ScriptableResource(const QUrl& url);
135  virtual ~ScriptableResource() = default;
136 
137  /*@jsdoc
138  * Releases the resource.
139  * @function ResourceObject#release
140  */
141  Q_INVOKABLE void release();
142 
143  const QUrl& getURL() const { return _url; }
144  int getState() const { return (int)_state; }
145  const QSharedPointer<Resource>& getResource() const { return _resource; }
146 
147  bool isInScript() const;
148  void setInScript(bool isInScript);
149 
150 signals:
151 
152  /*@jsdoc
153  * Triggered when the resource's download progress changes.
154  * @function ResourceObject#progressChanged
155  * @param {number} bytesReceived - Bytes downloaded so far.
156  * @param {number} bytesTotal - Total number of bytes in the resource.
157  * @returns {Signal}
158  */
159  void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal);
160 
161  /*@jsdoc
162  * Triggered when the resource's loading state changes.
163  * @function ResourceObject#stateChanged
164  * @param {Resource.State} state - New state.
165  * @returns {Signal}
166  */
167  void stateChanged(int state);
168 
169 protected:
170  void setState(State state) { _state = state; emit stateChanged(_state); }
171 
172 private slots:
173  void loadingChanged();
174  void loadedChanged();
175  void finished(bool success);
176 
177 private:
178  void disconnectHelper();
179 
180  friend class ResourceCache;
181 
182  // Holds a ref to the resource to keep it in scope
183  QSharedPointer<Resource> _resource;
184 
185  QMetaObject::Connection _progressConnection;
186  QMetaObject::Connection _loadingConnection;
187  QMetaObject::Connection _loadedConnection;
188  QMetaObject::Connection _finishedConnection;
189 
190  QUrl _url;
191  State _state{ QUEUED };
192 };
193 
194 Q_DECLARE_METATYPE(ScriptableResource*);
195 
197 class ResourceCache : public QObject {
198  Q_OBJECT
199 
200  Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty)
201  Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty)
202  Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)
203  Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty)
204 
205 public:
206 
207  size_t getNumTotalResources() const { return _numTotalResources; }
208  size_t getSizeTotalResources() const { return _totalResourcesSize; }
209  size_t getNumCachedResources() const { return _numUnusedResources; }
210  size_t getSizeCachedResources() const { return _unusedResourcesSize; }
211 
212  Q_INVOKABLE QVariantList getResourceList();
213 
214  static void setRequestLimit(uint32_t limit);
215  static uint32_t getRequestLimit() { return DependencyManager::get<ResourceCacheSharedItems>()->getRequestLimit(); }
216 
217  void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize);
218  qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; }
219 
220  static QList<std::pair<QSharedPointer<Resource>, float>> getLoadingRequests();
221  static uint32_t getPendingRequestCount();
222  static uint32_t getLoadingRequestCount();
223 
224  ResourceCache(QObject* parent = nullptr);
225  virtual ~ResourceCache();
226 
227  void refreshAll();
228  void clearUnusedResources();
229 
230 signals:
231 
232  void dirty();
233 
234 protected slots:
235 
236  void updateTotalSize(const qint64& deltaSize);
237 
238  // Prefetches a resource to be held by the ScriptEngine.
239  // Left as a protected member so subclasses can overload prefetch
240  // and delegate to it (see TextureCache::prefetch(const QUrl&, int).
241  ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash);
242 
243  // FIXME: The return type is not recognized by JavaScript.
248  // FIXME: std::numeric_limits<size_t>::max() could be a valid extraHash
249  QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl()) { return getResource(url, fallback, nullptr, std::numeric_limits<size_t>::max()); }
250  QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash);
251 
252 private slots:
253  void clearATPAssets();
254 
255 protected:
256  // Prefetches a resource to be held by the ScriptEngine.
257  // Pointers created through this method should be owned by the caller,
258  // which should be a ScriptEngine with ScriptableResource registered, so that
259  // the ScriptEngine will delete the pointer when it is garbage collected.
260  // JSDoc is provided on more general function signature.
261  Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits<size_t>::max()); }
262 
263  // Helper function used to move ScriptResource to the script engine thread to avoid causing issues with deleting it
264  // when corresponding script value gets deleted by script engine.
265  // It's automatically called by `prefetch`.
266  ScriptableResource* prefetchAndMoveToThread(const QUrl& url, void* extra, size_t extraHash, QThread *scriptThread);
267 
269  virtual QSharedPointer<Resource> createResource(const QUrl& url) = 0;
270  virtual QSharedPointer<Resource> createResourceCopy(const QSharedPointer<Resource>& resource) = 0;
271 
272  void addUnusedResource(const QSharedPointer<Resource>& resource);
273  void removeUnusedResource(const QSharedPointer<Resource>& resource);
274 
277  static bool attemptRequest(QSharedPointer<Resource> resource, float priority = NAN);
278  static void requestCompleted(QWeakPointer<Resource> resource);
279  static bool attemptHighestPriorityRequest();
280 
281 private:
282  friend class Resource;
283  friend class ScriptableResourceCache;
284 
285  void reserveUnusedResource(qint64 resourceSize);
286  void removeResource(const QUrl& url, size_t extraHash, qint64 size = 0);
287 
288  void resetTotalResourceCounter();
289  void resetUnusedResourceCounter();
290  void resetResourceCounters();
291 
292  // Resources
293  QHash<QUrl, QMultiHash<size_t, QWeakPointer<Resource>>> _resources;
294  QReadWriteLock _resourcesLock { QReadWriteLock::Recursive };
295  int _lastLRUKey = 0;
296 
297  std::atomic<size_t> _numTotalResources { 0 };
298  std::atomic<qint64> _totalResourcesSize { 0 };
299 
300  // Cached resources
301  QMap<int, QSharedPointer<Resource>> _unusedResources;
302  QReadWriteLock _unusedResourcesLock { QReadWriteLock::Recursive };
303  qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE;
304 
305  std::atomic<size_t> _numUnusedResources { 0 };
306  std::atomic<qint64> _unusedResourcesSize { 0 };
307 };
308 
310 class ScriptableResourceCache : public QObject {
311  Q_OBJECT
312 
313  // JSDoc 3.5.5 doesn't augment name spaces with @property definitions so the following properties JSDoc is copied to the
314  // different exposed cache classes.
315 
316  /*@jsdoc
317  * @property {number} numTotal - Total number of total resources. <em>Read-only.</em>
318  * @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
319  * @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
320  * @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
321  */
322  Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty)
323  Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty)
324  Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty)
325  Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty)
326 
327  /*@jsdoc
328  * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
329  * <em>Read-only.</em>
330  * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
331  * <em>Read-only.</em>
332  */
333  Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty)
334  Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty)
335 
336 public:
337  ScriptableResourceCache(QSharedPointer<ResourceCache> resourceCache);
338 
339  /*@jsdoc
340  * Gets the URLs of all resources in the cache.
341  * @function ResourceCache.getResourceList
342  * @returns {string[]} The URLs of all resources in the cache.
343  * @example <caption>Report cached resources.</caption>
344  * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate.
345  *
346  * var cachedResources = AnimationCache.getResourceList();
347  * print("Cached resources: " + JSON.stringify(cachedResources));
348  */
349  Q_INVOKABLE QVariantList getResourceList();
350 
351  /*@jsdoc
352  * @function ResourceCache.updateTotalSize
353  * @param {number} deltaSize - Delta size.
354  * @deprecated This function is deprecated and will be removed.
355  */
356  Q_INVOKABLE void updateTotalSize(const qint64& deltaSize);
357 
358  /*@jsdoc
359  * Prefetches a resource.
360  * @function ResourceCache.prefetch
361  * @param {string} url - The URL of the resource to prefetch.
362  * @returns {ResourceObject} A resource object.
363  * @example <caption>Prefetch a resource and wait until it has loaded.</caption>
364  * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate.
365  * // TextureCache has its own version of this function.
366  *
367  * var resourceURL = "https://apidocs.overte.org/examples/Silly%20Dancing.fbx";
368  * var resourceObject = AnimationCache.prefetch(resourceURL);
369  *
370  * function checkIfResourceLoaded(state) {
371  * if (state === Resource.State.FINISHED) {
372  * print("Resource loaded and ready.");
373  * } else if (state === Resource.State.FAILED) {
374  * print("Resource not loaded.");
375  * }
376  * }
377  *
378  * // Resource may have already been loaded.
379  * print("Resource state: " + resourceObject.state);
380  * checkIfResourceLoaded(resourceObject.state);
381  *
382  * // Resource may still be loading.
383  * resourceObject.stateChanged.connect(function (state) {
384  * print("Resource state changed to: " + state);
385  * checkIfResourceLoaded(state);
386  * });
387  */
388  Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits<size_t>::max()); }
389 
390  // FIXME: This function variation shouldn't be in the API.
391  Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash);
392 
393 signals:
394 
395  /*@jsdoc
396  * Triggered when the cache content has changed.
397  * @function ResourceCache.dirty
398  * @returns {Signal}
399  */
400  void dirty();
401 
402 private:
403  QSharedPointer<ResourceCache> _resourceCache;
404 
405  size_t getNumTotalResources() const { return _resourceCache->getNumTotalResources(); }
406  size_t getSizeTotalResources() const { return _resourceCache->getSizeTotalResources(); }
407  size_t getNumCachedResources() const { return _resourceCache->getNumCachedResources(); }
408  size_t getSizeCachedResources() const { return _resourceCache->getSizeCachedResources(); }
409 
410  size_t getNumGlobalQueriesPending() const { return ResourceCache::getPendingRequestCount(); }
411  size_t getNumGlobalQueriesLoading() const { return ResourceCache::getLoadingRequestCount(); }
412 };
413 
415 class Resource : public QObject {
416  Q_OBJECT
417 
418 public:
419  Resource() : QObject(), _loaded(true) {}
420  Resource(const Resource& other);
421  Resource(const QUrl& url);
422  virtual ~Resource();
423 
424  virtual QString getType() const { return "Resource"; }
425 
427  int getLRUKey() const { return _lruKey; }
428 
430  void ensureLoading();
431 
433  virtual void setLoadPriorityOperator(const QPointer<QObject>& owner, std::function<float()> priorityOperator);
434 
436  float getLoadPriority();
437 
439  virtual bool isLoaded() const { return _loaded; }
440 
442  virtual bool isFailed() const { return _failedToLoad; }
443 
445  qint64 getBytesReceived() const { return _bytesReceived; }
446 
448  qint64 getBytesTotal() const { return _bytesTotal; }
449 
451  qint64 getBytes() const { return _bytes; }
452 
454  float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : ((float)_bytesReceived / _bytesTotal); }
455 
457  virtual void refresh();
458 
459  void setSelf(const QWeakPointer<Resource>& self) { _self = self; }
460 
461  void setCache(ResourceCache* cache) { _cache = cache; }
462 
463  virtual void deleter() { allReferencesCleared(); }
464 
465  const QUrl& getURL() const { return _url; }
466 
467  unsigned int getDownloadAttempts() { return _attempts; }
468  unsigned int getDownloadAttemptsRemaining() { return _attemptsRemaining; }
469 
470  virtual void setExtra(void* extra) {};
471  void setExtraHash(size_t extraHash) { _extraHash = extraHash; }
472  size_t getExtraHash() const { return _extraHash; }
473 
474 signals:
476  void loading();
477 
480  void loaded(const QByteArray request);
481 
483  void finished(bool success);
484 
486  void failed(QNetworkReply::NetworkError error);
487 
489  void onRefresh();
490 
492  void onProgress(uint64_t bytesReceived, uint64_t bytesTotal);
493 
495  void updateSize(qint64 deltaSize);
496 
497 protected slots:
498  void attemptRequest();
499 
500 protected:
501  virtual void init(bool resetLoaded = true);
502 
506  virtual void makeRequest();
507 
509  virtual bool isCacheable() const { return _loaded; }
510 
513  virtual void downloadFinished(const QByteArray& data) { finishedLoading(true); }
514 
516  void setSize(const qint64& bytes);
517 
520  Q_INVOKABLE void finishedLoading(bool success);
521 
522  Q_INVOKABLE void allReferencesCleared();
523 
525  virtual bool handleFailedRequest(ResourceRequest::Result result);
526 
527  QUrl _url;
528  QUrl _effectiveBaseURL { _url };
529  QUrl _activeUrl;
530  ByteRange _requestByteRange;
531  bool _shouldFailOnRedirect { false };
532 
533  // _loaded == true means we are in a loaded and usable state. It is possible that there may still be
534  // active requests/loading while in this state. Example: Progressive KTX downloads, where higher resolution
535  // mips are being download.
536  bool _startedLoading = false;
537  bool _failedToLoad = false;
538  bool _loaded = false;
539 
540  QHash<QPointer<QObject>, std::function<float()>> _loadPriorityOperators;
541  QWeakPointer<Resource> _self;
542  QPointer<ResourceCache> _cache;
543 
544  qint64 _bytesReceived { 0 };
545  qint64 _bytesTotal { 0 };
546  qint64 _bytes { 0 };
547 
548  int _requestID;
549  ResourceRequest* _request { nullptr };
550 
551  size_t _extraHash { std::numeric_limits<size_t>::max() };
552 
553 public slots:
554  void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
555  void handleReplyFinished();
556 
557 private:
558  friend class ResourceCache;
559  friend class ScriptableResource;
560 
561  void setLRUKey(int lruKey) { _lruKey = lruKey; }
562 
563  void retry();
564  void reinsert();
565 
566  bool isInScript() const { return _isInScript; }
567  void setInScript(bool isInScript) { _isInScript = isInScript; }
568 
569  int _lruKey{ 0 };
570  QTimer* _replyTimer{ nullptr };
571  unsigned int _attempts{ 0 };
572  static const int MAX_ATTEMPTS = 8;
573  unsigned int _attemptsRemaining { MAX_ATTEMPTS };
574  bool _isInScript{ false };
575 };
576 
577 uint qHash(const QPointer<QObject>& value, uint seed = 0);
578 
579 #endif // hifi_ResourceCache_h
Base class for resource caches.
Definition: ResourceCache.h:197
static bool attemptRequest(QSharedPointer< Resource > resource, float priority=NAN)
Definition: ResourceCache.cpp:540
virtual QSharedPointer< Resource > createResource(const QUrl &url)=0
Creates a new resource.
QSharedPointer< Resource > getResource(const QUrl &url, const QUrl &fallback=QUrl())
Definition: ResourceCache.h:249
Base class for resources.
Definition: ResourceCache.h:415
void loading()
Fired when the resource begins downloading.
qint64 getBytesReceived() const
For loading resources, returns the number of bytes received.
Definition: ResourceCache.h:445
virtual bool isCacheable() const
Checks whether the resource is cacheable.
Definition: ResourceCache.h:509
qint64 getBytes() const
For loaded resources, returns the number of actual bytes (defaults to total bytes if not explicitly s...
Definition: ResourceCache.h:451
virtual void refresh()
Refreshes the resource.
Definition: ResourceCache.cpp:637
virtual bool isLoaded() const
Checks whether the resource has loaded.
Definition: ResourceCache.h:439
virtual bool isFailed() const
Checks whether the resource has failed to download.
Definition: ResourceCache.h:442
void finished(bool success)
Fired when the resource has finished loading.
void onProgress(uint64_t bytesReceived, uint64_t bytesTotal)
Fired on progress updates.
void onRefresh()
Fired when the resource is refreshed.
float getProgress() const
For loading resources, returns the load progress.
Definition: ResourceCache.h:454
void loaded(const QByteArray request)
void failed(QNetworkReply::NetworkError error)
Fired when the resource failed to load.
virtual void setLoadPriorityOperator(const QPointer< QObject > &owner, std::function< float()> priorityOperator)
Sets the load priority for one owner.
Definition: ResourceCache.cpp:614
float getLoadPriority()
Returns the highest load priority across all owners.
Definition: ResourceCache.cpp:620
Q_INVOKABLE void finishedLoading(bool success)
Definition: ResourceCache.cpp:733
virtual void downloadFinished(const QByteArray &data)
Definition: ResourceCache.h:513
void setSize(const qint64 &bytes)
Called when the download is finished and processed, sets the number of actual bytes.
Definition: ResourceCache.cpp:743
int getLRUKey() const
Returns the key last used to identify this resource in the unused map.
Definition: ResourceCache.h:427
qint64 getBytesTotal() const
For loading resources, returns the number of total bytes (<= zero if unknown).
Definition: ResourceCache.h:448
virtual bool handleFailedRequest(ResourceRequest::Result result)
Return true if the resource will be retried.
Definition: ResourceCache.cpp:843
void ensureLoading()
Makes sure that the resource has started loading.
Definition: ResourceCache.cpp:608
virtual void makeRequest()
Definition: ResourceCache.cpp:754
void updateSize(qint64 deltaSize)
Fired when the size changes (through setSize).
Wrapper to expose resource caches to JS/QML.
Definition: ResourceCache.h:310
Wrapper to expose resources to JS/QML.
Definition: ResourceCache.h:92