Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fd3b9e8
Initial changes for async inventory
rohvani Oct 1, 2025
0e5bc2e
Add support for AsyncInventoryMaxConcurrentFetches
rohvani Oct 1, 2025
24b833f
General improvements to async inventory
rohvani Oct 1, 2025
61778b4
Allow us to use cat versions from cache
rohvani Oct 1, 2025
07665b7
Fix cache dehydration
rohvani Oct 1, 2025
55931a2
Some cleanup changes
rohvani Oct 6, 2025
4181183
Performance optimizations around UI
rohvani Oct 6, 2025
a136f86
Add workflow_dispatch trigger to build workflow (#4774)
Oct 2, 2025
0743423
Fix lingering inventory after async skeleton failures (#4894)
marchcat Oct 24, 2025
aa20ce3
Merge branch 'release/2025.08' into pepper/async-inventory
Geenz Oct 24, 2025
9ce7af3
Merge branch 'release/2025.08' into pepper/async-inventory
Geenz Oct 28, 2025
7278895
#4893 Drop unused llpanel XML filename reference
marchcat Oct 28, 2025
456eddd
#4893 Extract async inventory skeleton loader
marchcat Oct 29, 2025
b0a94df
#4893 Tighten LLView child cache
marchcat Oct 29, 2025
105e1ec
Merge pull request #4906 from secondlife/marchcat/async-inv-4893
marchcat Oct 29, 2025
6fcfcd5
Merge branch 'release/2025.08' into pepper/async-inventory
marchcat Nov 6, 2025
8325e69
#4893 Enable async inventory loading by default
marchcat Nov 6, 2025
5526832
#4893 Use LOG_INV constant for inventory logging
marchcat Nov 6, 2025
1e38ebc
Merge branch 'release/2026.01' into pepper/async-inventory
marchcat Dec 3, 2025
cc96921
#4893 Build fix
marchcat Dec 3, 2025
8e8541e
Update LLTextBase to match #4932
marchcat Dec 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions indra/llcommon/llsingleton.h
Original file line number Diff line number Diff line change
Expand Up @@ -524,13 +524,16 @@ class LLSingleton : public LLSingletonBase
case INITIALIZING:
// here if DERIVED_TYPE::initSingleton() (directly or indirectly)
// calls DERIVED_TYPE::getInstance(): go ahead and allow it
case INITIALIZED:
// normal subsequent calls
// record the dependency, if any: check if we got here from another
// record the dependency: check if we got here from another
// LLSingleton's constructor or initSingleton() method
capture_dependency(lk->mInstance);
return lk->mInstance;

case INITIALIZED:
// normal subsequent calls - skip capture_dependency() for performance
// dependencies are only tracked during initialization
return lk->mInstance;

case DELETED:
// called after deleteSingleton()
logwarns({"Trying to access deleted singleton ",
Expand Down Expand Up @@ -730,12 +733,14 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>

case super::INITIALIZING:
// As with LLSingleton, explicitly permit circular calls from
// within initSingleton()
case super::INITIALIZED:
// for any valid call, capture dependencies
// within initSingleton() and capture dependencies
super::capture_dependency(lk->mInstance);
return lk->mInstance;

case super::INITIALIZED:
// normal subsequent calls - skip capture_dependency() for performance
return lk->mInstance;

case super::DELETED:
super::logerrs({"Trying to access deleted param singleton ",
super::template classname<DERIVED_TYPE>()});
Expand Down
18 changes: 18 additions & 0 deletions indra/llinventory/llfoldertype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,24 @@ bool LLFolderType::lookupIsEnsembleType(EType folder_type)
folder_type <= FT_ENSEMBLE_END);
}

// static
bool LLFolderType::lookupIsEssentialType(EType folder_type)
{
if (folder_type == FT_NONE)
{
return false;
}

if (folder_type == FT_ROOT_INVENTORY)
{
return true;
}

// Essential folders are those needed for basic viewer operation:
// singleton system folders and ensemble (outfit) folders
return lookupIsSingletonType(folder_type) || lookupIsEnsembleType(folder_type);
}

// static
LLAssetType::EType LLFolderType::folderTypeToAssetType(LLFolderType::EType folder_type)
{
Expand Down
2 changes: 2 additions & 0 deletions indra/llinventory/llfoldertype.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class LL_COMMON_API LLFolderType
static bool lookupIsAutomaticType(EType folder_type);
static bool lookupIsSingletonType(EType folder_type);
static bool lookupIsEnsembleType(EType folder_type);
// Returns true if this folder type should be fully loaded during async inventory startup
static bool lookupIsEssentialType(EType folder_type);

static LLAssetType::EType folderTypeToAssetType(LLFolderType::EType folder_type);
static LLFolderType::EType assetTypeToFolderType(LLAssetType::EType asset_type);
Expand Down
43 changes: 27 additions & 16 deletions indra/llui/llpanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,58 +489,69 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu
LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP);

LLXMLNodePtr referenced_xml;
std::string xml_filename = mXMLFilename;

// if the panel didn't provide a filename, check the node
if (xml_filename.empty())
if (mXMLFilename.empty())
{
node->getAttributeString("filename", xml_filename);
setXMLFilename(xml_filename);
std::string temp_filename;
node->getAttributeString("filename", temp_filename);
setXMLFilename(temp_filename);
}

// Cache singleton and filename to avoid repeated calls
LLUICtrlFactory* factory = LLUICtrlFactory::getInstance();

// Cache node name pointer to avoid repeated dereferencing
const LLStringTableEntry* node_name = node->getName();

// Cache registry to avoid repeated singleton access
const child_registry_t& registry = child_registry_t::instance();

LLXUIParser parser;

if (!xml_filename.empty())
if (!mXMLFilename.empty())
{
if (output_node)
{
//if we are exporting, we want to export the current xml
//not the referenced xml
parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName());
parser.readXUI(node, params, factory->getCurFileName());
Params output_params(params);
setupParamsForExport(output_params, parent);
output_node->setName(node->getName()->mString);
output_node->setName(node_name->mString);
parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params);
return true;
}

LLUICtrlFactory::instance().pushFileName(xml_filename);
factory->pushFileName(mXMLFilename);

LL_RECORD_BLOCK_TIME(FTM_EXTERNAL_PANEL_LOAD);
if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml))
if (!LLUICtrlFactory::getLayeredXMLNode(mXMLFilename, referenced_xml))
{
LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL;
LL_WARNS() << "Couldn't parse panel from: " << mXMLFilename << LL_ENDL;

return false;
}

parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName());
// Get filename after pushFileName
const std::string& updated_filename = factory->getCurFileName();
parser.readXUI(referenced_xml, params, updated_filename);

// add children using dimensions from referenced xml for consistent layout
setShape(params.rect);
LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance());
LLUICtrlFactory::createChildren(this, referenced_xml, registry);

LLUICtrlFactory::instance().popFileName();
factory->popFileName();
}

// ask LLUICtrlFactory for filename, since xml_filename might be empty
parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName());
parser.readXUI(node, params, factory->getCurFileName());

if (output_node)
{
Params output_params(params);
setupParamsForExport(output_params, parent);
output_node->setName(node->getName()->mString);
output_node->setName(node_name->mString);
parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params);
}

Expand All @@ -552,7 +563,7 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu
}

// add children
LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node);
LLUICtrlFactory::createChildren(this, node, registry, output_node);

// Connect to parent after children are built, because tab containers
// do a reshape() on their child panels, which requires that the children
Expand Down
1 change: 0 additions & 1 deletion indra/llui/lltextbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3477,7 +3477,6 @@ S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offs
void LLTextSegment::updateLayout(const LLTextBase& editor) {}
F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { return draw_rect.mLeft; }
bool LLTextSegment::canEdit() const { return false; }
bool LLTextSegment::getPermitsEmoji() const { return true; }
void LLTextSegment::unlinkFromDocument(LLTextBase*) {}
void LLTextSegment::linkToDocument(LLTextBase*) {}
const LLUIColor& LLTextSegment::getColor() const { static const LLUIColor white = LLUIColorTable::instance().getColor("White", LLColor4::white); return white; }
Expand Down
1 change: 0 additions & 1 deletion indra/llui/lltextbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ class LLTextSegment
virtual void updateLayout(const class LLTextBase& editor);
virtual F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
virtual bool canEdit() const;
virtual bool getPermitsEmoji() const;
virtual void unlinkFromDocument(class LLTextBase* editor);
virtual void linkToDocument(class LLTextBase* editor);

Expand Down
62 changes: 61 additions & 1 deletion indra/llui/llview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,28 @@ const std::string& LLView::getName() const
return mName.empty() ? no_name : mName;
}

void LLView::setName(const std::string& name)
{
if (name == mName)
{
return;
}

LLView* parent = mParentView;

if (parent && !mName.empty())
{
parent->mChildNameCache.erase(mName);
}

mName = name;

if (parent && !mName.empty())
{
parent->mChildNameCache[mName] = this;
}
}

