From e3131d20989d3770791e5a67dd2fcbaeaff1061e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 19 Apr 2016 21:25:59 -0700 Subject: [PATCH 01/26] Rm prefetchAnimation from avatar/rig --- interface/src/avatar/MyAvatar.cpp | 8 -------- interface/src/avatar/MyAvatar.h | 3 --- libraries/animation/src/Rig.cpp | 8 -------- libraries/animation/src/Rig.h | 2 -- 4 files changed, 21 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ea713e2d96..06d863eaac 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -627,14 +627,6 @@ void MyAvatar::restoreRoleAnimation(const QString& role) { _rig->restoreRoleAnimation(role); } -void MyAvatar::prefetchAnimation(const QString& url) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "prefetchAnimation", Q_ARG(const QString&, url)); - return; - } - _rig->prefetchAnimation(url); -} - void MyAvatar::saveData() { Settings settings; settings.beginGroup("Avatar"); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 35caabe0f7..2de8aa9405 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -143,9 +143,6 @@ public: // remove an animation role override and return to the standard animation. Q_INVOKABLE void restoreRoleAnimation(const QString& role); - // prefetch animation - Q_INVOKABLE void prefetchAnimation(const QString& url); - // Adds handler(animStateDictionaryIn) => animStateDictionaryOut, which will be invoked just before each animGraph state update. // The handler will be called with an animStateDictionaryIn that has all those properties specified by the (possibly empty) // propertiesList argument. However for debugging, if the properties argument is null, all internal animGraph state is provided. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b3f5e30d40..a7115199a2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -152,14 +152,6 @@ void Rig::restoreRoleAnimation(const QString& role) { } } -void Rig::prefetchAnimation(const QString& url) { - - // This will begin loading the NetworkGeometry for the given URL. - // which should speed us up if we request it later via overrideAnimation. - auto clipNode = std::make_shared("prefetch", url, 0, 0, 1.0, false, false); - _prefetchedAnimations.push_back(clipNode); -} - void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 897fa358e8..f50ea04b1d 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -94,7 +94,6 @@ public: QStringList getAnimationRoles() const; void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); - void prefetchAnimation(const QString& url); void initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset); void reset(const FBXGeometry& geometry); @@ -322,7 +321,6 @@ protected: SimpleMovingAverage _averageLateralSpeed { 10 }; std::map _origRoleAnimations; - std::vector _prefetchedAnimations; bool _lastEnableInverseKinematics { true }; bool _enableInverseKinematics { true }; From 74e1f817a54e013377cc1d41eca5bb4af22a0531 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 19 Apr 2016 21:26:11 -0700 Subject: [PATCH 02/26] Add prefetch to all resource caches --- libraries/networking/src/ResourceCache.cpp | 22 ++++++++++++++++++++++ libraries/networking/src/ResourceCache.h | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 68d9a61226..84dd307792 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -169,6 +169,11 @@ 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(); @@ -217,6 +222,23 @@ 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 ed3dbf69b6..e284e24860 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -96,6 +96,8 @@ public: Q_INVOKABLE QVariantList getResourceList(); + Q_INVOKABLE void prefetch(const QUrl& url); + static void setRequestLimit(int limit); static int getRequestLimit() { return _requestLimit; } @@ -175,6 +177,9 @@ private: qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE; QReadWriteLock _unusedResourcesLock { QReadWriteLock::Recursive }; QMap> _unusedResources; + + QMutex _prefetchingResourcesLock{ QMutex::Recursive }; + QMap> _prefetchingResources; }; /// Base class for resources. From 3c7fc95d6cd901e768e0f4943ea4ad422ee28693 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 19 Apr 2016 23:24:48 -0700 Subject: [PATCH 03/26] Allow url-only cache queries for tex, model --- .../model-networking/src/model-networking/ModelCache.cpp | 7 ++++--- .../model-networking/src/model-networking/TextureCache.cpp | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 6dd1d97d7f..3e685e6397 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -237,13 +237,14 @@ ModelCache::ModelCache() { QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - const GeometryExtra* geometryExtra = static_cast(extra); - Resource* resource = nullptr; if (url.path().toLower().endsWith(".fst")) { resource = new GeometryMappingResource(url); } else { - resource = new GeometryDefinitionResource(url, geometryExtra->mapping, geometryExtra->textureBaseUrl); + const GeometryExtra* geometryExtra = static_cast(extra); + auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); + auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl(); + resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl); } return QSharedPointer(resource, &Resource::deleter); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 7d18151f2c..c931b8bd2a 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -216,7 +216,9 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, TextureTy QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { const TextureExtra* textureExtra = static_cast(extra); - return QSharedPointer(new NetworkTexture(url, textureExtra->type, textureExtra->content), + auto type = textureExtra ? textureExtra->type : TextureType::DEFAULT_TEXTURE; + auto content = textureExtra ? textureExtra->content : QByteArray(); + return QSharedPointer(new NetworkTexture(url, type, content), &Resource::deleter); } From 8ad8b5d0d966c70a5aedc86bc6c40eee6110b17e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Tue, 19 Apr 2016 22:03:40 -0700 Subject: [PATCH 04/26] Return ScriptableResource from prefetch --- libraries/networking/src/ResourceCache.cpp | 75 ++++++++++++++------ libraries/networking/src/ResourceCache.h | 41 +++++++++-- libraries/script-engine/src/ScriptEngine.cpp | 16 +++++ 3 files changed, 105 insertions(+), 27 deletions(-) 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))); From 7d9790ecb57ef6f8a3059c265c9767c0a5fca89d Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 01:25:44 -0700 Subject: [PATCH 05/26] Add request for garbage collection Add garbage collection request in prod --- libraries/script-engine/src/ScriptEngine.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index e8ce00c66c..605f30ed6e 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -131,6 +131,8 @@ public: Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const MouseEvent& event); Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts Q_INVOKABLE void stop(); From f8bdcd1ef2687c4651c687826f3019f6b2496fed Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 01:27:10 -0700 Subject: [PATCH 06/26] Update away.js to use prefetch --- examples/away.js | 4 ++-- examples/theBird.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/away.js b/examples/away.js index 9c5aed98fa..6fc7452910 100644 --- a/examples/away.js +++ b/examples/away.js @@ -46,8 +46,8 @@ var AWAY_INTRO = { endFrame: 83.0 }; -// prefetch the kneel animation so it's resident in memory when we need it. -MyAvatar.prefetchAnimation(AWAY_INTRO.url); +// prefetch the kneel animation and hold a ref so it's always resident in memory when we need it. +var _animation = AnimationCache.prefetch(AWAY_INTRO.url); function playAwayAnimation() { MyAvatar.overrideAnimation(AWAY_INTRO.url, AWAY_INTRO.playbackRate, AWAY_INTRO.loopFlag, AWAY_INTRO.startFrame, AWAY_INTRO.endFrame); diff --git a/examples/theBird.js b/examples/theBird.js index 02b2e7fc5d..4adc6e3968 100644 --- a/examples/theBird.js +++ b/examples/theBird.js @@ -20,8 +20,6 @@ for (i = 0; i < l; i++) { print(roles[i]); } -MyAvatar.prefetchAnimation(THE_BIRD_RIGHT_URL); - // replace point animations with the bird! MyAvatar.overrideRoleAnimation("rightHandPointIntro", THE_BIRD_RIGHT_URL, 30, false, 0, 12); MyAvatar.overrideRoleAnimation("rightHandPointHold", THE_BIRD_RIGHT_URL, 30, false, 12, 12); From a148a69a35750934fb1073520cdf46736974665a Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 15:36:19 -0700 Subject: [PATCH 07/26] Move cache to bottom of texture header --- .../src/model-networking/TextureCache.cpp | 4 +- .../src/model-networking/TextureCache.h | 103 +++++++++--------- 2 files changed, 52 insertions(+), 55 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index c931b8bd2a..287025c84a 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -155,7 +155,7 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type } -TextureCache::TextureLoaderFunc getTextureLoaderForType(TextureType type) { +NetworkTexture::TextureLoaderFunc getTextureLoaderForType(TextureType type) { switch (type) { case ALBEDO_TEXTURE: { return model::TextureUsage::createAlbedoTextureFromImage; @@ -195,7 +195,7 @@ TextureCache::TextureLoaderFunc getTextureLoaderForType(TextureType type) { } case CUSTOM_TEXTURE: { Q_ASSERT(false); - return TextureCache::TextureLoaderFunc(); + return NetworkTexture::TextureLoaderFunc(); break; } case DEFAULT_TEXTURE: diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index c614a7ceb3..948792ae70 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -25,9 +25,6 @@ namespace gpu { class Batch; } -class NetworkTexture; - -typedef QSharedPointer NetworkTexturePointer; enum TextureType { DEFAULT_TEXTURE, @@ -45,6 +42,56 @@ enum TextureType { CUSTOM_TEXTURE }; +/// A simple object wrapper for an OpenGL texture. +class Texture { +public: + gpu::TexturePointer getGPUTexture() const { return _textureSource->getGPUTexture(); } + gpu::TextureSourcePointer _textureSource; +}; + +/// A texture loaded from the network. + +class NetworkTexture : public Resource, public Texture { + Q_OBJECT + +public: + + typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName); + using TextureLoaderFunc = std::function; + + NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); + NetworkTexture(const QUrl& url, const TextureLoaderFunc& textureLoader, const QByteArray& content); + + int getOriginalWidth() const { return _originalWidth; } + int getOriginalHeight() const { return _originalHeight; } + int getWidth() const { return _width; } + int getHeight() const { return _height; } + + TextureLoaderFunc getTextureLoader() const; + +signals: + void networkTextureCreated(const QWeakPointer& self); + +protected: + + virtual bool isCacheable() const override { return _loaded; } + + virtual void downloadFinished(const QByteArray& data) override; + + Q_INVOKABLE void loadContent(const QByteArray& content); + Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight); + +private: + TextureType _type; + TextureLoaderFunc _textureLoader; + int _originalWidth { 0 }; + int _originalHeight { 0 }; + int _width { 0 }; + int _height { 0 }; +}; + +using NetworkTexturePointer = QSharedPointer; + /// Stores cached textures, including render-to-texture targets. class TextureCache : public ResourceCache, public Dependency { Q_OBJECT @@ -78,9 +125,6 @@ public: NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, const QByteArray& content = QByteArray()); - typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName); - - typedef std::function TextureLoaderFunc; protected: virtual QSharedPointer createResource(const QUrl& url, @@ -99,51 +143,4 @@ private: gpu::TexturePointer _normalFittingTexture; }; -/// A simple object wrapper for an OpenGL texture. -class Texture { -public: - gpu::TexturePointer getGPUTexture() const { return _textureSource->getGPUTexture(); } - gpu::TextureSourcePointer _textureSource; -}; - -/// A texture loaded from the network. - -class NetworkTexture : public Resource, public Texture { - Q_OBJECT - -public: - - typedef TextureCache::TextureLoaderFunc TextureLoaderFunc; - - NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); - NetworkTexture(const QUrl& url, const TextureLoaderFunc& textureLoader, const QByteArray& content); - - int getOriginalWidth() const { return _originalWidth; } - int getOriginalHeight() const { return _originalHeight; } - int getWidth() const { return _width; } - int getHeight() const { return _height; } - - TextureLoaderFunc getTextureLoader() const; - -signals: - void networkTextureCreated(const QWeakPointer& self); - -protected: - - virtual bool isCacheable() const override { return _loaded; } - - virtual void downloadFinished(const QByteArray& data) override; - - Q_INVOKABLE void loadContent(const QByteArray& content); - Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight); - -private: - TextureType _type; - TextureLoaderFunc _textureLoader; - int _originalWidth { 0 }; - int _originalHeight { 0 }; - int _width { 0 }; - int _height { 0 }; -}; - #endif // hifi_TextureCache_h From c096b0dfa1d5b5cfc627a62795cd342ec6133f2c Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 15:47:47 -0700 Subject: [PATCH 08/26] Add TextureType to NetworkTexture for reflection --- .../src/EntityTreeRenderer.cpp | 4 +- .../src/model-networking/ModelCache.cpp | 32 ++++++------- .../src/model-networking/ModelCache.h | 2 + .../src/model-networking/TextureCache.cpp | 36 ++++++++------- .../src/model-networking/TextureCache.h | 46 ++++++++++--------- 5 files changed, 63 insertions(+), 57 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 09f50c5de3..b565982fa3 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -380,7 +380,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getKeyLightProperties().getAmbientURL(), CUBE_TEXTURE); + _ambientTexture = textureCache->getTexture(zone->getKeyLightProperties().getAmbientURL(), NetworkTexture::CUBE_TEXTURE); _pendingAmbientTexture = true; if (_ambientTexture && _ambientTexture->isLoaded()) { @@ -410,7 +410,7 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrgetTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE); + _skyboxTexture = textureCache->getTexture(zone->getSkyboxProperties().getURL(), NetworkTexture::CUBE_TEXTURE); _pendingSkyboxTexture = true; if (_skyboxTexture && _skyboxTexture->isLoaded()) { diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 3e685e6397..e4fb5c97f8 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -425,7 +425,7 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur { _textures = Textures(MapChannel::NUM_MAP_CHANNELS); if (!material.albedoTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.albedoTexture, NetworkTexture::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); _albedoTransform = material.albedoTexture.transform; map->setTextureTransform(_albedoTransform); @@ -442,39 +442,39 @@ NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textur if (!material.normalTexture.filename.isEmpty()) { - auto type = (material.normalTexture.isBumpmap ? BUMP_TEXTURE : NORMAL_TEXTURE); + auto type = (material.normalTexture.isBumpmap ? NetworkTexture::BUMP_TEXTURE : NetworkTexture::NORMAL_TEXTURE); auto map = fetchTextureMap(textureBaseUrl, material.normalTexture, type, MapChannel::NORMAL_MAP); setTextureMap(MapChannel::NORMAL_MAP, map); } if (!material.roughnessTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.roughnessTexture, NetworkTexture::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); setTextureMap(MapChannel::ROUGHNESS_MAP, map); } else if (!material.glossTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.glossTexture, NetworkTexture::GLOSS_TEXTURE, MapChannel::ROUGHNESS_MAP); setTextureMap(MapChannel::ROUGHNESS_MAP, map); } if (!material.metallicTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, METALLIC_TEXTURE, MapChannel::METALLIC_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.metallicTexture, NetworkTexture::METALLIC_TEXTURE, MapChannel::METALLIC_MAP); setTextureMap(MapChannel::METALLIC_MAP, map); } else if (!material.specularTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, SPECULAR_TEXTURE, MapChannel::METALLIC_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.specularTexture, NetworkTexture::SPECULAR_TEXTURE, MapChannel::METALLIC_MAP); setTextureMap(MapChannel::METALLIC_MAP, map); } if (!material.occlusionTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.occlusionTexture, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); setTextureMap(MapChannel::OCCLUSION_MAP, map); } if (!material.emissiveTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.emissiveTexture, NetworkTexture::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); setTextureMap(MapChannel::EMISSIVE_MAP, map); } if (!material.lightmapTexture.filename.isEmpty()) { - auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); + auto map = fetchTextureMap(textureBaseUrl, material.lightmapTexture, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); _lightmapTransform = material.lightmapTexture.transform; _lightmapParams = material.lightmapParams; map->setTextureTransform(_lightmapTransform); @@ -496,7 +496,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { if (!albedoName.isEmpty()) { auto url = textureMap.contains(albedoName) ? textureMap[albedoName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); + auto map = fetchTextureMap(url, NetworkTexture::ALBEDO_TEXTURE, MapChannel::ALBEDO_MAP); map->setTextureTransform(_albedoTransform); // when reassigning the albedo texture we also check for the alpha channel used as opacity map->setUseAlphaChannel(true); @@ -505,39 +505,39 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { if (!normalName.isEmpty()) { auto url = textureMap.contains(normalName) ? textureMap[normalName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, NORMAL_TEXTURE, MapChannel::NORMAL_MAP); + auto map = fetchTextureMap(url, NetworkTexture::NORMAL_TEXTURE, MapChannel::NORMAL_MAP); setTextureMap(MapChannel::NORMAL_MAP, map); } if (!roughnessName.isEmpty()) { auto url = textureMap.contains(roughnessName) ? textureMap[roughnessName].toUrl() : QUrl(); // FIXME: If passing a gloss map instead of a roughmap how do we know? - auto map = fetchTextureMap(url, ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); + auto map = fetchTextureMap(url, NetworkTexture::ROUGHNESS_TEXTURE, MapChannel::ROUGHNESS_MAP); setTextureMap(MapChannel::ROUGHNESS_MAP, map); } if (!metallicName.isEmpty()) { auto url = textureMap.contains(metallicName) ? textureMap[metallicName].toUrl() : QUrl(); // FIXME: If passing a specular map instead of a metallic how do we know? - auto map = fetchTextureMap(url, METALLIC_TEXTURE, MapChannel::METALLIC_MAP); + auto map = fetchTextureMap(url, NetworkTexture::METALLIC_TEXTURE, MapChannel::METALLIC_MAP); setTextureMap(MapChannel::METALLIC_MAP, map); } if (!occlusionName.isEmpty()) { auto url = textureMap.contains(occlusionName) ? textureMap[occlusionName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); + auto map = fetchTextureMap(url, NetworkTexture::OCCLUSION_TEXTURE, MapChannel::OCCLUSION_MAP); setTextureMap(MapChannel::OCCLUSION_MAP, map); } if (!emissiveName.isEmpty()) { auto url = textureMap.contains(emissiveName) ? textureMap[emissiveName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); + auto map = fetchTextureMap(url, NetworkTexture::EMISSIVE_TEXTURE, MapChannel::EMISSIVE_MAP); setTextureMap(MapChannel::EMISSIVE_MAP, map); } if (!lightmapName.isEmpty()) { auto url = textureMap.contains(lightmapName) ? textureMap[lightmapName].toUrl() : QUrl(); - auto map = fetchTextureMap(url, LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); + auto map = fetchTextureMap(url, NetworkTexture::LIGHTMAP_TEXTURE, MapChannel::LIGHTMAP_MAP); map->setTextureTransform(_lightmapTransform); map->setLightmapOffsetScale(_lightmapParams.x, _lightmapParams.y); setTextureMap(MapChannel::LIGHTMAP_MAP, map); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index f479dc9ce2..464cb557ff 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -170,6 +170,8 @@ protected: const bool& isOriginal() const { return _isOriginal; } private: + using TextureType = NetworkTexture::Type; + // Helpers for the ctors QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture); model::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture, diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 287025c84a..191f911120 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -145,60 +145,62 @@ const gpu::TexturePointer& TextureCache::getNormalFittingTexture() { /// Extra data for creating textures. class TextureExtra { public: - TextureType type; + NetworkTexture::Type type; const QByteArray& content; }; -NetworkTexturePointer TextureCache::getTexture(const QUrl& url, TextureType type, const QByteArray& content) { +NetworkTexturePointer TextureCache::getTexture(const QUrl& url, Type type, const QByteArray& content) { TextureExtra extra = { type, content }; return ResourceCache::getResource(url, QUrl(), content.isEmpty(), &extra).staticCast(); } -NetworkTexture::TextureLoaderFunc getTextureLoaderForType(TextureType type) { +NetworkTexture::TextureLoaderFunc getTextureLoaderForType(NetworkTexture::Type type) { + using Type = NetworkTexture; + switch (type) { - case ALBEDO_TEXTURE: { + case Type::ALBEDO_TEXTURE: { return model::TextureUsage::createAlbedoTextureFromImage; break; } - case EMISSIVE_TEXTURE: { + case Type::EMISSIVE_TEXTURE: { return model::TextureUsage::createEmissiveTextureFromImage; break; } - case LIGHTMAP_TEXTURE: { + case Type::LIGHTMAP_TEXTURE: { return model::TextureUsage::createLightmapTextureFromImage; break; } - case CUBE_TEXTURE: { + case Type::CUBE_TEXTURE: { return model::TextureUsage::createCubeTextureFromImage; break; } - case BUMP_TEXTURE: { + case Type::BUMP_TEXTURE: { return model::TextureUsage::createNormalTextureFromBumpImage; break; } - case NORMAL_TEXTURE: { + case Type::NORMAL_TEXTURE: { return model::TextureUsage::createNormalTextureFromNormalImage; break; } - case ROUGHNESS_TEXTURE: { + case Type::ROUGHNESS_TEXTURE: { return model::TextureUsage::createRoughnessTextureFromImage; break; } - case GLOSS_TEXTURE: { + case Type::GLOSS_TEXTURE: { return model::TextureUsage::createRoughnessTextureFromGlossImage; break; } - case SPECULAR_TEXTURE: { + case Type::SPECULAR_TEXTURE: { return model::TextureUsage::createMetallicTextureFromImage; break; } - case CUSTOM_TEXTURE: { + case Type::CUSTOM_TEXTURE: { Q_ASSERT(false); return NetworkTexture::TextureLoaderFunc(); break; } - case DEFAULT_TEXTURE: + case Type::DEFAULT_TEXTURE: default: { return model::TextureUsage::create2DTextureFromImage; break; @@ -207,7 +209,7 @@ NetworkTexture::TextureLoaderFunc getTextureLoaderForType(TextureType type) { } /// Returns a texture version of an image file -gpu::TexturePointer TextureCache::getImageTexture(const QString& path, TextureType type) { +gpu::TexturePointer TextureCache::getImageTexture(const QString& path, Type type) { QImage image = QImage(path); auto loader = getTextureLoaderForType(type); return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString())); @@ -216,13 +218,13 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, TextureTy QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { const TextureExtra* textureExtra = static_cast(extra); - auto type = textureExtra ? textureExtra->type : TextureType::DEFAULT_TEXTURE; + auto type = textureExtra ? textureExtra->type : Type::DEFAULT_TEXTURE; auto content = textureExtra ? textureExtra->content : QByteArray(); return QSharedPointer(new NetworkTexture(url, type, content), &Resource::deleter); } -NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content) : +NetworkTexture::NetworkTexture(const QUrl& url, Type type, const QByteArray& content) : Resource(url, !content.isEmpty()), _type(type) { diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 948792ae70..ebdc2002c5 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -26,22 +26,6 @@ namespace gpu { class Batch; } -enum TextureType { - DEFAULT_TEXTURE, - ALBEDO_TEXTURE, - NORMAL_TEXTURE, - BUMP_TEXTURE, - SPECULAR_TEXTURE, - METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey - ROUGHNESS_TEXTURE, - GLOSS_TEXTURE, - EMISSIVE_TEXTURE, - CUBE_TEXTURE, - OCCLUSION_TEXTURE, - LIGHTMAP_TEXTURE, - CUSTOM_TEXTURE -}; - /// A simple object wrapper for an OpenGL texture. class Texture { public: @@ -55,11 +39,27 @@ class NetworkTexture : public Resource, public Texture { Q_OBJECT public: - + enum Type { + DEFAULT_TEXTURE, + ALBEDO_TEXTURE, + NORMAL_TEXTURE, + BUMP_TEXTURE, + SPECULAR_TEXTURE, + METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey + ROUGHNESS_TEXTURE, + GLOSS_TEXTURE, + EMISSIVE_TEXTURE, + CUBE_TEXTURE, + OCCLUSION_TEXTURE, + LIGHTMAP_TEXTURE, + CUSTOM_TEXTURE + }; + Q_ENUM(Type) + typedef gpu::Texture* TextureLoader(const QImage& image, const std::string& srcImageName); using TextureLoaderFunc = std::function; - - NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); + + NetworkTexture(const QUrl& url, Type type, const QByteArray& content); NetworkTexture(const QUrl& url, const TextureLoaderFunc& textureLoader, const QByteArray& content); int getOriginalWidth() const { return _originalWidth; } @@ -82,7 +82,7 @@ protected: Q_INVOKABLE void setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight); private: - TextureType _type; + Type _type; TextureLoaderFunc _textureLoader; int _originalWidth { 0 }; int _originalHeight { 0 }; @@ -96,6 +96,8 @@ using NetworkTexturePointer = QSharedPointer; class TextureCache : public ResourceCache, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + + using Type = NetworkTexture::Type; public: /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture @@ -119,10 +121,10 @@ public: const gpu::TexturePointer& getNormalFittingTexture(); /// Returns a texture version of an image file - static gpu::TexturePointer getImageTexture(const QString& path, TextureType type = DEFAULT_TEXTURE); + static gpu::TexturePointer getImageTexture(const QString& path, Type type = Type::DEFAULT_TEXTURE); /// Loads a texture from the specified URL. - NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, + NetworkTexturePointer getTexture(const QUrl& url, Type type = Type::DEFAULT_TEXTURE, const QByteArray& content = QByteArray()); protected: From df4857947b154cadc1523f4aec5f298db352a00e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 17:16:59 -0700 Subject: [PATCH 09/26] Add prefetch overload for texture specificity --- .../src/model-networking/TextureCache.cpp | 12 ++++++++++++ .../src/model-networking/TextureCache.h | 7 +++++-- libraries/networking/src/ResourceCache.cpp | 4 ++-- libraries/networking/src/ResourceCache.h | 4 +++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 191f911120..af84ecd0f3 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -35,6 +35,12 @@ TextureCache::TextureCache() { const qint64 TEXTURE_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; setUnusedResourceCacheSize(TEXTURE_DEFAULT_UNUSED_MAX_SIZE); setObjectName("TextureCache"); + + // Expose enum Type to JS/QML via properties + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + setProperty(metaEnum.key(i), metaEnum.value(i)); + } } TextureCache::~TextureCache() { @@ -149,6 +155,12 @@ public: const QByteArray& content; }; +ScriptableResource* TextureCache::prefetch(const QUrl& url, int type) { + auto byteArray = QByteArray(); + TextureExtra extra = { (Type)type, byteArray }; + return ResourceCache::prefetch(url, &extra); +} + NetworkTexturePointer TextureCache::getTexture(const QUrl& url, Type type, const QByteArray& content) { TextureExtra extra = { type, content }; return ResourceCache::getResource(url, QUrl(), content.isEmpty(), &extra).staticCast(); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index ebdc2002c5..fced2b3c5c 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -34,7 +35,6 @@ public: }; /// A texture loaded from the network. - class NetworkTexture : public Resource, public Texture { Q_OBJECT @@ -98,8 +98,11 @@ class TextureCache : public ResourceCache, public Dependency { SINGLETON_DEPENDENCY using Type = NetworkTexture::Type; - + public: + // Overload ResourceCache::prefetch to allow specifying texture type for loads + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type); + /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and /// the second, a set of random unit vectors to be used as noise gradients. diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 7feeff114e..f6b79e3acb 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -140,7 +140,7 @@ void ScriptableResource::finished(bool success) { emit loadedChanged(_isLoaded); } -ScriptableResource* ResourceCache::prefetch(const QUrl& url) { +ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { auto result = new ScriptableResource(); if (QThread::currentThread() != thread()) { @@ -151,7 +151,7 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url) { } - auto resource = getResource(url); + auto resource = getResource(url, QUrl(), false, extra); result->_resource = resource; result->setObjectName(url.toString()); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 3cd749ebc9..d678196ea3 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -131,7 +131,7 @@ public: Q_INVOKABLE QVariantList getResourceList(); // This must be exposed as a ptr so the ScriptEngine may take ownership - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url); + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } static void setRequestLimit(int limit); static int getRequestLimit() { return _requestLimit; } @@ -164,6 +164,8 @@ private slots: void clearATPAssets(); protected: + ScriptableResource* prefetch(const QUrl& url, void* extra); + /// Loads a resource from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested From 825431829deb932f457d6aa57b0bb8f8dd6f4071 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 17:22:15 -0700 Subject: [PATCH 10/26] Add url property to ScriptableResource --- libraries/networking/src/ResourceCache.cpp | 6 +++--- libraries/networking/src/ResourceCache.h | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index f6b79e3acb..4a80628da4 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -119,9 +119,9 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { return highestResource; } -ScriptableResource::ScriptableResource(const QSharedPointer& resource) : +ScriptableResource::ScriptableResource(const QUrl& url) : QObject(nullptr), - _resource(resource) {} + _url(url) {} void ScriptableResource::finished(bool success) { if (_progressConnection) { @@ -141,7 +141,7 @@ void ScriptableResource::finished(bool success) { } ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { - auto result = new ScriptableResource(); + auto result = new ScriptableResource(url); if (QThread::currentThread() != thread()) { // Must be called in thread to ensure getResource returns a valid pointer diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index d678196ea3..35a0710aa6 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -81,12 +81,15 @@ private: /// Wrapper to expose resources to JS/QML class ScriptableResource : public QObject { Q_OBJECT + Q_PROPERTY(QUrl url READ getUrl) Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged) Q_PROPERTY(bool failed READ isFailed NOTIFY failedChanged) + public: - ScriptableResource(const QSharedPointer& resource = QSharedPointer()); + ScriptableResource(const QUrl& url); virtual ~ScriptableResource() = default; + const QUrl& getUrl() const { return _url; } bool isLoaded() const { return _isLoaded; } bool isFailed() const { return _isFailed; } @@ -107,6 +110,7 @@ private: QMetaObject::Connection _progressConnection; QMetaObject::Connection _finishedConnection; + QUrl _url; bool _isLoaded{ false }; bool _isFailed{ false }; }; From 5ec82fa43d89fcf32c99fd75390013802d804fab Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 17:27:00 -0700 Subject: [PATCH 11/26] Add release invokable to ScriptableResource --- libraries/networking/src/ResourceCache.cpp | 21 +++++++++++++++------ libraries/networking/src/ResourceCache.h | 4 ++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 4a80628da4..d1dabd5693 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -123,13 +123,13 @@ ScriptableResource::ScriptableResource(const QUrl& url) : QObject(nullptr), _url(url) {} +void ScriptableResource::release() { + disconnectHelper(); + _resource.reset(); +} + void ScriptableResource::finished(bool success) { - if (_progressConnection) { - disconnect(_progressConnection); - } - if (_finishedConnection) { - disconnect(_finishedConnection); - } + disconnectHelper(); _isLoaded = true; _isFailed = !success; @@ -140,6 +140,15 @@ void ScriptableResource::finished(bool success) { emit loadedChanged(_isLoaded); } +void ScriptableResource::disconnectHelper() { + if (_progressConnection) { + disconnect(_progressConnection); + } + if (_finishedConnection) { + disconnect(_finishedConnection); + } +} + ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { auto result = new ScriptableResource(url); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 35a0710aa6..997c9be37d 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -89,6 +89,8 @@ public: ScriptableResource(const QUrl& url); virtual ~ScriptableResource() = default; + Q_INVOKABLE void release(); + const QUrl& getUrl() const { return _url; } bool isLoaded() const { return _isLoaded; } bool isFailed() const { return _isFailed; } @@ -102,6 +104,8 @@ private slots: void finished(bool success); private: + void disconnectHelper(); + friend class ResourceCache; // Holds a ref to the resource to keep it in scope From 1a0a623d5f27a4ba12d2e574560e3b6e96d131f5 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 18:08:16 -0700 Subject: [PATCH 12/26] Report ScriptableResource memory cost to engine --- libraries/networking/src/ResourceCache.cpp | 14 ++++++++--- libraries/networking/src/ResourceCache.h | 26 ++++++++++++++------ libraries/script-engine/src/ScriptEngine.cpp | 7 ++++++ libraries/script-engine/src/ScriptEngine.h | 1 + 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index d1dabd5693..eb69f75279 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -128,6 +128,13 @@ void ScriptableResource::release() { _resource.reset(); } +void ScriptableResource::updateMemoryCost(const QObject* engine) { + if (_resource && !_resource->isInScript()) { + _resource->setInScript(true); + connect(_resource.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64))); + } +} + void ScriptableResource::finished(bool success) { disconnectHelper(); @@ -329,6 +336,7 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& getResource(fallback, QUrl(), true) : QSharedPointer(), delayLoad, extra); resource->setSelf(resource); resource->setCache(this); + connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); { QWriteLocker locker(&_resourcesLock); _resources.insert(url, resource); @@ -414,8 +422,8 @@ void ResourceCache::removeResource(const QUrl& url, qint64 size) { _totalResourcesSize -= size; } -void ResourceCache::updateTotalSize(const qint64& oldSize, const qint64& newSize) { - _totalResourcesSize += (newSize - oldSize); +void ResourceCache::updateTotalSize(const qint64& deltaSize) { + _totalResourcesSize += deltaSize; emit dirty(); } @@ -592,7 +600,7 @@ void Resource::finishedLoading(bool success) { } void Resource::setSize(const qint64& bytes) { - QMetaObject::invokeMethod(_cache.data(), "updateTotalSize", Q_ARG(qint64, _bytes), Q_ARG(qint64, bytes)); + emit updateSize(bytes - _bytes); _bytes = bytes; } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 997c9be37d..5b76cb48cd 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -95,6 +95,9 @@ public: bool isLoaded() const { return _isLoaded; } bool isFailed() const { return _isFailed; } + // Connects to a SLOT(updateMemoryCost(qint64) on the given engine + void updateMemoryCost(const QObject* engine); + signals: void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); void loadedChanged(bool loaded); // analogous to &Resource::finished @@ -166,7 +169,7 @@ public slots: void checkAsynchronousGets(); protected slots: - void updateTotalSize(const qint64& oldSize, const qint64& newSize); + void updateTotalSize(const qint64& deltaSize); private slots: void clearATPAssets(); @@ -291,6 +294,9 @@ signals: /// Fired when the resource is refreshed. void onRefresh(); + /// Fired when the size changes (through setSize). + void updateSize(qint64 deltaSize); + protected slots: void attemptRequest(); @@ -332,17 +338,21 @@ private: void makeRequest(); void retry(); void reinsert(); + + bool isInScript() const { return _isInScript; } + void setInScript(bool isInScript) { _isInScript = isInScript; } friend class ResourceCache; friend class ScriptableResource; - ResourceRequest* _request = nullptr; - int _lruKey = 0; - QTimer* _replyTimer = nullptr; - qint64 _bytesReceived = 0; - qint64 _bytesTotal = 0; - qint64 _bytes = 0; - int _attempts = 0; + ResourceRequest* _request{ nullptr }; + int _lruKey{ 0 }; + QTimer* _replyTimer{ nullptr }; + qint64 _bytesReceived{ 0 }; + qint64 _bytesTotal{ 0 }; + qint64 _bytes{ 0 }; + int _attempts{ 0 }; + bool _isInScript{ false }; }; uint qHash(const QPointer& value, uint seed = 0); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 4c6a988f36..29d592eec2 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -274,6 +274,7 @@ static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantR using ScriptableResourceRawPtr = ScriptableResource*; static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, const ScriptableResourceRawPtr& resource) { + resource->updateMemoryCost(engine); auto object = engine->newQObject( const_cast(resource), QScriptEngine::ScriptOwnership); @@ -809,6 +810,12 @@ void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantM } } +void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { + if (deltaSize > 0) { + reportAdditionalMemoryCost(deltaSize); + } +} + void ScriptEngine::timerFired() { QTimer* callingTimer = reinterpret_cast(sender()); CallbackData timerData = _timerFunctionMap.value(callingTimer); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 605f30ed6e..175a3f1f1c 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -158,6 +158,7 @@ public: public slots: void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); + void updateMemoryCost(const qint64&); signals: void scriptLoaded(const QString& scriptFilename); From 92e372f385b202837e0f3ea0d4306ba78aa0ac57 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 18:18:35 -0700 Subject: [PATCH 13/26] Expose Resource progress as a signal --- libraries/networking/src/ResourceCache.cpp | 6 ++++-- libraries/networking/src/ResourceCache.h | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index eb69f75279..9d99f7c636 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -176,7 +176,7 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { result->finished(!resource->_failedToLoad); } else { result->_progressConnection = connect( - resource.data(), &Resource::handleDownloadProgress, + resource.data(), &Resource::onProgress, result, &ScriptableResource::progressChanged); result->_finishedConnection = connect( resource.data(), &Resource::finished, @@ -627,7 +627,9 @@ void Resource::makeRequest() { qCDebug(networking).noquote() << "Starting request for:" << _url.toDisplayString(); - connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress); + connect(_request, &ResourceRequest::progress, this, &Resource::onProgress); + connect(this, &Resource::onProgress, this, &Resource::handleDownloadProgress); + connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished); _bytesReceived = _bytesTotal = _bytes = 0; diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 5b76cb48cd..fbbf64e837 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -294,6 +294,9 @@ signals: /// 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); From 713fc44ec5738789dbca9973ddc539b44ce0de82 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 20 Apr 2016 19:58:26 -0700 Subject: [PATCH 14/26] Add scriptableResourceTest.js --- examples/tests/scriptableResourceTest.js | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 examples/tests/scriptableResourceTest.js diff --git a/examples/tests/scriptableResourceTest.js b/examples/tests/scriptableResourceTest.js new file mode 100644 index 0000000000..48a03d9975 --- /dev/null +++ b/examples/tests/scriptableResourceTest.js @@ -0,0 +1,73 @@ +// +// scriptableResourceTest.js +// examples/tests +// +// Created by Zach Pomerantz on 4/20/16. +// Copyright 2016 High Fidelity, Inc. +// +// Preloads textures to play a simple movie, plays it, and frees those textures. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// A model exported from blender with a texture named 'Picture' on one face. +var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/finalFrame.fbx"; +// A folder full of individual frames. +var MOVIE_URL = "http://hifi-content.s3.amazonaws.com/james/vidtest/"; + +var center = Vec3.sum( + Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), + Vec3.multiply(1, Quat.getFront(Camera.getOrientation())) +); + +// Left-pad num with 0s until it is size digits +function pad(num, size) { + var s = num + ""; + while (s.length < size) s = "0" + s; + return s; +} + +var pictureFrameProperties = { + name: 'scriptableResourceTest Picture Frame', + type: 'Model', + position: center, + modelURL: FRAME_URL, + dynamic: true, +} +var pictureFrame = Entities.addEntity(pictureFrameProperties); + +var frames = []; + +// Preload +var numLoading = 0; +for (var i = 0; i < 159; i++) { + var padded = pad(i, 3); + var filepath = MOVIE_URL + padded + '.jpg'; + var texture = TextureCache.prefetch(filepath); + frames.push(texture); + if (!texture.loaded) { + numLoading++; + texture.loadedChanged.connect(function() { + numLoading--; + if (!numLoading) play(); + }); + } +} + +function play() { + var frame = 0; + var movieInterval = Script.setInterval(function() { + Entities.editEntity(pictureFrame, { textures: JSON.stringify({ Picture: frames[frame].url }) }) + frame += 1; + if (frame == 159) { + Script.clearInterval(movieInterval); + Entities.deleteEntity(pictureFrame); + // free the textures at the next garbage collection + while (frames.length) frames.pop(); + // alternatively, the textures can be forcibly freed: + // frames.forEach(function(texture) { texture.release(); }); + Script.requestGarbageCollection(); + } + }, 33.3); +} From cba5bff89ef5403afb38a257895560abdfb82dde Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 21 Apr 2016 10:33:35 -0700 Subject: [PATCH 15/26] Make ResourceCache::prefetch private and comment --- .../src/model-networking/TextureCache.h | 5 ++--- libraries/networking/src/ResourceCache.h | 13 ++++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index fced2b3c5c..82f9edde8f 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -100,9 +100,6 @@ class TextureCache : public ResourceCache, public Dependency { using Type = NetworkTexture::Type; public: - // Overload ResourceCache::prefetch to allow specifying texture type for loads - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type); - /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and /// the second, a set of random unit vectors to be used as noise gradients. @@ -131,6 +128,8 @@ public: const QByteArray& content = QByteArray()); protected: + // Overload ResourceCache::prefetch to allow specifying texture type for loads + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type); virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index fbbf64e837..d5360fc5b3 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -141,9 +141,6 @@ public: Q_INVOKABLE QVariantList getResourceList(); - // This must be exposed as a ptr so the ScriptEngine may take ownership - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } - static void setRequestLimit(int limit); static int getRequestLimit() { return _requestLimit; } @@ -175,6 +172,16 @@ private slots: void clearATPAssets(); protected: + // Prefetches a resource to be held by the QScriptEngine. + // Pointers created through this method should be owned by the caller, + // which should be a QScriptEngine with ScriptableResource registered, so that + // the QScriptEngine will delete the pointer when it is garbage collected. + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } + + + // Prefetches a resource to be held by the QScriptEngine. + // 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); /// Loads a resource from the specified URL. From 2fd88d9fe1414b0825413a34ff560bae0ed59d42 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 21 Apr 2016 10:41:04 -0700 Subject: [PATCH 16/26] Add sanity check to ResourceCache::updateTotalSize --- libraries/networking/src/ResourceCache.cpp | 5 +++++ libraries/networking/src/ResourceCache.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 9d99f7c636..83fcc90a4d 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -424,6 +424,11 @@ void ResourceCache::removeResource(const QUrl& url, qint64 size) { void ResourceCache::updateTotalSize(const qint64& deltaSize) { _totalResourcesSize += deltaSize; + + // Sanity checks + assert(_totalResourcesSize >= 0); + assert(_totalResourcesSize < (1024 * BYTES_PER_GIGABYTES)); + emit dirty(); } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index d5360fc5b3..1461094cfb 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -50,7 +50,7 @@ static const qint64 DEFAULT_UNUSED_MAX_SIZE = 100 * BYTES_PER_MEGABYTES; 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 = 10 * BYTES_PER_GIGABYTES; +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 From 6e12493ed73abdb56b05968afda0b3efeba9623e Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 21 Apr 2016 10:54:03 -0700 Subject: [PATCH 17/26] Add default NetworkTexture::_textureLoader --- libraries/model-networking/src/model-networking/TextureCache.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 82f9edde8f..8fd0b12369 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -83,7 +83,7 @@ protected: private: Type _type; - TextureLoaderFunc _textureLoader; + TextureLoaderFunc _textureLoader { [](const QImage&, const std::string&){ return nullptr; } }; int _originalWidth { 0 }; int _originalHeight { 0 }; int _width { 0 }; From 4df417e7568ed7a744435df2fcb63f6680a365fe Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 21 Apr 2016 11:47:48 -0700 Subject: [PATCH 18/26] Add CONSTs to scriptableResourceTest --- examples/tests/scriptableResourceTest.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/tests/scriptableResourceTest.js b/examples/tests/scriptableResourceTest.js index 48a03d9975..ad338f3b6b 100644 --- a/examples/tests/scriptableResourceTest.js +++ b/examples/tests/scriptableResourceTest.js @@ -16,6 +16,9 @@ var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/ // A folder full of individual frames. var MOVIE_URL = "http://hifi-content.s3.amazonaws.com/james/vidtest/"; +var NUM_FRAMES = 158; // 158 available +var FRAME_RATE = 30; // 30 default + var center = Vec3.sum( Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Vec3.multiply(1, Quat.getFront(Camera.getOrientation())) @@ -41,7 +44,7 @@ var frames = []; // Preload var numLoading = 0; -for (var i = 0; i < 159; i++) { +for (var i = 1; i <= NUM_FRAMES + 1; i++) { var padded = pad(i, 3); var filepath = MOVIE_URL + padded + '.jpg'; var texture = TextureCache.prefetch(filepath); @@ -60,7 +63,7 @@ function play() { var movieInterval = Script.setInterval(function() { Entities.editEntity(pictureFrame, { textures: JSON.stringify({ Picture: frames[frame].url }) }) frame += 1; - if (frame == 159) { + if (frame > NUM_FRAMES) { Script.clearInterval(movieInterval); Entities.deleteEntity(pictureFrame); // free the textures at the next garbage collection @@ -69,5 +72,5 @@ function play() { // frames.forEach(function(texture) { texture.release(); }); Script.requestGarbageCollection(); } - }, 33.3); + }, 1000 / FRAME_RATE); } From 069065dc23a7e66f32aa896dc22b3a165e69c4b5 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Thu, 21 Apr 2016 17:15:11 -0700 Subject: [PATCH 19/26] Fix recursive invokes of ResourceCache::prefetch --- libraries/networking/src/ResourceCache.cpp | 3 ++- libraries/networking/src/ResourceCache.h | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 83fcc90a4d..2bd85ce5d2 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -162,7 +162,8 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { 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)); + Q_RETURN_ARG(ScriptableResource*, result), + Q_ARG(QUrl, url), Q_ARG(void*, extra)); return result; } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 1461094cfb..7d9e0f65fd 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -168,6 +168,11 @@ public slots: protected slots: void updateTotalSize(const qint64& deltaSize); + // Prefetches a resource to be held by the QScriptEngine. + // 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); + private slots: void clearATPAssets(); @@ -178,12 +183,6 @@ protected: // the QScriptEngine will delete the pointer when it is garbage collected. Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } - - // Prefetches a resource to be held by the QScriptEngine. - // 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); - /// Loads a resource from the specified URL. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested From 6b53c1e85d13e5cd6e80412ec859bc6600d38781 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 27 Apr 2016 11:55:02 -0700 Subject: [PATCH 20/26] Avoid leaking ScriptableResource --- libraries/networking/src/ResourceCache.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 2bd85ce5d2..48bc1dfc0c 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -157,7 +157,7 @@ void ScriptableResource::disconnectHelper() { } ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { - auto result = new ScriptableResource(url); + ScriptableResource* result = nullptr; if (QThread::currentThread() != thread()) { // Must be called in thread to ensure getResource returns a valid pointer @@ -167,6 +167,7 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { return result; } + result = new ScriptableResource(url); auto resource = getResource(url, QUrl(), false, extra); result->_resource = resource; From c2909946e6e9bd01db372458264e0c56029f1771 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 27 Apr 2016 13:59:27 -0700 Subject: [PATCH 21/26] Split test to prefetch, movie --- examples/tests/scriptableResourceTest.js | 76 --------------- .../developer/tests/scriptableResource/lib.js | 96 +++++++++++++++++++ .../tests/scriptableResource/movieTest.js | 42 ++++++++ .../tests/scriptableResource/prefetchTest.js | 33 +++++++ 4 files changed, 171 insertions(+), 76 deletions(-) delete mode 100644 examples/tests/scriptableResourceTest.js create mode 100644 scripts/developer/tests/scriptableResource/lib.js create mode 100644 scripts/developer/tests/scriptableResource/movieTest.js create mode 100644 scripts/developer/tests/scriptableResource/prefetchTest.js diff --git a/examples/tests/scriptableResourceTest.js b/examples/tests/scriptableResourceTest.js deleted file mode 100644 index ad338f3b6b..0000000000 --- a/examples/tests/scriptableResourceTest.js +++ /dev/null @@ -1,76 +0,0 @@ -// -// scriptableResourceTest.js -// examples/tests -// -// Created by Zach Pomerantz on 4/20/16. -// Copyright 2016 High Fidelity, Inc. -// -// Preloads textures to play a simple movie, plays it, and frees those textures. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -// A model exported from blender with a texture named 'Picture' on one face. -var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/finalFrame.fbx"; -// A folder full of individual frames. -var MOVIE_URL = "http://hifi-content.s3.amazonaws.com/james/vidtest/"; - -var NUM_FRAMES = 158; // 158 available -var FRAME_RATE = 30; // 30 default - -var center = Vec3.sum( - Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), - Vec3.multiply(1, Quat.getFront(Camera.getOrientation())) -); - -// Left-pad num with 0s until it is size digits -function pad(num, size) { - var s = num + ""; - while (s.length < size) s = "0" + s; - return s; -} - -var pictureFrameProperties = { - name: 'scriptableResourceTest Picture Frame', - type: 'Model', - position: center, - modelURL: FRAME_URL, - dynamic: true, -} -var pictureFrame = Entities.addEntity(pictureFrameProperties); - -var frames = []; - -// Preload -var numLoading = 0; -for (var i = 1; i <= NUM_FRAMES + 1; i++) { - var padded = pad(i, 3); - var filepath = MOVIE_URL + padded + '.jpg'; - var texture = TextureCache.prefetch(filepath); - frames.push(texture); - if (!texture.loaded) { - numLoading++; - texture.loadedChanged.connect(function() { - numLoading--; - if (!numLoading) play(); - }); - } -} - -function play() { - var frame = 0; - var movieInterval = Script.setInterval(function() { - Entities.editEntity(pictureFrame, { textures: JSON.stringify({ Picture: frames[frame].url }) }) - frame += 1; - if (frame > NUM_FRAMES) { - Script.clearInterval(movieInterval); - Entities.deleteEntity(pictureFrame); - // free the textures at the next garbage collection - while (frames.length) frames.pop(); - // alternatively, the textures can be forcibly freed: - // frames.forEach(function(texture) { texture.release(); }); - Script.requestGarbageCollection(); - } - }, 1000 / FRAME_RATE); -} diff --git a/scripts/developer/tests/scriptableResource/lib.js b/scripts/developer/tests/scriptableResource/lib.js new file mode 100644 index 0000000000..ed3d746061 --- /dev/null +++ b/scripts/developer/tests/scriptableResource/lib.js @@ -0,0 +1,96 @@ +// +// lib.js +// scripts/developer/tests/scriptableResource +// +// Created by Zach Pomerantz on 4/20/16. +// Copyright 2016 High Fidelity, Inc. +// +// Preloads textures to play a simple movie, plays it, and frees those textures. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var NUM_FRAMES = 158; // 158 available +var FRAME_RATE = 30; // 30 default + +function getFrame(callback) { + // A model exported from blender with a texture named 'Picture' on one face. + var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/finalFrame.fbx"; + + var model = ModelCache.prefetch(FRAME_URL); + if (model.loaded) { + makeFrame(true); + } else { + model.loadedChanged.connect(makeFrame); + } + + function makeFrame(success) { + if (!success) { throw "Failed to load frame"; } + + var pictureFrameProperties = { + name: 'scriptableResourceTest Picture Frame', + type: 'Model', + position: getPosition(), + modelURL: FRAME_URL, + dynamic: true, + }; + + callback(Entities.addEntity(pictureFrameProperties)); + } + + function getPosition() { + // Always put it 5 meters in front of you + var position = MyAvatar.position; + var yaw = MyAvatar.bodyYaw + MyAvatar.getHeadFinalYaw(); + var rads = (yaw / 180) * Math.PI; + + position.y += 0.5; + position.x += - 5 * Math.sin(rads); + position.z += - 5 * Math.cos(rads); + + print(JSON.stringify(position)); + return position; + } +} + +function prefetch(callback) { + // A folder full of individual frames. + var MOVIE_URL = "http://hifi-content.s3.amazonaws.com/james/vidtest/"; + + var frames = []; + + var numLoading = 0; + for (var i = 1; i <= NUM_FRAMES; ++i) { + var padded = pad(i, 3); + var filepath = MOVIE_URL + padded + '.jpg'; + var texture = TextureCache.prefetch(filepath); + frames.push(texture); + if (!texture.loaded) { + numLoading++; + texture.loadedChanged.connect(function() { + --numLoading; + if (!numLoading) { callback(frames); } + }); + } + } + if (!numLoading) { callback(frames); } + + function pad(num, size) { // left-pad num with zeros until it is size digits + var s = num.toString(); + while (s.length < size) { s = "0" + s; } + return s; + } +} + +function play(model, frames, callback) { + var frame = 0; + var movieInterval = Script.setInterval(function() { + Entities.editEntity(model, { textures: JSON.stringify({ Picture: frames[frame].url }) }); + if (++frame >= frames.length) { + Script.clearInterval(movieInterval); + callback(); + } + }, 1000 / FRAME_RATE); +} + diff --git a/scripts/developer/tests/scriptableResource/movieTest.js b/scripts/developer/tests/scriptableResource/movieTest.js new file mode 100644 index 0000000000..61b2bf7942 --- /dev/null +++ b/scripts/developer/tests/scriptableResource/movieTest.js @@ -0,0 +1,42 @@ +// +// testMovie.js +// scripts/developer/tests/scriptableResource +// +// Created by Zach Pomerantz on 4/27/16. +// Copyright 2016 High Fidelity, Inc. +// +// Preloads textures, plays them on a frame model, and unloads them. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var entity; + +Script.include([ + '../../../developer/utilities/cache/cacheStats.js', + 'lib.js', +], function() { + getFrame(function(frame) { + entity = frame; + prefetch(function(frames) { + play(frame, frames, function() { + // Delete each texture, so the next garbage collection cycle will release them. + + // Setting frames = null breaks the reference, + // but will not delete frames from the calling scope. + // Instead, we must mutate it in-place to free its elements for GC + // (assuming the elements are not held elsewhere). + while (frames.length) { frames.pop(); } + + // Alternatively, forcibly release each texture without relying on GC. + // frames.forEach(function(texture) { texture.release(); }); + + Entities.deleteEntity(entity); + Script.requestGarbageCollection(); + }); + }); + }); +}); + +Script.scriptEnding.connect(function() { entity && Entities.deleteEntity(entity); }); diff --git a/scripts/developer/tests/scriptableResource/prefetchTest.js b/scripts/developer/tests/scriptableResource/prefetchTest.js new file mode 100644 index 0000000000..cda805967e --- /dev/null +++ b/scripts/developer/tests/scriptableResource/prefetchTest.js @@ -0,0 +1,33 @@ +// +// testPrefetch.js +// scripts/developer/tests/scriptableResource +// +// Created by Zach Pomerantz on 4/27/16. +// Copyright 2016 High Fidelity, Inc. +// +// Preloads textures and unloads them. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include([ + '../../../developer/utilities/cache/cacheStats.js', + 'lib.js', +], function() { + prefetch(function(frames) { + // Delete each texture, so the next garbage collection cycle will release them. + + // Setting frames = null breaks the reference, + // but will not delete frames from the calling scope. + // Instead, we must mutate it in-place to free its elements for GC + // (assuming the elements are not held elsewhere). + while (frames.length) { frames.pop(); } + + // Alternatively, forcibly release each texture without relying on GC. + // frames.forEach(function(texture) { texture.release(); }); + + Script.requestGarbageCollection(); + }); +}); + From de8145ec7122dacace4c691a4673ec1213407db7 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 27 Apr 2016 16:47:18 -0700 Subject: [PATCH 22/26] Nest TextureType JS interface in Type --- .../model-networking/src/model-networking/TextureCache.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index af84ecd0f3..2aaddace88 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -37,9 +37,13 @@ TextureCache::TextureCache() { setObjectName("TextureCache"); // Expose enum Type to JS/QML via properties + // Despite being one-off, this should be fine, because TextureCache is a SINGLETON_DEPENDENCY + QObject* type = new QObject(this); + type->setObjectName("TextureType"); + setProperty("Type", QVariant::fromValue(type)); auto metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { - setProperty(metaEnum.key(i), metaEnum.value(i)); + type->setProperty(metaEnum.key(i), metaEnum.value(i)); } } From 0c78d5bdd19044a4b353e9939d9fd4d3c0d7fe16 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 27 Apr 2016 17:12:03 -0700 Subject: [PATCH 23/26] Change ScriptableResource signal to state --- libraries/networking/src/ResourceCache.cpp | 41 +++++++++++++++---- libraries/networking/src/ResourceCache.h | 30 ++++++++++---- .../developer/tests/scriptableResource/lib.js | 15 ++++--- 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 3e7e8dd345..aa42f94e8b 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -121,7 +121,17 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { ScriptableResource::ScriptableResource(const QUrl& url) : QObject(nullptr), - _url(url) {} + _url(url) { + + // Expose enum State to JS/QML via properties + QObject* state = new QObject(this); + state->setObjectName("ResourceState"); + setProperty("State", QVariant::fromValue(state)); + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + state->setProperty(metaEnum.key(i), metaEnum.value(i)); + } +} void ScriptableResource::release() { disconnectHelper(); @@ -135,22 +145,30 @@ void ScriptableResource::updateMemoryCost(const QObject* engine) { } } +void ScriptableResource::loadingChanged() { + emit stateChanged(LOADING); +} + +void ScriptableResource::loadedChanged() { + emit stateChanged(LOADED); +} + void ScriptableResource::finished(bool success) { disconnectHelper(); - _isLoaded = true; - _isFailed = !success; - - if (_isFailed) { - emit failedChanged(_isFailed); - } - emit loadedChanged(_isLoaded); + emit stateChanged(success ? FINISHED : FAILED); } void ScriptableResource::disconnectHelper() { if (_progressConnection) { disconnect(_progressConnection); } + if (_loadingConnection) { + disconnect(_loadingConnection); + } + if (_loadedConnection) { + disconnect(_loadedConnection); + } if (_finishedConnection) { disconnect(_finishedConnection); } @@ -180,6 +198,12 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra) { 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); @@ -644,6 +668,7 @@ void Resource::makeRequest() { } qCDebug(networking).noquote() << "Starting request for:" << _url.toDisplayString(); + emit loading(); connect(_request, &ResourceRequest::progress, this, &Resource::onProgress); connect(this, &Resource::onProgress, this, &Resource::handleDownloadProgress); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 7d9e0f65fd..904da4f097 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -82,28 +82,36 @@ private: class ScriptableResource : public QObject { Q_OBJECT Q_PROPERTY(QUrl url READ getUrl) - Q_PROPERTY(bool loaded READ isLoaded NOTIFY loadedChanged) - Q_PROPERTY(bool failed READ isFailed NOTIFY failedChanged) + Q_PROPERTY(int state READ getState NOTIFY stateChanged) public: + enum State { + QUEUED, + LOADING, + LOADED, + FINISHED, + FAILED, + }; + Q_ENUM(State) + ScriptableResource(const QUrl& url); virtual ~ScriptableResource() = default; Q_INVOKABLE void release(); const QUrl& getUrl() const { return _url; } - bool isLoaded() const { return _isLoaded; } - bool isFailed() const { return _isFailed; } + int getState() const { return (int)_state; } - // Connects to a SLOT(updateMemoryCost(qint64) on the given engine + // Connects to a SLOT(updateMemoryCost(qint64)) on the given engine void updateMemoryCost(const QObject* engine); signals: void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); - void loadedChanged(bool loaded); // analogous to &Resource::finished - void failedChanged(bool failed); + void stateChanged(int state); private slots: + void loadingChanged(); + void loadedChanged(); void finished(bool success); private: @@ -115,11 +123,12 @@ private: QSharedPointer _resource; QMetaObject::Connection _progressConnection; + QMetaObject::Connection _loadingConnection; + QMetaObject::Connection _loadedConnection; QMetaObject::Connection _finishedConnection; QUrl _url; - bool _isLoaded{ false }; - bool _isFailed{ false }; + State _state{ QUEUED }; }; Q_DECLARE_METATYPE(ScriptableResource*); @@ -287,6 +296,9 @@ public: const QUrl& getURL() const { return _url; } 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); diff --git a/scripts/developer/tests/scriptableResource/lib.js b/scripts/developer/tests/scriptableResource/lib.js index ed3d746061..21e2e64cae 100644 --- a/scripts/developer/tests/scriptableResource/lib.js +++ b/scripts/developer/tests/scriptableResource/lib.js @@ -22,11 +22,12 @@ function getFrame(callback) { if (model.loaded) { makeFrame(true); } else { - model.loadedChanged.connect(makeFrame); + model.stateChanged.connect(makeFrame); } - function makeFrame(success) { - if (!success) { throw "Failed to load frame"; } + function makeFrame(state) { + if (state == 4) { throw "Failed to load frame"; } + if (state != 3) { return; } var pictureFrameProperties = { name: 'scriptableResourceTest Picture Frame', @@ -68,9 +69,11 @@ function prefetch(callback) { frames.push(texture); if (!texture.loaded) { numLoading++; - texture.loadedChanged.connect(function() { - --numLoading; - if (!numLoading) { callback(frames); } + texture.stateChanged.connect(function(state) { + if (state == 3 || state == 4) { + --numLoading; + if (!numLoading) { callback(frames); } + } }); } } From 1e8d45aecb1c7d9056461ed93054eb611d127f3a Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 27 Apr 2016 18:27:41 -0700 Subject: [PATCH 24/26] Put ScriptableResource state enum in prototype --- libraries/networking/src/ResourceCache.cpp | 12 +--------- libraries/networking/src/ResourceCache.h | 3 +++ libraries/script-engine/src/ScriptEngine.cpp | 22 ++++++++++++++++++- .../developer/tests/scriptableResource/lib.js | 12 +++++----- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index aa42f94e8b..32b1c03766 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -121,17 +121,7 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { ScriptableResource::ScriptableResource(const QUrl& url) : QObject(nullptr), - _url(url) { - - // Expose enum State to JS/QML via properties - QObject* state = new QObject(this); - state->setObjectName("ResourceState"); - setProperty("State", QVariant::fromValue(state)); - auto metaEnum = QMetaEnum::fromType(); - for (int i = 0; i < metaEnum.keyCount(); ++i) { - state->setProperty(metaEnum.key(i), metaEnum.value(i)); - } -} + _url(url) { } void ScriptableResource::release() { disconnectHelper(); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 904da4f097..98473a58c3 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -24,9 +24,12 @@ #include #include #include + #include #include +#include + #include #include "ResourceManager.h" diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 29d592eec2..1cbd2a6127 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -285,6 +285,23 @@ static void scriptableResourceFromScriptValue(const QScriptValue& value, Scripta resource = static_cast(value.toQObject()); } +static QScriptValue createScriptableResourcePrototype(QScriptEngine* engine) { + auto prototype = engine->newObject(); + + // Expose enum State to JS/QML via properties + QObject* state = new QObject(engine); + state->setObjectName("ResourceState"); + auto metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); ++i) { + state->setProperty(metaEnum.key(i), metaEnum.value(i)); + } + + auto prototypeState = engine->newQObject(state, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSlots | QScriptEngine::ExcludeSuperClassMethods); + prototype.setProperty("State", prototypeState); + + return prototype; +} + void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once @@ -342,11 +359,14 @@ void ScriptEngine::init() { registerGlobalObject("Vec3", &_vec3Library); registerGlobalObject("Mat4", &_mat4Library); registerGlobalObject("Uuid", &_uuidLibrary); - registerGlobalObject("AnimationCache", DependencyManager::get().data()); registerGlobalObject("Messages", DependencyManager::get().data()); qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue); qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue); + // Scriptable cache access + auto resourcePrototype = createScriptableResourcePrototype(this); + globalObject().setProperty("Resource", resourcePrototype); + setDefaultPrototype(qMetaTypeId(), resourcePrototype); qScriptRegisterMetaType(this, scriptableResourceToScriptValue, scriptableResourceFromScriptValue); // constants diff --git a/scripts/developer/tests/scriptableResource/lib.js b/scripts/developer/tests/scriptableResource/lib.js index 21e2e64cae..053cd0bf44 100644 --- a/scripts/developer/tests/scriptableResource/lib.js +++ b/scripts/developer/tests/scriptableResource/lib.js @@ -19,15 +19,15 @@ function getFrame(callback) { var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/finalFrame.fbx"; var model = ModelCache.prefetch(FRAME_URL); - if (model.loaded) { - makeFrame(true); + if (model.state = Resource.State.FINISHED) { + makeFrame(Resource.State.FINISHED); } else { model.stateChanged.connect(makeFrame); } function makeFrame(state) { - if (state == 4) { throw "Failed to load frame"; } - if (state != 3) { return; } + if (state == Resource.State.FAILED) { throw "Failed to load frame"; } + if (state != Resource.State.FINISHED) { return; } var pictureFrameProperties = { name: 'scriptableResourceTest Picture Frame', @@ -67,10 +67,10 @@ function prefetch(callback) { var filepath = MOVIE_URL + padded + '.jpg'; var texture = TextureCache.prefetch(filepath); frames.push(texture); - if (!texture.loaded) { + if (!texture.state == Resource.State.FINISHED) { numLoading++; texture.stateChanged.connect(function(state) { - if (state == 3 || state == 4) { + if (state == Resource.State.FAILED || state == Resource.State.FINISHED) { --numLoading; if (!numLoading) { callback(frames); } } From 854c0ea3e3ab36b0b527f900f5a1dc029a083835 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 27 Apr 2016 18:37:30 -0700 Subject: [PATCH 25/26] Clean out updateMemoryCost --- libraries/networking/src/ResourceCache.cpp | 11 +++++++---- libraries/networking/src/ResourceCache.h | 11 ++++++----- libraries/script-engine/src/ScriptEngine.cpp | 12 +++++++++++- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 32b1c03766..4cc8b1d4f0 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -128,10 +128,13 @@ void ScriptableResource::release() { _resource.reset(); } -void ScriptableResource::updateMemoryCost(const QObject* engine) { - if (_resource && !_resource->isInScript()) { - _resource->setInScript(true); - connect(_resource.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64))); +bool ScriptableResource::isInScript() const { + return _resource && _resource->isInScript(); +} + +void ScriptableResource::setInScript(bool isInScript) { + if (_resource) { + _resource->setInScript(isInScript); } } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 98473a58c3..b81c69c079 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -104,9 +104,10 @@ public: const QUrl& getUrl() const { return _url; } int getState() const { return (int)_state; } + const QSharedPointer& getResource() const { return _resource; } - // Connects to a SLOT(updateMemoryCost(qint64)) on the given engine - void updateMemoryCost(const QObject* engine); + bool isInScript() const; + void setInScript(bool isInScript); signals: void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); @@ -357,6 +358,9 @@ private slots: void handleReplyFinished(); private: + friend class ResourceCache; + friend class ScriptableResource; + void setLRUKey(int lruKey) { _lruKey = lruKey; } void makeRequest(); @@ -366,9 +370,6 @@ private: bool isInScript() const { return _isInScript; } void setInScript(bool isInScript) { _isInScript = isInScript; } - friend class ResourceCache; - friend class ScriptableResource; - ResourceRequest* _request{ nullptr }; int _lruKey{ 0 }; QTimer* _replyTimer{ nullptr }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 1cbd2a6127..fc8b581ffe 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -274,7 +274,17 @@ static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantR using ScriptableResourceRawPtr = ScriptableResource*; static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, const ScriptableResourceRawPtr& resource) { - resource->updateMemoryCost(engine); + // The first script to encounter this resource will track its memory. + // In this way, it will be more likely to GC. + // This fails in the case that the resource is used across many scripts, but + // in that case it would be too difficult to tell which one should track the memory, and + // this serves the common case (use in a single script). + auto data = resource->getResource(); + if (data && !resource->isInScript()) { + resource->setInScript(true); + QObject::connect(data.data(), SIGNAL(updateSize(qint64)), engine, SLOT(updateMemoryCost(qint64))); + } + auto object = engine->newQObject( const_cast(resource), QScriptEngine::ScriptOwnership); From 29e0744b89bb52652f084f243c92b3959428f7c0 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Mon, 2 May 2016 09:40:08 -0700 Subject: [PATCH 26/26] Fix equality in scriptableResource/lib --- scripts/developer/tests/scriptableResource/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/developer/tests/scriptableResource/lib.js b/scripts/developer/tests/scriptableResource/lib.js index 053cd0bf44..5241d0968e 100644 --- a/scripts/developer/tests/scriptableResource/lib.js +++ b/scripts/developer/tests/scriptableResource/lib.js @@ -19,7 +19,7 @@ function getFrame(callback) { var FRAME_URL = "http://hifi-production.s3.amazonaws.com/tutorials/pictureFrame/finalFrame.fbx"; var model = ModelCache.prefetch(FRAME_URL); - if (model.state = Resource.State.FINISHED) { + if (model.state === Resource.State.FINISHED) { makeFrame(Resource.State.FINISHED); } else { model.stateChanged.connect(makeFrame);