diff --git a/interface/resources/qml/+android_interface/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml index 97d076ab6a..ef3db5d570 100644 --- a/interface/resources/qml/+android_interface/Stats.qml +++ b/interface/resources/qml/+android_interface/Stats.qml @@ -241,9 +241,9 @@ Item { model: root.downloadUrls delegate: StatText { visible: root.expanded; - text: modelData.length > 30 + text: (modelData.length > 30 ? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22) - : modelData + : modelData) + "\n\t" + (!isNaN(root.downloadPriorities[index]) ? ("Priority: " + root.downloadPriorities[index] + ", ") : "") + "Progress: " + root.downloadProgresses[index] + "%" } } } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 0b5eba99d0..1482b6f92f 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -304,16 +304,16 @@ Item { } ListView { width: geoCol.width - height: root.downloadUrls.length * 15 + height: root.downloadUrls.length * 30 visible: root.expanded && root.downloadUrls.length > 0; model: root.downloadUrls delegate: StatText { visible: root.expanded; - text: modelData.length > 30 + text: (modelData.length > 30 ? modelData.substring(0, 5) + "..." + modelData.substring(modelData.length - 22) - : modelData + : modelData) + "\n\t" + (!isNaN(root.downloadPriorities[index]) ? ("Priority: " + root.downloadPriorities[index] + ", ") : "") + "Progress: " + root.downloadProgresses[index] + "%" } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 764f2bb54d..16831ee091 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2277,12 +2277,12 @@ void Application::initialize(const QCommandLineParser &parser) { auto loadingRequests = ResourceCache::getLoadingRequests(); QJsonArray loadingRequestsStats; - for (const auto& request : loadingRequests) { + for (const auto& requestPair : loadingRequests) { QJsonObject requestStats; - requestStats["filename"] = request->getURL().fileName(); - requestStats["received"] = request->getBytesReceived(); - requestStats["total"] = request->getBytesTotal(); - requestStats["attempts"] = (int)request->getDownloadAttempts(); + requestStats["filename"] = requestPair.first->getURL().fileName(); + requestStats["received"] = requestPair.first->getBytesReceived(); + requestStats["total"] = requestPair.first->getBytesTotal(); + requestStats["attempts"] = (int)requestPair.first->getDownloadAttempts(); loadingRequestsStats.append(requestStats); } @@ -5731,15 +5731,30 @@ void Application::init() { getEntities()->init(); getEntities()->setEntityLoadingPriorityFunction([this](const EntityItem& item) { - auto dims = item.getScaledDimensions(); - auto maxSize = glm::compMax(dims); + if (item.getEntityHostType() == entity::HostType::AVATAR) { + return item.isMyAvatarEntity() ? Avatar::MYAVATAR_ENTITY_LOADING_PRIORITY : Avatar::OTHERAVATAR_ENTITY_LOADING_PRIORITY; + } + const float maxSize = glm::compMax(item.getScaledDimensions()); if (maxSize <= 0.0f) { return 0.0f; } - auto distance = glm::distance(getMyAvatar()->getWorldPosition(), item.getWorldPosition()); - return atan2(maxSize, distance); + const glm::vec3 itemPosition = item.getWorldPosition(); + const float distance = glm::distance(getMyAvatar()->getWorldPosition(), itemPosition); + float result = atan2(maxSize, distance); + + bool isInView = true; + { + QMutexLocker viewLocker(&_viewMutex); + isInView = _viewFrustum.sphereIntersectsKeyhole(itemPosition, maxSize); + } + if (!isInView) { + const float OUT_OF_VIEW_PENALTY = -M_PI_2; + result += OUT_OF_VIEW_PENALTY; + } + + return result; }); ObjectMotionState::setShapeManager(&_shapeManager); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index fc6fae5456..1779d64dc6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -259,7 +259,7 @@ MyAvatar::MyAvatar(QThread* thread) : _headData = new MyHead(this); _skeletonModel = std::make_shared(this, nullptr); - _skeletonModel->setLoadingPriority(MYAVATAR_LOADING_PRIORITY); + _skeletonModel->setLoadingPriorityOperator([]() { return MYAVATAR_LOADING_PRIORITY; }); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); connect(_skeletonModel.get(), &Model::setURLFinished, this, [this](bool success) { if (success) { @@ -1841,6 +1841,8 @@ void MyAvatar::handleChangedAvatarEntityData() { } }); } + + _hasCheckedForAvatarEntities = true; } bool MyAvatar::updateStaleAvatarEntityBlobs() const { @@ -1896,6 +1898,7 @@ void MyAvatar::prepareAvatarEntityDataForReload() { }); _reloadAvatarEntityDataFromSettings = true; + _hasCheckedForAvatarEntities = false; } AvatarEntityMap MyAvatar::getAvatarEntityData() const { diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index aacb6f56a9..ab33a6f001 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -44,7 +44,7 @@ OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { // give the pointer to our head to inherited _headData variable from AvatarData _headData = new Head(this); _skeletonModel = std::make_shared(this, nullptr); - _skeletonModel->setLoadingPriority(OTHERAVATAR_LOADING_PRIORITY); + _skeletonModel->setLoadingPriorityOperator([]() { return OTHERAVATAR_LOADING_PRIORITY; }); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); @@ -595,7 +595,8 @@ void OtherAvatar::handleChangedAvatarEntityData() { } }); - setAvatarEntityDataChanged(false); + _avatarEntityDataChanged = false; + _hasCheckedForAvatarEntities = true; } void OtherAvatar::onAddAttachedAvatarEntity(const QUuid& id) { @@ -630,3 +631,11 @@ void OtherAvatar::updateAttachedAvatarEntities() { } } } + +void OtherAvatar::onIdentityRecieved() { + if (_avatarEntityIdentityCountdown > 0) { + _avatarEntityIdentityCountdown--; + } else { + _hasCheckedForAvatarEntities = true; + } +} diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index cfe0c8332d..094644a1d3 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -73,6 +73,8 @@ protected: void onAddAttachedAvatarEntity(const QUuid& id); void onRemoveAttachedAvatarEntity(const QUuid& id); + void onIdentityRecieved() override; + class AvatarEntityDataHash { public: AvatarEntityDataHash(uint32_t h) : hash(h) {}; @@ -91,6 +93,14 @@ protected: uint8_t _workloadRegion { workload::Region::INVALID }; BodyLOD _bodyLOD { BodyLOD::Sphere }; bool _needsDetailedRebuild { false }; + +private: + // When determining _hasCheckedForAvatarEntities for OtherAvatars, we can set it to true in + // handleChangedAvatarEntityData if we have avatar entities. But we never receive explicit + // confirmation from the avatar mixer if we don't have any. So instead, we wait to receive + // a few identity packets, and assume that if we haven't gotten any avatar entities by then, + // that we're safe to say there aren't any. + uint8_t _avatarEntityIdentityCountdown { 2 }; }; using OtherAvatarPointer = std::shared_ptr; diff --git a/interface/src/scripting/AccountServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp index 35e9f3b36d..e77fb13b89 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -159,8 +159,8 @@ bool DownloadInfoResultFromScriptValue(const ScriptValue& object, DownloadInfoRe DownloadInfoResult AccountServicesScriptingInterface::getDownloadInfo() { DownloadInfoResult result; - foreach(const auto& resource, ResourceCache::getLoadingRequests()) { - result.downloading.append(resource->getProgress() * 100.0f); + foreach(const auto& resourcePair, ResourceCache::getLoadingRequests()) { + result.downloading.append(resourcePair.first->getProgress() * 100.0f); } result.pending = ResourceCache::getPendingRequestCount(); return result; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 0e3a329375..79e4d0e28c 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -289,8 +289,8 @@ void Stats::updateStats(bool force) { STAT_UPDATE(entityPacketsInKbps, octreeServerCount ? totalEntityKbps / octreeServerCount : -1); - auto loadingRequests = ResourceCache::getLoadingRequests(); - STAT_UPDATE(downloads, loadingRequests.size()); + auto loadingRequestPairs = ResourceCache::getLoadingRequests(); + STAT_UPDATE(downloads, loadingRequestPairs.size()); STAT_UPDATE(downloadLimit, (int)ResourceCache::getRequestLimit()) STAT_UPDATE(downloadsPending, (int)ResourceCache::getPendingRequestCount()); STAT_UPDATE(processing, DependencyManager::get()->getStat("Processing").toInt()); @@ -298,29 +298,37 @@ void Stats::updateStats(bool force) { // See if the active download urls have changed bool shouldUpdateUrls = _downloads != _downloadUrls.size(); + bool shouldUpdateProgresses = false; if (!shouldUpdateUrls) { for (int i = 0; i < _downloads; i++) { - if (loadingRequests[i]->getURL().toString() != _downloadUrls[i]) { + if (loadingRequestPairs[i].first->getURL().toString() != _downloadUrls[i]) { shouldUpdateUrls = true; break; + } else if (loadingRequestPairs[i].first->getProgress() != _downloadProgresses[i]) { + shouldUpdateProgresses = true; } } } // If the urls have changed, update the list if (shouldUpdateUrls) { _downloadUrls.clear(); - foreach (const auto& resource, loadingRequests) { - _downloadUrls << resource->getURL().toString(); + _downloadPriorities.clear(); + foreach (const auto& resourcePair, loadingRequestPairs) { + _downloadUrls << resourcePair.first->getURL().toString(); + _downloadPriorities << resourcePair.second; } emit downloadUrlsChanged(); + emit downloadPrioritiesChanged(); + shouldUpdateProgresses = true; + } + + if (shouldUpdateProgresses) { + _downloadProgresses.clear(); + foreach (const auto& resourcePair, loadingRequestPairs) { + _downloadProgresses << (int)(100.0f * resourcePair.first->getProgress()); + } + emit downloadProgressesChanged(); } - // TODO fix to match original behavior - //stringstream downloads; - //downloads << "Downloads: "; - //foreach(Resource* resource, ) { - // downloads << (int)(resource->getProgress() * 100.0f) << "% "; - //} - //downloads << "(" << << " pending)"; } // Fourth column, octree stats diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index a3366904bd..b6d5e5ac9c 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -211,7 +211,10 @@ private: \ * Read-only. * @property {string[]} downloadUrls - The download URLs. * Read-only. - *

Note: Property not available in the API.

+ * @property {number[]} downloadProgresses - The download progresses. + * Read-only. + * @property {number[]} downloadPriorities - The download priorities. + * Read-only. * @property {number} processing - The number of completed downloads being processed. * Read-only. * @property {number} processingPending - The number of completed downloads waiting to be processed. @@ -529,6 +532,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, downloadLimit, 0) STATS_PROPERTY(int, downloadsPending, 0) Q_PROPERTY(QStringList downloadUrls READ downloadUrls NOTIFY downloadUrlsChanged) + Q_PROPERTY(QList downloadProgresses READ downloadProgresses NOTIFY downloadProgressesChanged) + Q_PROPERTY(QList downloadPriorities READ downloadPriorities NOTIFY downloadPrioritiesChanged) STATS_PROPERTY(int, processing, 0) STATS_PROPERTY(int, processingPending, 0) STATS_PROPERTY(int, triangles, 0) @@ -622,7 +627,9 @@ public: } } - QStringList downloadUrls () { return _downloadUrls; } + QStringList downloadUrls() { return _downloadUrls; } + QList downloadProgresses() { return _downloadProgresses; } + QList downloadPriorities() { return _downloadPriorities; } public slots: @@ -1091,6 +1098,20 @@ signals: */ void downloadUrlsChanged(); + /*@jsdoc + * Triggered when the value of the downloadProgresses property changes. + * @function Stats.downloadProgressesChanged + * @returns {Signal} + */ + void downloadProgressesChanged(); + + /*@jsdoc + * Triggered when the value of the downloadPriorities property changes. + * @function Stats.downloadPrioritiesChanged + * @returns {Signal} + */ + void downloadPrioritiesChanged(); + /*@jsdoc * Triggered when the value of the processing property changes. * @function Stats.processingChanged @@ -1809,14 +1830,16 @@ signals: */ private: - int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process - bool _resetRecentMaxPacketsSoon{ true }; - bool _expanded{ false }; - bool _showTimingDetails{ false }; - bool _showGameUpdateStats{ false }; + int _recentMaxPackets { 0 } ; // recent max incoming voxel packets to process + bool _resetRecentMaxPacketsSoon { true }; + bool _expanded { false }; + bool _showTimingDetails { false }; + bool _showGameUpdateStats { false }; QString _monospaceFont; const AudioIOStats* _audioStats; - QStringList _downloadUrls = QStringList(); + QStringList _downloadUrls { QStringList() }; + QList _downloadProgresses { QList() }; + QList _downloadPriorities { QList() }; }; #endif // hifi_Stats_h diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 9474c0309f..066ccec056 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -1085,7 +1085,7 @@ AnimNodeLoader::AnimNodeLoader(const QUrl& url) : { _resource = QSharedPointer::create(url); _resource->setSelf(_resource); - _resource->setLoadPriority(this, ANIM_GRAPH_LOAD_PRIORITY); + _resource->setLoadPriorityOperator(this, []() { return ANIM_GRAPH_LOAD_PRIORITY; }); connect(_resource.data(), &Resource::loaded, this, &AnimNodeLoader::onRequestDone); connect(_resource.data(), &Resource::failed, this, &AnimNodeLoader::onRequestError); _resource->ensureLoading(); diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 2b02a566ac..55a32e7237 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -35,7 +35,7 @@ SharedSoundPointer SoundCache::getSound(const QUrl& url) { QSharedPointer SoundCache::createResource(const QUrl& url) { auto resource = QSharedPointer(new Sound(url), &Resource::deleter); - resource->setLoadPriority(this, SOUNDS_LOADING_PRIORITY); + resource->setLoadPriorityOperator(this, []() { return SOUNDS_LOADING_PRIORITY; }); return resource; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 25473bda90..6b27a31f8e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -51,12 +51,14 @@ const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); const float Avatar::MYAVATAR_LOADING_PRIORITY = (float)M_PI; // Entity priority is computed as atan2(maxDim, distance) which is <= PI / 2 const float Avatar::OTHERAVATAR_LOADING_PRIORITY = MYAVATAR_LOADING_PRIORITY - EPSILON; +const float Avatar::MYAVATAR_ENTITY_LOADING_PRIORITY = MYAVATAR_LOADING_PRIORITY - EPSILON; +const float Avatar::OTHERAVATAR_ENTITY_LOADING_PRIORITY = OTHERAVATAR_LOADING_PRIORITY - EPSILON; namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { ItemKey::Builder keyBuilder = ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::hifi::TAG_ALL_VIEWS).withMetaCullGroup(); auto avatarPtr = static_pointer_cast(avatar); - if (!avatarPtr->getEnableMeshVisible()) { + if (!avatarPtr->shouldRender()) { keyBuilder.withInvisible(); } return keyBuilder.build(); @@ -646,7 +648,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); _skeletonModel->setCanCastShadow(true); - _skeletonModel->setVisibleInScene(_isMeshVisible, scene); + _skeletonModel->setVisibleInScene(shouldRender(), scene); processMaterials(); @@ -841,8 +843,26 @@ bool Avatar::getEnableMeshVisible() const { } void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { - bool canTryFade{ false }; + if (_needsWearablesLoadedCheck && _hasCheckedForAvatarEntities) { + bool wearablesAreLoaded = true; + // Technically, we should be checking for descendant avatar entities that are owned by this avatar. + // But it's sufficient to just check all children entities here. + forEachChild([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + auto entity = std::dynamic_pointer_cast(child); + if (entity && !entity->isVisuallyReady()) { + wearablesAreLoaded = false; + } + } + }); + _isReadyToDraw = wearablesAreLoaded; + if (_isReadyToDraw) { + _needMeshVisibleSwitch = true; + } + _needsWearablesLoadedCheck = !wearablesAreLoaded; + } + bool canTryFade = false; // check to see if when we added our models to the scene they were ready, if they were not ready, then // fix them up in the scene render::Transaction transaction; @@ -854,7 +874,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); _skeletonModel->setCanCastShadow(true); - _skeletonModel->setVisibleInScene(_isMeshVisible, scene); + _skeletonModel->setVisibleInScene(shouldRender(), scene); processMaterials(); canTryFade = true; @@ -862,7 +882,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { } if (_needMeshVisibleSwitch) { - _skeletonModel->setVisibleInScene(_isMeshVisible, scene); + _skeletonModel->setVisibleInScene(shouldRender(), scene); updateRenderItem(transaction); _needMeshVisibleSwitch = false; } @@ -1484,6 +1504,10 @@ void Avatar::rigReady() { buildSpine2SplineRatioCache(); setSkeletonData(getSkeletonDefaultData()); sendSkeletonData(); + + _needsWearablesLoadedCheck = _skeletonModel && _skeletonModel->isLoaded() && _skeletonModel->getGeometry()->shouldWaitForWearables(); + _needMeshVisibleSwitch = (_isReadyToDraw != !_needsWearablesLoadedCheck); + _isReadyToDraw = !_needsWearablesLoadedCheck; } // rig has been reset. diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index af6b58a187..2e618350a4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -554,6 +554,11 @@ public: uint32_t appendSubMetaItems(render::ItemIDs& subItems); + virtual bool shouldRender() const { return _isMeshVisible && _isReadyToDraw; } + + static const float MYAVATAR_ENTITY_LOADING_PRIORITY; + static const float OTHERAVATAR_ENTITY_LOADING_PRIORITY; + signals: /*@jsdoc * Triggered when the avatar's target scale is changed. The target scale is the desired scale of the avatar without any @@ -742,8 +747,11 @@ protected: void processMaterials(); AABox _renderBound; - bool _isMeshVisible{ true }; - bool _needMeshVisibleSwitch{ true }; + bool _isMeshVisible { true }; + bool _needMeshVisibleSwitch { true }; + bool _isReadyToDraw { false }; + bool _needsWearablesLoadedCheck { false }; + bool _hasCheckedForAvatarEntities { false }; static const float MYAVATAR_LOADING_PRIORITY; static const float OTHERAVATAR_LOADING_PRIORITY; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d44890b1b8..fc28ad9e02 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2072,6 +2072,8 @@ void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identity << "is >=" << (udt::SequenceNumber::Type) incomingSequenceNumber; #endif } + + onIdentityRecieved(); } QUrl AvatarData::getWireSafeSkeletonModelURL() const { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 55b1682974..a31291e1fc 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1276,7 +1276,6 @@ public: */ Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); - void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } AvatarEntityIDs getAndClearRecentlyRemovedIDs(); /*@jsdoc @@ -1583,6 +1582,8 @@ protected: virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; } virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer + virtual void onIdentityRecieved() {} + // Body scale float _targetScale; float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 1b54d1b3b7..793871e55a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1310,6 +1310,10 @@ void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint scene->enqueueTransaction(transaction); }); entity->setModel(model); + model->setLoadingPriorityOperator([entity]() { + float loadPriority = entity->getLoadPriority(); + return fabs(loadPriority) > EPSILON ? loadPriority : EntityTreeRenderer::getEntityLoadingPriority(*entity); + }); withWriteLock([&] { _model = model; }); } @@ -1317,7 +1321,6 @@ void ModelEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint if (_parsedModelURL != model->getURL()) { _texturesLoaded = false; _jointMappingCompleted = false; - model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); model->setURL(_parsedModelURL); } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index c477a3fcef..2f0b1e0945 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -612,6 +612,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_GROUP_CULLED, groupCulled); CHECK_PROPERTY_CHANGE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients); CHECK_PROPERTY_CHANGE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot); + CHECK_PROPERTY_CHANGE(PROP_LOAD_PRIORITY, loadPriority); changedProperties += _animation.getChangedProperties(); // Light @@ -1090,6 +1091,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {boolean} useOriginalPivot=false - If false, the model will be centered based on its content, * ignoring any offset in the model itself. If true, the model will respect its original offset. Currently, * only pivots relative to {x: 0, y: 0, z: 0} are supported. + * @property {number} loadPriority=0.0 - If 0, the model download will be prioritized based on distance, size, and + * other factors, and assigned a priority automatically between 0 and PI / 2. Otherwise, the + * download will be ordered based on the set loadPriority. * @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the * model's original textures. Use a texture name from the originalTextures property to override that texture. * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no @@ -1919,6 +1923,7 @@ ScriptValue EntityItemProperties::copyToScriptValue(ScriptEngine* engine, bool s COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOAD_PRIORITY, loadPriority); _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties, returnNothingOnEmptyPropertyFlags, isMyOwnAvatarEntity); } @@ -2350,6 +2355,7 @@ void EntityItemProperties::copyFromScriptValue(const ScriptValue& object, bool h COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled); COPY_PROPERTY_FROM_QSCRIPTVALUE(blendshapeCoefficients, QString, setBlendshapeCoefficients); COPY_PROPERTY_FROM_QSCRIPTVALUE(useOriginalPivot, bool, setUseOriginalPivot); + COPY_PROPERTY_FROM_QSCRIPTVALUE(loadPriority, float, setLoadPriority); _animation.copyFromScriptValue(object, namesSet, _defaultSettings); // Light @@ -2658,6 +2664,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(groupCulled); COPY_PROPERTY_IF_CHANGED(blendshapeCoefficients); COPY_PROPERTY_IF_CHANGED(useOriginalPivot); + COPY_PROPERTY_IF_CHANGED(loadPriority); _animation.merge(other._animation); // Light @@ -3036,6 +3043,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool); ADD_PROPERTY_TO_MAP(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString); ADD_PROPERTY_TO_MAP(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool); + ADD_PROPERTY_TO_MAP(PROP_LOAD_PRIORITY, LoadPriority, loadPriority, float); { // Animation ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); @@ -3517,6 +3525,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, properties.getGroupCulled()); APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, properties.getBlendshapeCoefficients()); APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, properties.getUseOriginalPivot()); + APPEND_ENTITY_PROPERTY(PROP_LOAD_PRIORITY, properties.getLoadPriority()); _staticAnimation.setProperties(properties); _staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -4029,6 +4038,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GROUP_CULLED, bool, setGroupCulled); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOAD_PRIORITY, float, setLoadPriority); properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); } @@ -4438,6 +4448,7 @@ void EntityItemProperties::markAllChanged() { _groupCulledChanged = true; _blendshapeCoefficientsChanged = true; _useOriginalPivotChanged = true; + _loadPriorityChanged = true; _animation.markAllChanged(); // Light @@ -5024,6 +5035,9 @@ QList EntityItemProperties::listChangedProperties() { if (useOriginalPivotChanged()) { out += "useOriginalPivot"; } + if (loadPriorityChanged()) { + out += "loadPriority"; + } getAnimation().listChangedProperties(out); // Light diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index f8e7377df7..e9a4800892 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -315,6 +315,7 @@ public: DEFINE_PROPERTY_REF(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool, false); DEFINE_PROPERTY_REF(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString, ""); DEFINE_PROPERTY_REF(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool, false); + DEFINE_PROPERTY_REF(PROP_LOAD_PRIORITY, LoadPriority, loadPriority, float, 0.0f); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); // Light diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 2ed634c42c..a95feed55b 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -255,17 +255,18 @@ enum EntityPropertyList { PROP_GROUP_CULLED = PROP_DERIVED_7, PROP_BLENDSHAPE_COEFFICIENTS = PROP_DERIVED_8, PROP_USE_ORIGINAL_PIVOT = PROP_DERIVED_9, + PROP_LOAD_PRIORITY = PROP_DERIVED_10, // Animation - PROP_ANIMATION_URL = PROP_DERIVED_10, - PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_11, - PROP_ANIMATION_FPS = PROP_DERIVED_12, - PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_13, - PROP_ANIMATION_PLAYING = PROP_DERIVED_14, - PROP_ANIMATION_LOOP = PROP_DERIVED_15, - PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_16, - PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_17, - PROP_ANIMATION_HOLD = PROP_DERIVED_18, - PROP_ANIMATION_SMOOTH_FRAMES = PROP_DERIVED_19, + PROP_ANIMATION_URL = PROP_DERIVED_11, + PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_12, + PROP_ANIMATION_FPS = PROP_DERIVED_13, + PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_14, + PROP_ANIMATION_PLAYING = PROP_DERIVED_15, + PROP_ANIMATION_LOOP = PROP_DERIVED_16, + PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_17, + PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_18, + PROP_ANIMATION_HOLD = PROP_DERIVED_19, + PROP_ANIMATION_SMOOTH_FRAMES = PROP_DERIVED_20, // Light PROP_IS_SPOTLIGHT = PROP_DERIVED_0, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index c96cb8d667..209240533b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -368,7 +368,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } } } else { - if (getIsServer()) { + if (isEntityServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); if (properties.simulationOwnerChanged()) { QUuid submittedID = properties.getSimulationOwner().getID(); @@ -674,7 +674,7 @@ void EntityTree::deleteEntitiesByID(const std::vector& ids, bool f // this method has two paths: // (a) entity-server: applies delete filter // (b) interface-client: deletes local- and my-avatar-entities immediately, submits domainEntity deletes to the entity-server - if (getIsServer()) { + if (isEntityServer()) { withWriteLock([&] { std::vector entitiesToDelete; entitiesToDelete.reserve(ids.size()); @@ -1468,7 +1468,7 @@ bool EntityTree::isScriptInAllowlist(const QString& scriptProperty) { // NOTE: Caller must lock the tree before calling this. int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { - if (!getIsServer()) { + if (!isEntityServer()) { qCWarning(entities) << "EntityTree::processEditPacketData() should only be called on a server tree."; return 0; } @@ -2071,7 +2071,7 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo // Which means this is a state synchronization message from the the entity-server. It is saying // "The following domain-entities have already been deleted". While need to perform sanity checking // (e.g. verify these are domain entities) permissions need NOT checked for the domain-entities. - assert(!getIsServer()); + assert(!isEntityServer()); // TODO: remove this stuff out of EntityTree:: and into interface-client code. #ifdef EXTRA_ERASE_DEBUGGING qCDebug(entities) << "EntityTree::processEraseMessage()"; @@ -2141,7 +2141,7 @@ int EntityTree::processEraseMessage(ReceivedMessage& message, const SharedNodePo int EntityTree::processEraseMessageDetails(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { // NOTE: this is called on entity-server when receiving a delete request from an interface-client or agent //TODO: assert(treeIsLocked); - assert(getIsServer()); + assert(isEntityServer()); #ifdef EXTRA_ERASE_DEBUGGING qCDebug(entities) << "EntityTree::processEraseMessageDetails()"; #endif diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 8910961c50..5306613925 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -72,6 +72,7 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d COPY_ENTITY_PROPERTY_TO_PROPERTIES(groupCulled, getGroupCulled); COPY_ENTITY_PROPERTY_TO_PROPERTIES(blendshapeCoefficients, getBlendshapeCoefficients); COPY_ENTITY_PROPERTY_TO_PROPERTIES(useOriginalPivot, getUseOriginalPivot); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(loadPriority, getLoadPriority); withReadLock([&] { _animationProperties.getProperties(properties); }); @@ -96,6 +97,7 @@ bool ModelEntityItem::setSubClassProperties(const EntityItemProperties& properti SET_ENTITY_PROPERTY_FROM_PROPERTIES(groupCulled, setGroupCulled); SET_ENTITY_PROPERTY_FROM_PROPERTIES(blendshapeCoefficients, setBlendshapeCoefficients); SET_ENTITY_PROPERTY_FROM_PROPERTIES(useOriginalPivot, setUseOriginalPivot); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(loadPriority, setLoadPriority); withWriteLock([&] { AnimationPropertyGroup animationProperties = _animationProperties; @@ -131,6 +133,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_GROUP_CULLED, bool, setGroupCulled); READ_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients); READ_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot); + READ_ENTITY_PROPERTY(PROP_LOAD_PRIORITY, float, setLoadPriority); // grab a local copy of _animationProperties to avoid multiple locks int bytesFromAnimation; @@ -171,6 +174,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_GROUP_CULLED; requestedProperties += PROP_BLENDSHAPE_COEFFICIENTS; requestedProperties += PROP_USE_ORIGINAL_PIVOT; + requestedProperties += PROP_LOAD_PRIORITY; requestedProperties += _animationProperties.getEntityProperties(params); return requestedProperties; @@ -201,6 +205,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, getGroupCulled()); APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, getBlendshapeCoefficients()); APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, getUseOriginalPivot()); + APPEND_ENTITY_PROPERTY(PROP_LOAD_PRIORITY, getLoadPriority()); withReadLock([&] { _animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -255,6 +260,7 @@ void ModelEntityItem::debugDump() const { qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL(); qCDebug(entities) << " blendshapeCoefficients:" << getBlendshapeCoefficients(); qCDebug(entities) << " useOrigialPivot:" << getUseOriginalPivot(); + qCDebug(entities) << " loadPriority:" << getLoadPriority(); } void ModelEntityItem::setShapeType(ShapeType type) { @@ -768,3 +774,15 @@ bool ModelEntityItem::getUseOriginalPivot() const { return _useOriginalPivot; }); } + +float ModelEntityItem::getLoadPriority() const { + return resultWithReadLock([&] { + return _loadPriority; + }); +} + +void ModelEntityItem::setLoadPriority(float loadPriority) { + withWriteLock([&] { + _loadPriority = loadPriority; + }); +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 2e1995be88..f6c3e82073 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -123,6 +123,9 @@ public: bool getUseOriginalPivot() const; void setUseOriginalPivot(bool useOriginalPivot); + float getLoadPriority() const; + void setLoadPriority(float loadPriority); + private: void setAnimationSettings(const QString& value); // only called for old bitstream format bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); @@ -152,6 +155,7 @@ protected: glm::u8vec3 _color; glm::vec3 _modelScale { 1.0f }; QString _modelURL; + float _loadPriority { 0.0f }; bool _relayParentJoints; bool _groupCulled { false }; QVariantMap _blendshapeCoefficientsMap; diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index 496b00ae2c..840fa50a0a 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -448,9 +448,9 @@ void NetworkTexture::setExtra(void* extra) { _shouldFailOnRedirect = _currentlyLoadingResourceType != ResourceType::KTX; if (_type == image::TextureUsage::SKY_TEXTURE) { - setLoadPriority(this, SKYBOX_LOAD_PRIORITY); + setLoadPriorityOperator(this, []() { return SKYBOX_LOAD_PRIORITY; }); } else if (_currentlyLoadingResourceType == ResourceType::KTX) { - setLoadPriority(this, HIGH_MIPS_LOAD_PRIORITY); + setLoadPriorityOperator(this, []() { return HIGH_MIPS_LOAD_PRIORITY; }); } if (!_url.isValid()) { @@ -704,7 +704,7 @@ void NetworkTexture::startRequestForNextMipLevel() { init(false); float priority = -(float)_originalKtxDescriptor->header.numberOfMipmapLevels + (float)_lowestKnownPopulatedMip; - setLoadPriority(this, priority); + setLoadPriorityOperator(this, [priority]() { return priority; }); _url.setFragment(QString::number(_lowestKnownPopulatedMip - 1)); TextureCache::attemptRequest(self); } diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index ddfdeb79d1..264c6b9801 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -252,7 +252,6 @@ void GeometryResource::downloadFinished(const QByteArray& data) { } auto animGraphVariant = _mapping.value("animGraphUrl"); - if (animGraphVariant.isValid()) { QUrl fstUrl(animGraphVariant.toString()); if (fstUrl.isValid()) { @@ -264,6 +263,8 @@ void GeometryResource::downloadFinished(const QByteArray& data) { _animGraphOverrideUrl = QUrl(); } + _waitForWearables = _mapping.value(WAIT_FOR_WEARABLES_FIELD).toBool(); + auto modelCache = DependencyManager::get(); GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseURL, false }; @@ -452,6 +453,7 @@ Geometry::Geometry(const Geometry& geometry) { } _animGraphOverrideUrl = geometry._animGraphOverrideUrl; + _waitForWearables = geometry._waitForWearables; _mapping = geometry._mapping; } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 236c6262bf..7902108709 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -58,6 +58,7 @@ public: virtual bool areTexturesLoaded() const; const QUrl& getAnimGraphOverrideUrl() const { return _animGraphOverrideUrl; } + bool shouldWaitForWearables() const { return _waitForWearables; } const QVariantHash& getMapping() const { return _mapping; } protected: @@ -72,6 +73,7 @@ protected: QUrl _animGraphOverrideUrl; QVariantHash _mapping; // parsed contents of FST file. + bool _waitForWearables { false }; private: mutable bool _areTexturesLoaded { false }; diff --git a/libraries/model-serializers/src/FSTReader.cpp b/libraries/model-serializers/src/FSTReader.cpp index 7e84f012a7..9c1ff5a431 100644 --- a/libraries/model-serializers/src/FSTReader.cpp +++ b/libraries/model-serializers/src/FSTReader.cpp @@ -22,7 +22,7 @@ #include -const QStringList SINGLE_VALUE_PROPERTIES{"name", "filename", "texdir", "script", "comment"}; +const QStringList SINGLE_VALUE_PROPERTIES { NAME_FIELD, FILENAME_FIELD, TEXDIR_FIELD, SCRIPT_FIELD, WAIT_FOR_WEARABLES_FIELD, COMMENT_FIELD }; hifi::VariantMultiHash FSTReader::parseMapping(QIODevice* device) { hifi::VariantMultiHash properties; diff --git a/libraries/model-serializers/src/FSTReader.h b/libraries/model-serializers/src/FSTReader.h index e1b7405346..5557df67c6 100644 --- a/libraries/model-serializers/src/FSTReader.h +++ b/libraries/model-serializers/src/FSTReader.h @@ -33,6 +33,7 @@ static const QString BLENDSHAPE_FIELD = "bs"; static const QString SCRIPT_FIELD = "script"; static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; static const QString MATERIAL_MAPPING_FIELD = "materialMap"; +static const QString WAIT_FOR_WEARABLES_FIELD = "waitForWearables"; static const QString COMMENT_FIELD = "comment"; class FSTReader { diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 4e36fb4646..782c40609d 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -30,10 +30,10 @@ #include "NetworkLogging.h" #include "NodeList.h" -bool ResourceCacheSharedItems::appendRequest(QWeakPointer resource) { +bool ResourceCacheSharedItems::appendRequest(QWeakPointer resource, float priority) { Lock lock(_mutex); if ((uint32_t)_loadingRequests.size() < _requestLimit) { - _loadingRequests.append(resource); + _loadingRequests.append({ resource, priority }); return true; } else { _pendingRequests.append(resource); @@ -70,14 +70,14 @@ uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const { return _pendingRequests.size(); } -QList> ResourceCacheSharedItems::getLoadingRequests() const { - QList> result; +QList, float>> ResourceCacheSharedItems::getLoadingRequests() const { + QList, float>> result; Lock lock(_mutex); - foreach(QWeakPointer resource, _loadingRequests) { - auto locked = resource.lock(); + foreach(auto resourcePair, _loadingRequests) { + auto locked = resourcePair.first.lock(); if (locked) { - result.append(locked); + result.append({ locked, resourcePair.second }); } } @@ -96,7 +96,7 @@ void ResourceCacheSharedItems::removeRequest(QWeakPointer resource) { // QWeakPointer has no operator== implementation for two weak ptrs, so // manually loop in case resource has been freed. for (int i = 0; i < _loadingRequests.size();) { - auto request = _loadingRequests.at(i); + auto request = _loadingRequests.at(i).first; // Clear our resource and any freed resources if (!request || request.toStrongRef().data() == resource.toStrongRef().data()) { _loadingRequests.removeAt(i); @@ -106,7 +106,7 @@ void ResourceCacheSharedItems::removeRequest(QWeakPointer resource) { } } -QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { +std::pair, float> ResourceCacheSharedItems::getHighestPendingRequest() { // look for the highest priority pending request int highestIndex = -1; float highestPriority = -FLT_MAX; @@ -139,7 +139,7 @@ QSharedPointer ResourceCacheSharedItems::getHighestPendingRequest() { _pendingRequests.takeAt(highestIndex); } - return highestResource; + return { highestResource, highestPriority }; } void ResourceCacheSharedItems::clear() { @@ -519,7 +519,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) { emit dirty(); } -QList> ResourceCache::getLoadingRequests() { +QList, float>> ResourceCache::getLoadingRequests() { return DependencyManager::get()->getLoadingRequests(); } @@ -531,11 +531,11 @@ uint32_t ResourceCache::getLoadingRequestCount() { return DependencyManager::get()->getLoadingRequestsCount(); } -bool ResourceCache::attemptRequest(QSharedPointer resource) { +bool ResourceCache::attemptRequest(QSharedPointer resource, float priority) { Q_ASSERT(!resource.isNull()); auto sharedItems = DependencyManager::get(); - if (sharedItems->appendRequest(resource)) { + if (sharedItems->appendRequest(resource, priority)) { resource->makeRequest(); return true; } @@ -555,8 +555,8 @@ void ResourceCache::requestCompleted(QWeakPointer resource) { bool ResourceCache::attemptHighestPriorityRequest() { auto sharedItems = DependencyManager::get(); - auto resource = sharedItems->getHighestPendingRequest(); - return (resource && attemptRequest(resource)); + auto resourcePair = sharedItems->getHighestPendingRequest(); + return (resourcePair.first && attemptRequest(resourcePair.first, resourcePair.second)); } static int requestID = 0; @@ -571,7 +571,7 @@ Resource::Resource(const Resource& other) : _startedLoading(other._startedLoading), _failedToLoad(other._failedToLoad), _loaded(other._loaded), - _loadPriorities(other._loadPriorities), + _loadPriorityOperators(other._loadPriorityOperators), _bytesReceived(other._bytesReceived), _bytesTotal(other._bytesTotal), _bytes(other._bytes), @@ -605,40 +605,24 @@ void Resource::ensureLoading() { } } -void Resource::setLoadPriority(const QPointer& owner, float priority) { +void Resource::setLoadPriorityOperator(const QPointer& owner, std::function priorityOperator) { if (!_failedToLoad) { - _loadPriorities.insert(owner, priority); - } -} - -void Resource::setLoadPriorities(const QHash, float>& priorities) { - if (_failedToLoad) { - return; - } - for (QHash, float>::const_iterator it = priorities.constBegin(); - it != priorities.constEnd(); it++) { - _loadPriorities.insert(it.key(), it.value()); - } -} - -void Resource::clearLoadPriority(const QPointer& owner) { - if (!_failedToLoad) { - _loadPriorities.remove(owner); + _loadPriorityOperators.insert(owner, priorityOperator); } } float Resource::getLoadPriority() { - if (_loadPriorities.size() == 0) { + if (_loadPriorityOperators.size() == 0) { return 0; } float highestPriority = -FLT_MAX; - for (QHash, float>::iterator it = _loadPriorities.begin(); it != _loadPriorities.end(); ) { - if (it.key().isNull()) { - it = _loadPriorities.erase(it); + for (QHash, std::function>::iterator it = _loadPriorityOperators.begin(); it != _loadPriorityOperators.end();) { + if (it.key().isNull() || !it.value()) { + it = _loadPriorityOperators.erase(it); continue; } - highestPriority = qMax(highestPriority, it.value()); + highestPriority = qMax(highestPriority, it.value()()); it++; } return highestPriority; @@ -742,7 +726,7 @@ void Resource::attemptRequest() { void Resource::finishedLoading(bool success) { if (success) { - _loadPriorities.clear(); + _loadPriorityOperators.clear(); _loaded = true; } else { _failedToLoad = true; diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 0eafd1f900..d2687f0964 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -66,14 +67,14 @@ class ResourceCacheSharedItems : public Dependency { using Lock = std::unique_lock; public: - bool appendRequest(QWeakPointer newRequest); + bool appendRequest(QWeakPointer newRequest, float priority); void removeRequest(QWeakPointer doneRequest); void setRequestLimit(uint32_t limit); uint32_t getRequestLimit() const; QList> getPendingRequests() const; - QSharedPointer getHighestPendingRequest(); + std::pair, float> getHighestPendingRequest(); uint32_t getPendingRequestsCount() const; - QList> getLoadingRequests() const; + QList, float>> getLoadingRequests() const; uint32_t getLoadingRequestsCount() const; void clear(); @@ -82,7 +83,7 @@ private: mutable Mutex _mutex; QList> _pendingRequests; - QList> _loadingRequests; + QList, float>> _loadingRequests; const uint32_t DEFAULT_REQUEST_LIMIT = 10; uint32_t _requestLimit { DEFAULT_REQUEST_LIMIT }; }; @@ -216,7 +217,7 @@ public: void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize); qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; } - static QList> getLoadingRequests(); + static QList, float>> getLoadingRequests(); static uint32_t getPendingRequestCount(); static uint32_t getLoadingRequestCount(); @@ -268,7 +269,7 @@ protected: /// Attempt to load a resource if requests are below the limit, otherwise queue the resource for loading /// \return true if the resource began loading, otherwise false if the resource is in the pending queue - static bool attemptRequest(QSharedPointer resource); + static bool attemptRequest(QSharedPointer resource, float priority = NAN); static void requestCompleted(QWeakPointer resource); static bool attemptHighestPriorityRequest(); @@ -424,13 +425,7 @@ public: void ensureLoading(); /// Sets the load priority for one owner. - virtual void setLoadPriority(const QPointer& owner, float priority); - - /// Sets a set of priorities at once. - virtual void setLoadPriorities(const QHash, float>& priorities); - - /// Clears the load priority for one owner. - virtual void clearLoadPriority(const QPointer& owner); + virtual void setLoadPriorityOperator(const QPointer& owner, std::function priorityOperator); /// Returns the highest load priority across all owners. float getLoadPriority(); @@ -451,7 +446,7 @@ public: qint64 getBytes() const { return _bytes; } /// For loading resources, returns the load progress. - float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : (float)_bytesReceived / _bytesTotal; } + float getProgress() const { return (_bytesTotal <= 0) ? 0.0f : ((float)_bytesReceived / _bytesTotal); } /// Refreshes the resource. virtual void refresh(); @@ -537,7 +532,7 @@ protected: bool _failedToLoad = false; bool _loaded = false; - QHash, float> _loadPriorities; + QHash, std::function> _loadPriorityOperators; QWeakPointer _self; QPointer _cache; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 67b9544f2e..960345ffaa 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -359,6 +359,7 @@ enum class EntityVersion : PacketVersion { AmbientColor, SoundEntities, TonemappingAndAmbientOcclusion, + ModelLoadPriority, // Add new versions above here NUM_PACKET_TYPE, diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b7b645dd8a..69a593ed09 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1343,7 +1343,7 @@ void Model::setURL(const QUrl& url) { auto resource = DependencyManager::get()->getGeometryResource(url); if (resource) { - resource->setLoadPriority(this, _loadingPriority); + resource->setLoadPriorityOperator(this, _loadingPriorityOperator); _renderWatcher.setResource(resource); } _rig.initFlow(false); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 63a96f7253..bc9b8fcfff 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -295,7 +295,7 @@ public: // returns 'true' if needs fullUpdate after geometry change virtual bool updateGeometry(); - void setLoadingPriority(float priority) { _loadingPriority = priority; } + void setLoadingPriorityOperator(std::function priorityOperator) { _loadingPriorityOperator = priorityOperator; } size_t getRenderInfoVertexCount() const { return _renderInfoVertexCount; } size_t getRenderInfoTextureSize(); @@ -518,7 +518,7 @@ protected: uint64_t _created; private: - float _loadingPriority { 0.0f }; + std::function _loadingPriorityOperator { []() { return 0.0f; } }; void calculateTextureInfo();