void LLView::sendChildToFront(LLView* child)
{
// llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs
Expand Down Expand Up @@ -305,6 +327,12 @@ bool LLView::addChild(LLView* child, S32 tab_group)
// add to front of child list, as normal
mChildList.push_front(child);

// Add to name cache for fast lookup
if (child->hasName())
{
mChildNameCache[child->getName()] = child;
}

// add to tab order list
if (tab_group != 0)
{
Expand Down Expand Up @@ -344,6 +372,13 @@ void LLView::removeChild(LLView* child)
// if we are removing an item we are currently iterating over, that would be bad
llassert(!child->mInDraw);
mChildList.remove( child );

// Remove from name cache
if (child->hasName())
{
mChildNameCache.erase(child->getName());
}

child->mParentView = NULL;
child_tab_order_t::iterator found = mTabOrder.find(child);
if (found != mTabOrder.end())
Expand Down Expand Up @@ -1649,15 +1684,40 @@ LLView* LLView::findChildView(std::string_view name, bool recurse) const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;

// Check cache first for direct children - O(1) lookup instead of O(n)
if (!mChildNameCache.empty())
{
std::string lookup_key(name);
auto cache_it = mChildNameCache.find(lookup_key);
if (cache_it != mChildNameCache.end())
{
return cache_it->second;
}
}

// Look for direct children *first*
for (LLView* childp : mChildList)
{
llassert(childp);
if (childp->getName() == name)
const std::string& child_name = childp->getName();

if (child_name.empty())
{
if (name.empty())
{
return childp;
}
continue;
}

if (child_name == name)
{
// Cache the result for next lookup
mChildNameCache[child_name] = childp;
return childp;
}
}

if (recurse)
{
// Look inside each child as well.
Expand Down
7 changes: 6 additions & 1 deletion indra/llui/llview.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

#include <functional>
#include <list>
#include <unordered_map>

class LLSD;

Expand Down Expand Up @@ -236,7 +237,8 @@ class LLView
void setFollowsAll() { mReshapeFlags |= FOLLOWS_ALL; }

void setSoundFlags(U8 flags) { mSoundFlags = flags; }
void setName(std::string name) { mName = name; }
void setName(const std::string& name);
bool hasName() const { return !mName.empty(); }
void setUseBoundingRect( bool use_bounding_rect );
bool getUseBoundingRect() const;

Expand Down Expand Up @@ -588,6 +590,9 @@ class LLView
LLView* mParentView;
child_list_t mChildList;

// Cache for fast child lookup by name - O(1) instead of O(n)
mutable std::unordered_map<std::string, LLView*> mChildNameCache;

// location in pixels, relative to surrounding structure, bottom,left=0,0
bool mVisible;
LLRect mRect;
Expand Down
2 changes: 2 additions & 0 deletions indra/newview/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ set(viewer_SOURCE_FILES
llappearancemgr.cpp
llappviewer.cpp
llappviewerlistener.cpp
llasyncinventoryskeletonloader.cpp
llattachmentsmgr.cpp
llaudiosourcevo.cpp
llautoreplace.cpp
Expand Down Expand Up @@ -790,6 +791,7 @@ set(viewer_HEADER_FILES
llappearancemgr.h
llappviewer.h
llappviewerlistener.h
llasyncinventoryskeletonloader.h
llattachmentsmgr.h
llaudiosourcevo.h
llautoreplace.h
Expand Down
66 changes: 66 additions & 0 deletions indra/newview/app_settings/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3280,6 +3280,72 @@
<key>Value</key>
<integer>255</integer>
</map>
<key>ForceAsyncInventorySkeleton</key>
<map>
<key>Comment</key>
<string>Force viewer to skip legacy login inventory skeleton and rely on async AIS fetching.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>AsyncInventoryMaxConcurrentFetches</key>
<map>
<key>Comment</key>
<string>Maximum number of concurrent AIS fetches used during async inventory skeleton loading.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>U32</string>
<key>Value</key>
<integer>2</integer>
</map>
<key>AsyncInventoryNotifyMinInterval</key>
<map>
<key>Comment</key>
<string>Minimum seconds between inventory observer notifications while async inventory loading is active.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>0.05</real>
</map>
<key>AsyncInventoryCapsTimeout</key>
<map>
<key>Comment</key>
<string>Maximum seconds to wait for inventory capabilities during async skeleton loading.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>45.0</real>
</map>
<key>AsyncInventoryFetchTimeout</key>
<map>
<key>Comment</key>
<string>Maximum seconds for the entire async inventory skeleton fetch process.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>180.0</real>
</map>
<key>AsyncInventoryEssentialTimeout</key>
<map>
<key>Comment</key>
<string>Maximum seconds to wait for essential inventory folders during async loading.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>90.0</real>
</map>
<key>ForceLoginURL</key>
<map>
<key>Comment</key>
Expand Down
Loading