From c2460e2dbd6dc3525703f5eb7312714716bb5bc8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:13:11 +0300 Subject: #4724 Fix performance problems with My Outfits --- indra/newview/llinventoryitemslist.cpp | 85 ++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 9 deletions(-) (limited to 'indra/newview/llinventoryitemslist.cpp') diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index cfa37cc3ee..73cc953692 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -40,6 +40,10 @@ #include "llinventorymodel.h" #include "llviewerinventory.h" +bool LLInventoryItemsList::sListIdleRegistered = false; +LLInventoryItemsList::all_list_t LLInventoryItemsList::sAllLists; +LLInventoryItemsList::all_list_t::iterator LLInventoryItemsList::sAllListIter; + LLInventoryItemsList::Params::Params() {} @@ -55,13 +59,39 @@ LLInventoryItemsList::LLInventoryItemsList(const LLInventoryItemsList::Params& p setNoFilteredItemsMsg(LLTrans::getString("InventoryNoMatchingItems")); - gIdleCallbacks.addFunction(idle, this); + sAllLists.push_back(this); + sAllListIter = sAllLists.begin(); + + if (!sListIdleRegistered) + { + sAllListIter = sAllLists.begin(); + gIdleCallbacks.addFunction(idle, nullptr); + + LLEventPumps::instance().obtain("LLApp").listen( + "LLInventoryItemsList", + [](const LLSD& stat) + { + std::string status(stat["status"]); + if (status != "running") + { + // viewer is starting shutdown + gIdleCallbacks.deleteFunction(idle, nullptr); + } + return false; + }); + sListIdleRegistered = true; + } } // virtual LLInventoryItemsList::~LLInventoryItemsList() { - gIdleCallbacks.deleteFunction(idle, this); + auto it = std::find(sAllLists.begin(), sAllLists.end(), this); + if (it != sAllLists.end()) + { + sAllLists.erase(it); + sAllListIter = sAllLists.begin(); + } } void LLInventoryItemsList::refreshList(const LLInventoryModel::item_array_t item_array) @@ -111,25 +141,55 @@ void LLInventoryItemsList::updateSelection() mSelectTheseIDs.clear(); } -void LLInventoryItemsList::doIdle() +bool LLInventoryItemsList::doIdle() { - if (mRefreshState == REFRESH_COMPLETE) return; + if (mRefreshState == REFRESH_COMPLETE) return true; // done if (isInVisibleChain() || mForceRefresh || !getFilterSubString().empty()) { refresh(); mRefreshCompleteSignal(this, LLSD()); + return false; // keep going } + return true; // done } //static void LLInventoryItemsList::idle(void* user_data) { - LLInventoryItemsList* self = static_cast(user_data); - if ( self ) - { // Do the real idle - self->doIdle(); + if (sAllLists.empty()) + return; + + LL_PROFILE_ZONE_SCOPED; + + using namespace std::chrono; + auto start = steady_clock::now(); + const milliseconds time_limit = milliseconds(3); + const auto end_time = start + time_limit; + S32 max_update_count = 50; + + if (sAllListIter == sAllLists.end()) + { + sAllListIter = sAllLists.begin(); + } + + S32 updated = 0; + while (steady_clock::now() < end_time + && updated < max_update_count + && sAllListIter != sAllLists.end()) + { + LLInventoryItemsList* list = *sAllListIter; + // Refresh is split into multiple separate parts, + // so keep repeating it while there is time, until done. + // Todo: refresh() split is pointless now? + // Or still useful for large folders? + if (list->doIdle()) + { + // Item is done + ++sAllListIter; + updated++; + } } } @@ -141,6 +201,7 @@ void LLInventoryItemsList::refresh() { case REFRESH_ALL: { + LL_PROFILE_ZONE_NAMED("items_refresh_all"); mAddedItems.clear(); mRemovedItems.clear(); computeDifference(getIDs(), mAddedItems, mRemovedItems); @@ -163,6 +224,7 @@ void LLInventoryItemsList::refresh() } case REFRESH_LIST_ERASE: { + LL_PROFILE_ZONE_NAMED("items_refresh_erase"); uuid_vec_t::const_iterator it = mRemovedItems.begin(); for (; mRemovedItems.end() != it; ++it) { @@ -175,6 +237,7 @@ void LLInventoryItemsList::refresh() } case REFRESH_LIST_APPEND: { + LL_PROFILE_ZONE_NAMED("items_refresh_append"); static const unsigned ADD_LIMIT = 25; // Note: affects perfomance unsigned int nadded = 0; @@ -239,6 +302,7 @@ void LLInventoryItemsList::refresh() } case REFRESH_LIST_SORT: { + LL_PROFILE_ZONE_NAMED("items_refresh_sort"); // Filter, sort, rearrange and notify parent about shape changes filterItems(true, true); @@ -255,7 +319,10 @@ void LLInventoryItemsList::refresh() break; } default: - break; + { + mRefreshState = REFRESH_COMPLETE; + break; + } } setForceRefresh(mRefreshState != REFRESH_COMPLETE); -- cgit v1.3 From bc4492da8b4dd173356a7d68e85e14b2ca3fe9e6 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 25 Nov 2025 21:10:58 +0200 Subject: #5046 Remove redundant updates in outfit list #1 --- indra/llui/llflatlistview.cpp | 6 +++- indra/llui/llflatlistview.h | 3 +- indra/newview/llinventoryitemslist.cpp | 51 ++++++++++++++++++++++++++++------ indra/newview/llinventoryitemslist.h | 1 + 4 files changed, 51 insertions(+), 10 deletions(-) (limited to 'indra/newview/llinventoryitemslist.cpp') diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index dfe0a71b74..34eb1ea3fc 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -537,6 +537,7 @@ bool LLFlatListView::postBuild() void LLFlatListView::rearrangeItems() { + LL_PROFILE_ZONE_SCOPED; static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); setNoItemsCommentVisible(0==size()); @@ -1132,6 +1133,7 @@ bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange) void LLFlatListView::notifyParentItemsRectChanged() { + LL_PROFILE_ZONE_SCOPED; S32 comment_height = 0; // take into account comment text height if exists @@ -1401,7 +1403,7 @@ bool LLFlatListViewEx::updateItemVisibility(LLPanel* item, const LLSD &action) return false; } -void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) +bool LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) { std::string cur_filter = mFilterSubString; LLStringUtil::toUpper(cur_filter); @@ -1426,7 +1428,9 @@ void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) { rearrangeItems(); notifyParentItemsRectChanged(); + return true; } + return false; } bool LLFlatListViewEx::hasMatchedItems() diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 1f22360a8a..39afa33be8 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -499,8 +499,9 @@ public: /** * Filters the list, rearranges and notifies parent about shape changes. * Derived classes may want to overload rearrangeItems() to exclude repeated separators after filtration. + * Returns true in case of changes */ - void filterItems(bool re_sort, bool notify_parent); + bool filterItems(bool re_sort, bool notify_parent); /** * Returns true if last call of filterItems() found at least one matching item diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index 73cc953692..d2ccfe5dfe 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -51,6 +51,7 @@ LLInventoryItemsList::LLInventoryItemsList(const LLInventoryItemsList::Params& p : LLFlatListViewEx(p) , mRefreshState(REFRESH_COMPLETE) , mForceRefresh(false) +, mNeedsArrange(true) { // TODO: mCommitOnSelectionChange is set to "false" in LLFlatListView // but reset to true in all derived classes. This settings might need to @@ -218,8 +219,6 @@ void LLInventoryItemsList::refresh() mRefreshState = REFRESH_LIST_SORT; } - rearrangeItems(); - notifyParentItemsRectChanged(); break; } case REFRESH_LIST_ERASE: @@ -229,10 +228,21 @@ void LLInventoryItemsList::refresh() for (; mRemovedItems.end() != it; ++it) { // don't filter items right away - removeItemByUUID(*it, false); + removeItemByUUID(*it, false /*don't rearrange*/); } mRemovedItems.clear(); - mRefreshState = REFRESH_LIST_SORT; // fix visibility and arrange + mRefreshState = REFRESH_LIST_SORT; // fix visibility + + // Assume that visible items were removed. + if (getVisible()) + { + rearrangeItems(); + notifyParentItemsRectChanged(); + } + else + { + mNeedsArrange = true; + } break; } case REFRESH_LIST_APPEND: @@ -275,18 +285,25 @@ void LLInventoryItemsList::refresh() LLSD action; action.with("match_filter", cur_filter); + bool new_visible_items = false; pairs_const_iterator_t pair_it = panel_list.begin(); for (; pair_it != panel_list.end(); ++pair_it) { item_pair_t* item_pair = *pair_it; if (item_pair->first->getParent() != NULL) { - updateItemVisibility(item_pair->first, action); + new_visible_items |= updateItemVisibility(item_pair->first, action); } } - rearrangeItems(); - notifyParentItemsRectChanged(); + mNeedsArrange |= new_visible_items; + if (mNeedsArrange && getVisible()) + { + // show changes now + rearrangeItems(); + notifyParentItemsRectChanged(); + mNeedsArrange = false; + } if (mAddedItems.size() > 0) { @@ -304,16 +321,33 @@ void LLInventoryItemsList::refresh() { LL_PROFILE_ZONE_NAMED("items_refresh_sort"); // Filter, sort, rearrange and notify parent about shape changes - filterItems(true, true); + if (filterItems(true, true)) + { + mNeedsArrange = false; // just rearranged + } if (mAddedItems.size() == 0) { + if (mNeedsArrange) + { + // Done, last chance to rearrange + rearrangeItems(); + notifyParentItemsRectChanged(); + mNeedsArrange = false; + } // After list building completed, select items that had been requested to select before list was build updateSelection(); mRefreshState = REFRESH_COMPLETE; } else { + if (mNeedsArrange && getVisible()) + { + // show changes now + rearrangeItems(); + notifyParentItemsRectChanged(); + mNeedsArrange = false; + } mRefreshState = REFRESH_LIST_APPEND; } break; @@ -347,6 +381,7 @@ void LLInventoryItemsList::computeDifference( LLPanel* LLInventoryItemsList::createNewItem(LLViewerInventoryItem* item) { + LL_PROFILE_ZONE_SCOPED; if (!item) { LL_WARNS() << "No inventory item. Couldn't create flat list item." << LL_ENDL; diff --git a/indra/newview/llinventoryitemslist.h b/indra/newview/llinventoryitemslist.h index b20c27eec8..f80d6b31b8 100644 --- a/indra/newview/llinventoryitemslist.h +++ b/indra/newview/llinventoryitemslist.h @@ -117,6 +117,7 @@ protected: }; ERefreshStates mRefreshState; + bool mNeedsArrange = true; private: uuid_vec_t mIDs; // IDs of items that were added in refreshList(). -- cgit v1.3 From 3cab5caa2169c1acc7b2f2ddba31e5926e14455b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 25 Nov 2025 23:52:55 +0200 Subject: #5046 Fix accordion control's excessive rearranges #2 Since arrange is no longer part of LLInventoryItemsList::doIdle(), reduced time limit. --- indra/llui/llaccordionctrl.cpp | 39 +++++++++++++++++++++++++++++----- indra/llui/llaccordionctrl.h | 6 ++++++ indra/newview/llinventoryitemslist.cpp | 3 ++- indra/newview/llinventorylistitem.cpp | 2 ++ indra/newview/lloutfitslist.cpp | 4 ++++ indra/newview/llviewerwindow.cpp | 3 +++ 6 files changed, 51 insertions(+), 6 deletions(-) (limited to 'indra/newview/llinventoryitemslist.cpp') diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index 1a64c2699d..8dcc809dfe 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -47,6 +47,8 @@ static constexpr F32 AUTO_SCROLL_RATE_ACCEL = 120.f; static LLDefaultChildRegistry::Register t2("accordion"); +std::set LLAccordionCtrl::sPendingArrange; + LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params) , mFitParent(params.fit_parent) , mNoVisibleTabsOrigString(params.no_visible_tabs_text.initial_value().asString()) @@ -163,7 +165,11 @@ bool LLAccordionCtrl::postBuild() //--------------------------------------------------------------------------------- LLAccordionCtrl::~LLAccordionCtrl() { - mAccordionTabs.clear(); + if (mArrangePending) + { + sPendingArrange.erase(this); + } + mAccordionTabs.clear(); } //--------------------------------------------------------------------------------- @@ -184,7 +190,7 @@ void LLAccordionCtrl::reshape(S32 width, S32 height, bool called_from_parent) // necessary text paddings can be set via h_pad and v_pad mNoVisibleTabsHelpText->setRect(getLocalRect()); - arrange(); + scheduleArrange(); } //--------------------------------------------------------------------------------- @@ -328,7 +334,7 @@ void LLAccordionCtrl::addCollapsibleCtrl(LLAccordionCtrlTab* accordion_tab) mAccordionTabs.push_back(accordion_tab); accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, (S16)(mAccordionTabs.size() - 1)) ); - arrange(); + scheduleArrange(); } void LLAccordionCtrl::removeCollapsibleCtrl(LLAccordionCtrlTab* accordion_tab) @@ -685,8 +691,9 @@ S32 LLAccordionCtrl::notifyParent(const LLSD& info) std::string str_action = info["action"]; if (str_action == "size_changes") { - // - arrange(); + // Multiple children can request an arrange, + // but only need to do it once so schedule it for later. + scheduleArrange(); return 1; } if (str_action == "select_next") @@ -928,3 +935,25 @@ void LLAccordionCtrl::collapseAllTabs() arrange(); } } + +void LLAccordionCtrl::scheduleArrange() +{ + if (!mArrangePending) + { + mArrangePending = true; + sPendingArrange.insert(this); + } +} + +void LLAccordionCtrl::updateClass() +{ + for (LLAccordionCtrl* inst : sPendingArrange) + { + if (inst) + { + inst->mArrangePending = false; + inst->arrange(); + } + } + sPendingArrange.clear(); +} diff --git a/indra/llui/llaccordionctrl.h b/indra/llui/llaccordionctrl.h index 43a33a2b3c..c7bb8bc9ff 100644 --- a/indra/llui/llaccordionctrl.h +++ b/indra/llui/llaccordionctrl.h @@ -142,6 +142,9 @@ public: void setSkipScrollToChild(bool skip) { mSkipScrollToChild = skip; } + void scheduleArrange(); + static void updateClass(); + private: void initNoTabsWidget(const LLTextBox::Params& tb_params); void updateNoTabsHelpTextVisibility(); @@ -188,12 +191,15 @@ private: LLTextBox* mNoVisibleTabsHelpText = nullptr; bool mSkipScrollToChild = false; + bool mArrangePending = false; std::string mNoMatchedTabsOrigString; std::string mNoVisibleTabsOrigString; LLAccordionCtrlTab* mSelectedTab = nullptr; const LLTabComparator* mTabComparator = nullptr; + + static std::set sPendingArrange; }; diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index d2ccfe5dfe..15735ebde3 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -145,6 +145,7 @@ void LLInventoryItemsList::updateSelection() bool LLInventoryItemsList::doIdle() { if (mRefreshState == REFRESH_COMPLETE) return true; // done + LL_PROFILE_ZONE_SCOPED; if (isInVisibleChain() || mForceRefresh || !getFilterSubString().empty()) { @@ -166,7 +167,7 @@ void LLInventoryItemsList::idle(void* user_data) using namespace std::chrono; auto start = steady_clock::now(); - const milliseconds time_limit = milliseconds(3); + const milliseconds time_limit = milliseconds(2); const auto end_time = start + time_limit; S32 max_update_count = 50; diff --git a/indra/newview/llinventorylistitem.cpp b/indra/newview/llinventorylistitem.cpp index 5fb5b0f23f..a435a4f7c7 100644 --- a/indra/newview/llinventorylistitem.cpp +++ b/indra/newview/llinventorylistitem.cpp @@ -69,6 +69,7 @@ LLPanelInventoryListItemBase::Params::Params() LLPanelInventoryListItemBase* LLPanelInventoryListItemBase::create(LLViewerInventoryItem* item) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; LLPanelInventoryListItemBase* list_item = NULL; if (item) { @@ -189,6 +190,7 @@ void LLPanelInventoryListItemBase::setShowWidget(LLUICtrl* ctrl, bool show) bool LLPanelInventoryListItemBase::postBuild() { + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; LLViewerInventoryItem* inv_item = getItem(); if (inv_item) { diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 58cd9fab83..c153561d70 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -568,6 +568,10 @@ void LLOutfitsList::onRefreshComplete(LLUICtrl* ctrl) if (!ctrl || getFilterSubString().empty()) return; + LL_PROFILE_ZONE_SCOPED; + + // TODO: We are doing it multiple times per frame on init + // as multiple lists are refreshing. Needs improvements. for (outfits_map_t::iterator iter = mOutfitsMap.begin(), iter_end = mOutfitsMap.end(); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 670a3b2939..74b34ceee6 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -86,6 +86,7 @@ #include "raytrace.h" // newview includes +#include "llaccordionctrl.h" #include "llbox.h" #include "llchicletbar.h" #include "llconsole.h" @@ -3440,6 +3441,8 @@ void LLViewerWindow::updateUI() LLConsole::updateClass(); + // execute postponed arrange calls + LLAccordionCtrl::updateClass(); // animate layout stacks so we have up to date rect for world view LLLayoutStack::updateClass(); -- cgit v1.3