#4724 Fix performance problems with My Outfits - <FS:PP> Cherry pick for FIRE-35936

# Conflicts:
#	indra/newview/lloutfitslist.cpp
master
Andrey Kleshchev 2025-09-23 00:13:11 +03:00 committed by PanteraPolnocy
parent 42df6526a2
commit fa7cacc755
7 changed files with 121 additions and 19 deletions

View File

@ -303,8 +303,11 @@ void LLAccordionCtrl::ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S3
return; return;
LLRect panel_rect = panel->getRect(); LLRect panel_rect = panel->getRect();
panel_rect.setLeftTopAndSize( left, top, width, height); panel_rect.setLeftTopAndSize( left, top, width, height);
panel->reshape( width, height, 1); if (panel->getRect() != panel_rect)
panel->setRect(panel_rect); {
panel->reshape( width, height, 1);
panel->setRect(panel_rect);
}
} }
void LLAccordionCtrl::ctrlShiftVertical(LLView* panel, S32 delta) void LLAccordionCtrl::ctrlShiftVertical(LLView* panel, S32 delta)
@ -494,6 +497,7 @@ void LLAccordionCtrl::arrangeMultiple()
void LLAccordionCtrl::arrange() void LLAccordionCtrl::arrange()
{ {
LL_PROFILE_ZONE_SCOPED;
updateNoTabsHelpTextVisibility(); updateNoTabsHelpTextVisibility();
if (mAccordionTabs.empty()) if (mAccordionTabs.empty())

View File

@ -248,10 +248,22 @@ void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::draw()
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::reshape(S32 width, S32 height, bool called_from_parent /* = true */) void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::reshape(S32 width, S32 height, bool called_from_parent /* = true */)
{ {
S32 header_height = mHeaderTextbox->getTextPixelHeight(); S32 header_height = mHeaderTextbox->getTextPixelHeight();
LLRect old_header_rect = mHeaderTextbox->getRect();
LLRect textboxRect(HEADER_TEXT_LEFT_OFFSET, (height + header_height) / 2, width, (height - header_height) / 2); LLRect textboxRect(HEADER_TEXT_LEFT_OFFSET, (height + header_height) / 2, width, (height - header_height) / 2);
mHeaderTextbox->reshape(textboxRect.getWidth(), textboxRect.getHeight()); if (old_header_rect.getHeight() != textboxRect.getHeight()
mHeaderTextbox->setRect(textboxRect); || old_header_rect.mLeft != textboxRect.mLeft
|| old_header_rect.mTop != textboxRect.mTop
|| old_header_rect.getWidth() > textboxRect.getWidth() // reducing header's width
|| (old_header_rect.getWidth() < textboxRect.getWidth() && old_header_rect.getWidth() < mHeaderTextbox->getTextPixelWidth()))
{
// Expensive text reflow
// Update if position or height changes
// Update if width reduces
// But do not update if text already fits and width increases (arguably LLTextBox::reshape should be smarter, not Accordion)
mHeaderTextbox->reshape(textboxRect.getWidth(), textboxRect.getHeight());
mHeaderTextbox->setRect(textboxRect);
}
if (mHeaderTextbox->getTextPixelWidth() > mHeaderTextbox->getRect().getWidth()) if (mHeaderTextbox->getTextPixelWidth() > mHeaderTextbox->getRect().getWidth())
{ {
@ -416,8 +428,11 @@ void LLAccordionCtrlTab::reshape(S32 width, S32 height, bool called_from_parent
LLRect headerRect; LLRect headerRect;
headerRect.setLeftTopAndSize(0, height, width, HEADER_HEIGHT); headerRect.setLeftTopAndSize(0, height, width, HEADER_HEIGHT);
mHeader->setRect(headerRect); if (mHeader->getRect() != headerRect)
mHeader->reshape(headerRect.getWidth(), headerRect.getHeight()); {
mHeader->setRect(headerRect);
mHeader->reshape(headerRect.getWidth(), headerRect.getHeight());
}
if (!mDisplayChildren) if (!mDisplayChildren)
return; return;
@ -932,7 +947,7 @@ void LLAccordionCtrlTab::adjustContainerPanel(const LLRect& child_rect)
show_hide_scrollbar(child_rect); show_hide_scrollbar(child_rect);
updateLayout(child_rect); updateLayout(child_rect);
} }
else else if (mContainerPanel->getRect() != child_rect)
{ {
mContainerPanel->reshape(child_rect.getWidth(), child_rect.getHeight()); mContainerPanel->reshape(child_rect.getWidth(), child_rect.getHeight());
mContainerPanel->setRect(child_rect); mContainerPanel->setRect(child_rect);

View File

@ -1592,6 +1592,8 @@ void LLTextBase::reshape(S32 width, S32 height, bool called_from_parent)
// up-to-date mVisibleTextRect // up-to-date mVisibleTextRect
updateRects(); updateRects();
// Todo: This might be wrong. updateRects already sets needsReflow conditionaly.
// Reflow is expensive and doing it at any twith can be too much.
needsReflow(); needsReflow();
} }
} }

View File

@ -40,6 +40,10 @@
#include "llinventorymodel.h" #include "llinventorymodel.h"
#include "llviewerinventory.h" #include "llviewerinventory.h"
bool LLInventoryItemsList::sListIdleRegistered = false;
LLInventoryItemsList::all_list_t LLInventoryItemsList::sAllLists;
LLInventoryItemsList::all_list_t::iterator LLInventoryItemsList::sAllListIter;
LLInventoryItemsList::Params::Params() LLInventoryItemsList::Params::Params()
{} {}
@ -55,13 +59,39 @@ LLInventoryItemsList::LLInventoryItemsList(const LLInventoryItemsList::Params& p
setNoFilteredItemsMsg(LLTrans::getString("InventoryNoMatchingItems")); 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 // virtual
LLInventoryItemsList::~LLInventoryItemsList() 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) void LLInventoryItemsList::refreshList(const LLInventoryModel::item_array_t item_array)
@ -111,25 +141,55 @@ void LLInventoryItemsList::updateSelection()
mSelectTheseIDs.clear(); 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()) if (isInVisibleChain() || mForceRefresh || !getFilterSubString().empty())
{ {
refresh(); refresh();
mRefreshCompleteSignal(this, LLSD()); mRefreshCompleteSignal(this, LLSD());
return false; // keep going
} }
return true; // done
} }
//static //static
void LLInventoryItemsList::idle(void* user_data) void LLInventoryItemsList::idle(void* user_data)
{ {
LLInventoryItemsList* self = static_cast<LLInventoryItemsList*>(user_data); if (sAllLists.empty())
if ( self ) return;
{ // Do the real idle
self->doIdle(); 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: case REFRESH_ALL:
{ {
LL_PROFILE_ZONE_NAMED("items_refresh_all");
mAddedItems.clear(); mAddedItems.clear();
mRemovedItems.clear(); mRemovedItems.clear();
computeDifference(getIDs(), mAddedItems, mRemovedItems); computeDifference(getIDs(), mAddedItems, mRemovedItems);
@ -163,6 +224,7 @@ void LLInventoryItemsList::refresh()
} }
case REFRESH_LIST_ERASE: case REFRESH_LIST_ERASE:
{ {
LL_PROFILE_ZONE_NAMED("items_refresh_erase");
uuid_vec_t::const_iterator it = mRemovedItems.begin(); uuid_vec_t::const_iterator it = mRemovedItems.begin();
for (; mRemovedItems.end() != it; ++it) for (; mRemovedItems.end() != it; ++it)
{ {
@ -175,6 +237,7 @@ void LLInventoryItemsList::refresh()
} }
case REFRESH_LIST_APPEND: case REFRESH_LIST_APPEND:
{ {
LL_PROFILE_ZONE_NAMED("items_refresh_append");
static const unsigned ADD_LIMIT = 25; // Note: affects perfomance static const unsigned ADD_LIMIT = 25; // Note: affects perfomance
unsigned int nadded = 0; unsigned int nadded = 0;
@ -239,6 +302,7 @@ void LLInventoryItemsList::refresh()
} }
case REFRESH_LIST_SORT: case REFRESH_LIST_SORT:
{ {
LL_PROFILE_ZONE_NAMED("items_refresh_sort");
// Filter, sort, rearrange and notify parent about shape changes // Filter, sort, rearrange and notify parent about shape changes
filterItems(true, true); filterItems(true, true);
@ -255,7 +319,10 @@ void LLInventoryItemsList::refresh()
break; break;
} }
default: default:
break; {
mRefreshState = REFRESH_COMPLETE;
break;
}
} }
setForceRefresh(mRefreshState != REFRESH_COMPLETE); setForceRefresh(mRefreshState != REFRESH_COMPLETE);

View File

@ -59,7 +59,10 @@ public:
* Sets the flag indicating that the list needs to be refreshed even if it is * Sets the flag indicating that the list needs to be refreshed even if it is
* not currently visible. * not currently visible.
*/ */
void setForceRefresh(bool force_refresh) { mForceRefresh = force_refresh; } void setForceRefresh(bool force_refresh)
{
mForceRefresh = force_refresh;
}
/** /**
* If refreshes when invisible. * If refreshes when invisible.
@ -76,7 +79,7 @@ public:
* This is needed for example to filter items of the list hidden by closed * This is needed for example to filter items of the list hidden by closed
* accordion tab. * accordion tab.
*/ */
virtual void doIdle(); // Real idle routine bool doIdle(); // Real idle routine
static void idle(void* user_data); // static glue to doIdle() static void idle(void* user_data); // static glue to doIdle()
protected: protected:
@ -126,6 +129,12 @@ private:
bool mForceRefresh; bool mForceRefresh;
commit_signal_t mRefreshCompleteSignal; commit_signal_t mRefreshCompleteSignal;
// Update synchronization
typedef std::vector<LLInventoryItemsList*> all_list_t;
static all_list_t sAllLists;
static all_list_t::iterator sAllListIter;
static bool sListIdleRegistered;
}; };
#endif //LL_LLINVENTORYITEMSLIST_H #endif //LL_LLINVENTORYITEMSLIST_H

View File

@ -818,6 +818,7 @@ void LLOutfitGallery::getCurrentCategories(uuid_vec_t& vcur)
void LLOutfitGallery::updateAddedCategory(LLUUID cat_id) void LLOutfitGallery::updateAddedCategory(LLUUID cat_id)
{ {
LL_PROFILE_ZONE_SCOPED;
LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
if (!cat) return; if (!cat) return;

View File

@ -189,6 +189,7 @@ void LLOutfitsList::onOpen(const LLSD& info)
void LLOutfitsList::updateAddedCategory(LLUUID cat_id) void LLOutfitsList::updateAddedCategory(LLUUID cat_id)
{ {
LL_PROFILE_ZONE_SCOPED;
LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
if (!cat) return; if (!cat) return;
@ -260,7 +261,9 @@ void LLOutfitsList::updateAddedCategory(LLUUID cat_id)
if (AISAPI::isAvailable() && LLInventoryModelBackgroundFetch::instance().folderFetchActive()) if (AISAPI::isAvailable() && LLInventoryModelBackgroundFetch::instance().folderFetchActive())
{ {
// for reliability just fetch it whole, linked items included // For reliability just fetch it whole, linked items included
// Todo: list is not warrantied to exist once callback arrives
// Fix it!
LLInventoryModelBackgroundFetch::instance().fetchFolderAndLinks(cat_id, [cat_id, list] LLInventoryModelBackgroundFetch::instance().fetchFolderAndLinks(cat_id, [cat_id, list]
{ {
if (list) if (list)
@ -1153,6 +1156,7 @@ void LLOutfitListBase::onIdle(void* userdata)
void LLOutfitListBase::onIdleRefreshList() void LLOutfitListBase::onIdleRefreshList()
{ {
LL_PROFILE_ZONE_SCOPED;
if (LLAppViewer::instance()->quitRequested()) if (LLAppViewer::instance()->quitRequested())
{ {
mRefreshListState.CategoryUUID.setNull(); mRefreshListState.CategoryUUID.setNull();