diff --git a/lib/usd/ui/layerEditor/layerTreeModel.cpp b/lib/usd/ui/layerEditor/layerTreeModel.cpp index 4bf35c2fb..c9355f763 100644 --- a/lib/usd/ui/layerEditor/layerTreeModel.cpp +++ b/lib/usd/ui/layerEditor/layerTreeModel.cpp @@ -297,13 +297,17 @@ void LayerTreeModel::setSessionState(SessionState* in_sessionState) rebuildModelOnIdle(true); } -void LayerTreeModel::rebuildModelOnIdle(bool dataChanged) +void LayerTreeModel::rebuildModelOnIdle(bool dataChanged, bool refreshLockState) { + // Coalesce rebuilds within one event-loop turn into a single rebuild. Accumulate + // the strongest request across coalesced callers (data-changed and lock-refresh). _selectedLayerDataChanged |= dataChanged; + _rebuildOnIdleRefreshLockState = _rebuildOnIdleRefreshLockState || refreshLockState; if (!_rebuildOnIdlePending) { _rebuildOnIdlePending = true; QTimer::singleShot(0, this, [this]() { - bool refreshLockState = false; + const bool refreshLockState = _rebuildOnIdleRefreshLockState; + _rebuildOnIdleRefreshLockState = false; this->rebuildModel(refreshLockState); }); } @@ -527,8 +531,9 @@ void LayerTreeModel::usd_layerDirtinessChanged( // called from SessionState::currentStageChangedSignal void LayerTreeModel::sessionStageChanged() { - bool refreshLockState = true; - rebuildModel(refreshLockState); + // Coalesce: a burst of stage changes collapses into one rebuild instead of + // rebuilding synchronously on every change. + rebuildModelOnIdle(/*dataChanged*/ false, /*refreshLockState*/ true); } // called from SessionState::autoHideSessionLayerSignal diff --git a/lib/usd/ui/layerEditor/layerTreeModel.h b/lib/usd/ui/layerEditor/layerTreeModel.h index aa7c91ada..4319af830 100644 --- a/lib/usd/ui/layerEditor/layerTreeModel.h +++ b/lib/usd/ui/layerEditor/layerTreeModel.h @@ -150,9 +150,10 @@ class LayerTreeModel mutable int _lastAskedAnonLayerNameSinceRebuild = 0; - void rebuildModelOnIdle(bool dataChanged = false); + void rebuildModelOnIdle(bool dataChanged = false, bool refreshLockState = false); bool _rebuildOnIdlePending = false; bool _selectedLayerDataChanged = false; + bool _rebuildOnIdleRefreshLockState = false; void rebuildModel(bool refreshLockState = false); void updateTargetLayer(InRebuildModel inRebuild); diff --git a/lib/usd/ui/layerEditor/layerTreeView.cpp b/lib/usd/ui/layerEditor/layerTreeView.cpp index 608c06939..3e4c6b7f7 100644 --- a/lib/usd/ui/layerEditor/layerTreeView.cpp +++ b/lib/usd/ui/layerEditor/layerTreeView.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -140,7 +141,7 @@ LayerTreeView::LayerTreeView(SessionState* in_sessionState, QWidget* in_parent) _model->sessionState(), &SessionState::stageListChangedSignal, this, - &LayerTreeView::updateFromSessionState); + &LayerTreeView::updateFromSessionStateOnIdle); auto buttonDefinitions = LayerTreeItem::actionButtonsDefinition(); auto muteActionIter = buttonDefinitions.find(LayerActionType::Mute); @@ -352,6 +353,19 @@ void LayerViewMemento::restore(LayerTreeView& view, LayerTreeModel& model) } } +// Coalesce a burst of stageListChangedSignal notifications into a single refresh +// (updateFromSessionState re-scans all stages, so avoid doing it per stage). +void LayerTreeView::updateFromSessionStateOnIdle() +{ + if (!_updateFromSessionStatePending) { + _updateFromSessionStatePending = true; + QTimer::singleShot(0, this, [this]() { + _updateFromSessionStatePending = false; + updateFromSessionState(); + }); + } +} + void LayerTreeView::updateFromSessionState() { if (_cachedModelState == nullptr) { diff --git a/lib/usd/ui/layerEditor/layerTreeView.h b/lib/usd/ui/layerEditor/layerTreeView.h index 13a9316f3..c40986991 100644 --- a/lib/usd/ui/layerEditor/layerTreeView.h +++ b/lib/usd/ui/layerEditor/layerTreeView.h @@ -147,6 +147,9 @@ class LayerTreeView // Updates the _cachedModelState using stage data from the session void updateFromSessionState(); + // Coalesced updateFromSessionState (one refresh per event-loop turn). + void updateFromSessionStateOnIdle(); + bool _updateFromSessionStatePending { false }; LayerTreeViewStyle _treeViewStyle; QPointer _model; diff --git a/lib/usd/ui/layerEditor/stageSelectorWidget.cpp b/lib/usd/ui/layerEditor/stageSelectorWidget.cpp index bbe744b94..5d544b887 100644 --- a/lib/usd/ui/layerEditor/stageSelectorWidget.cpp +++ b/lib/usd/ui/layerEditor/stageSelectorWidget.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include @@ -43,29 +44,23 @@ Q_DECLARE_METATYPE(UsdLayerEditor::SessionState::StageEntry); namespace { -int getEntryIndexById( - const std::string& id, - std::vector const& stages) +// Find the combo index whose stored StageEntry matches id, or -1. Used instead of +// SessionState::allStages() (a full DAG scan) so per-stage handlers stay O(combo). +int comboIndexById(const QComboBox* dropDown, const std::string& id) { - auto it = std::find_if( - stages.begin(), stages.end(), [&id](UsdLayerEditor::SessionState::StageEntry stageEntry) { - return (id == stageEntry._id); - }); - - if (it != stages.end()) { - return std::distance(stages.begin(), it); + for (int i = 0, count = dropDown->count(); i < count; ++i) { + // Bind itemData() to a named QVariant so value<>() uses the const-lvalue + // overload. Calling value<>() directly on the returned rvalue selects the + // move overload, which move-constructs StageEntry and trips a spurious + // -Werror=array-bounds under GCC. + const QVariant data = dropDown->itemData(i); + if (data.value()._id == id) { + return i; + } } - return -1; } -int getEntryIndexById( - UsdLayerEditor::SessionState::StageEntry const& entry, - std::vector const& stages) -{ - return getEntryIndexById(entry._id, stages); -} - bool loadStagePinnedOption() { const MString optionName = PXR_NS::MayaUsdOptionVars->PinLayerEditorStage.GetText(); @@ -241,7 +236,7 @@ void StageSelectorWidget::setSessionState(SessionState* in_sessionState) _sessionState, &SessionState::stageListChangedSignal, this, - &StageSelectorWidget::updateFromSessionState); + &StageSelectorWidget::updateFromSessionStateOnIdle); connect( _sessionState, &SessionState::currentStageChangedSignal, @@ -269,6 +264,23 @@ SessionState::StageEntry const StageSelectorWidget::selectedStage() return SessionState::StageEntry(); } +// Coalesce a burst of stageListChangedSignal notifications into a single dropdown +// rebuild (last requested selection wins). Avoids a per-stage rebuild + DAG scan. +void StageSelectorWidget::updateFromSessionStateOnIdle( + SessionState::StageEntry const& entryToSelect) +{ + _pendingSelectEntry = entryToSelect; + if (!_updateFromSessionStatePending) { + _updateFromSessionStatePending = true; + QTimer::singleShot(0, this, [this]() { + _updateFromSessionStatePending = false; + const SessionState::StageEntry entry = _pendingSelectEntry; + _pendingSelectEntry = SessionState::StageEntry(); + updateFromSessionState(entry); + }); + } +} + // repopulates the combo based on the session stage list void StageSelectorWidget::updateFromSessionState(SessionState::StageEntry const& entryToSelect) { @@ -326,9 +338,6 @@ void StageSelectorWidget::selectionChanged() if (!ufeGlobalSelection) return; - std::vector allStages; - bool allStagesFilled = false; - // We will set the currently selected stage to be the stage of the first item // that is a USD item. So if multiple stages are selected, the first one wins. const Ufe::Selection& ufeSelection = *ufeGlobalSelection; @@ -337,14 +346,9 @@ void StageSelectorWidget::selectionChanged() if (!proxyShapePtr) continue; - if (!allStagesFilled) { - allStages = _sessionState->allStages(); - allStagesFilled = true; - } - MFnDagNode dagNode(proxyShapePtr->thisMObject()); const std::string id = dagNode.uuid().asString().asChar(); - const int index = getEntryIndexById(id, allStages); + const int index = comboIndexById(_dropDown, id); if (index == -1) continue; @@ -393,7 +397,7 @@ void StageSelectorWidget::updateContentButton() void StageSelectorWidget::sessionStageChanged() { if (!_internalChange) { - auto index = getEntryIndexById(_sessionState->stageEntry(), _sessionState->allStages()); + auto index = comboIndexById(_dropDown, _sessionState->stageEntry()._id); if (index != -1) { QSignalBlocker blocker(_dropDown); _dropDown->setCurrentIndex(index); @@ -403,7 +407,7 @@ void StageSelectorWidget::sessionStageChanged() void StageSelectorWidget::stageRenamed(SessionState::StageEntry const& renamedEntry) { - auto index = getEntryIndexById(renamedEntry, _sessionState->allStages()); + auto index = comboIndexById(_dropDown, renamedEntry._id); if (index != -1) { _dropDown->setItemText(index, renamedEntry._displayName.c_str()); _dropDown->setItemData(index, QVariant::fromValue(renamedEntry)); @@ -412,18 +416,10 @@ void StageSelectorWidget::stageRenamed(SessionState::StageEntry const& renamedEn void StageSelectorWidget::stageReset(SessionState::StageEntry const& entry) { - // Individual combo box entries have a short display name and a reference to a stage, - // which is not a unique combination. By construction the combo box indices do line - // up with the SessionState StageEntry vector though, so in the case of resetting - // a proxy we will find the matching full proxy path in that vector and use its index - // to update the combo box. - auto count = _dropDown->count(); - if (count <= 0) { - return; - } - - auto index = getEntryIndexById(entry, _sessionState->allStages()); - if (index >= 0 && index < count) { + // Refresh only the matching combo item's data, matched locally by id, instead + // of SessionState::allStages() (a full DAG scan) — this fires O(N) times. + const int index = comboIndexById(_dropDown, entry._id); + if (index != -1) { _dropDown->setItemData(index, QVariant::fromValue(entry)); } } diff --git a/lib/usd/ui/layerEditor/stageSelectorWidget.h b/lib/usd/ui/layerEditor/stageSelectorWidget.h index 9965a8720..43a02b3e7 100644 --- a/lib/usd/ui/layerEditor/stageSelectorWidget.h +++ b/lib/usd/ui/layerEditor/stageSelectorWidget.h @@ -49,6 +49,9 @@ class StageSelectorWidget : public QWidget // slot: void updateFromSessionState( SessionState::StageEntry const& entryToSelect = SessionState::StageEntry()); + // Coalesced updateFromSessionState (one dropdown rebuild per event-loop turn). + void updateFromSessionStateOnIdle( + SessionState::StageEntry const& entryToSelect = SessionState::StageEntry()); void stageRenamed(SessionState::StageEntry const& renamedEntry); void stageReset(SessionState::StageEntry const& entry); void sessionStageChanged(); @@ -64,6 +67,10 @@ class StageSelectorWidget : public QWidget QPushButton* _collapseContent { nullptr }; bool _internalChange { false }; // for notifications bool _pinStageSelection { true }; + + // Coalescing state for updateFromSessionStateOnIdle(). + bool _updateFromSessionStatePending { false }; + SessionState::StageEntry _pendingSelectEntry; }; } // namespace UsdLayerEditor