@@ -127,26 +127,30 @@ bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
127127 // HACK: downcast
128128 LLViewerInventoryCategory* c = (LLViewerInventoryCategory*)cat;
129129
130- bool descendents_match = true ;
130+ // Cache the category if it has a valid version number.
131+ // Categories with mismatched descendent counts are still cached so that
132+ // we preserve the folder structure. During async skeleton loading, these
133+ // categories will be marked for refresh (VERSION_UNKNOWN) if their counts
134+ // don't match, ensuring they get fetched from the server.
131135 if (c->getVersion () != LLViewerInventoryCategory::VERSION_UNKNOWN)
132136 {
133137 const S32 descendents_server = c->getDescendentCount ();
134138 const S32 descendents_actual = c->getViewerDescendentCount ();
135- descendents_match = (descendents_server == descendents_actual);
136139
137- if (!descendents_match )
140+ if (descendents_server != descendents_actual )
138141 {
139142 LL_DEBUGS (" AsyncInventory" ) << " Caching category with mismatched descendents"
140143 << " cat_id=" << c->getUUID ()
141144 << " name=\" " << c->getName () << " \" "
142145 << " server_descendents=" << descendents_server
143146 << " viewer_descendents=" << descendents_actual
147+ << " (will be marked for refresh on next login)"
144148 << LL_ENDL;
145149 }
146- }
147150
148- mCachedCatIDs .insert (c->getUUID ());
149- rv = true ;
151+ mCachedCatIDs .insert (c->getUUID ());
152+ rv = true ;
153+ }
150154 }
151155 return rv;
152156}
@@ -2192,6 +2196,20 @@ void LLInventoryModel::idleNotifyObservers()
21922196 // *FIX: Think I want this conditional or moved elsewhere...
21932197 handleResponses (true );
21942198
2199+ // Check if we have a pending notification from async inventory throttling
2200+ if (mAllowAsyncInventoryUpdates && mAsyncNotifyPending )
2201+ {
2202+ if (mAsyncNotifyTimer .getElapsedTimeF32 () >= mAsyncNotifyIntervalSec )
2203+ {
2204+ // Timer has expired, force notification
2205+ mAsyncNotifyPending = false ;
2206+ if (mModifyMask != LLInventoryObserver::NONE || (mChangedItemIDs .size () != 0 ))
2207+ {
2208+ notifyObservers ();
2209+ }
2210+ }
2211+ }
2212+
21952213 if (mLinksRebuildList .size () > 0 )
21962214 {
21972215 if (mModifyMask != LLInventoryObserver::NONE || (mChangedItemIDs .size () != 0 ))
@@ -2229,6 +2247,8 @@ void LLInventoryModel::notifyObservers()
22292247 {
22302248 if (mAsyncNotifyTimer .getElapsedTimeF32 () < mAsyncNotifyIntervalSec )
22312249 {
2250+ // Mark that we have a pending notification that will be delivered
2251+ // on the next idleNotifyObservers() call after the timer expires
22322252 mAsyncNotifyPending = true ;
22332253 return ;
22342254 }
@@ -2424,6 +2444,13 @@ void LLInventoryModel::cache(
24242444 INCLUDE_TRASH,
24252445 can_cache);
24262446
2447+ // Fallback pass: catch any categories/items that collectDescendentsIf missed.
2448+ // This can happen when:
2449+ // 1. Categories have VERSION_UNKNOWN (e.g., during async loading)
2450+ // 2. Parent-child tree has broken links (orphaned folders)
2451+ // 3. Categories with mismatched descendent counts weren't added to mCachedCatIDs
2452+ // Without this pass, we'd lose 80k+ folders in deeply nested inventories.
2453+ // (I feel like this might be surfacing a bug somewhere...)
24272454 std::set<LLUUID> cached_category_ids;
24282455 std::set<LLUUID> cached_item_ids;
24292456 for (auto & cat_ptr : categories)
@@ -2441,8 +2468,6 @@ void LLInventoryModel::cache(
24412468 }
24422469 }
24432470
2444- // Fallback pass: ensure every known category/item under the root is persisted
2445- // even if the parent/child map failed to enumerate it.
24462471 for (auto & entry : mCategoryMap )
24472472 {
24482473 LLViewerInventoryCategory* cat = entry.second ;
@@ -2482,9 +2507,13 @@ void LLInventoryModel::cache(
24822507 {
24832508 continue ;
24842509 }
2485- items.push_back (item);
2486- cached_item_ids.insert (item_id);
2510+ if (can_cache (NULL , item))
2511+ {
2512+ items.push_back (item);
2513+ cached_item_ids.insert (item_id);
2514+ }
24872515 }
2516+
24882517 // Use temporary file to avoid potential conflicts with other
24892518 // instances (even a 'read only' instance unzips into a file)
24902519 std::string temp_file = gDirUtilp ->getTempFilename ();
@@ -3269,19 +3298,26 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
32693298{
32703299 LL_INFOS (LOG_INV) << " LLInventoryModel::buildParentChildMap()" << LL_ENDL;
32713300
3272- // Rebuild from scratch so we do not accumulate duplicate children
3273- // across consecutive calls triggered during async skeleton hydration.
3274- std::for_each (
3275- mParentChildCategoryTree .begin (),
3276- mParentChildCategoryTree .end (),
3277- DeletePairedPointer ());
3278- mParentChildCategoryTree .clear ();
3301+ // Clear existing parent-child maps if they exist to avoid duplicate entries.
3302+ // This handles the case where buildParentChildMap is called multiple times
3303+ // during async skeleton loading (once without validation, once with).
3304+ if (!mParentChildCategoryTree .empty ())
3305+ {
3306+ std::for_each (
3307+ mParentChildCategoryTree .begin (),
3308+ mParentChildCategoryTree .end (),
3309+ DeletePairedPointer ());
3310+ mParentChildCategoryTree .clear ();
3311+ }
32793312
3280- std::for_each (
3281- mParentChildItemTree .begin (),
3282- mParentChildItemTree .end (),
3283- DeletePairedPointer ());
3284- mParentChildItemTree .clear ();
3313+ if (!mParentChildItemTree .empty ())
3314+ {
3315+ std::for_each (
3316+ mParentChildItemTree .begin (),
3317+ mParentChildItemTree .end (),
3318+ DeletePairedPointer ());
3319+ mParentChildItemTree .clear ();
3320+ }
32853321
32863322 mCategoryLock .clear ();
32873323 mItemLock .clear ();
@@ -3332,7 +3368,6 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
33323368 S32 i;
33333369 S32 lost = 0 ;
33343370 cat_array_t lost_cats;
3335- size_t tree_links_inserted = 0 ;
33363371 for (auto & cat : cats)
33373372 {
33383373 catsp = getUnlockedCatArray (cat->getParentUUID ());
@@ -3343,7 +3378,6 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
33433378 cat->getPreferredType () == LLFolderType::FT_ROOT_INVENTORY ))
33443379 {
33453380 catsp->push_back (cat);
3346- ++tree_links_inserted;
33473381 }
33483382 else
33493383 {
@@ -3398,7 +3432,6 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
33983432 if (catsp)
33993433 {
34003434 catsp->push_back (cat);
3401- ++tree_links_inserted;
34023435 }
34033436 else
34043437 {
@@ -3414,7 +3447,6 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
34143447 // have to do is iterate over the items and put them in the right
34153448 // place.
34163449 item_array_t items;
3417- size_t item_links_inserted = 0 ;
34183450 if (!mItemMap .empty ())
34193451 {
34203452 LLPointer<LLViewerInventoryItem> item;
@@ -3432,7 +3464,6 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
34323464 if (itemsp)
34333465 {
34343466 itemsp->push_back (item);
3435- ++item_links_inserted;
34363467 }
34373468 else
34383469 {
@@ -3450,7 +3481,6 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
34503481 if (itemsp)
34513482 {
34523483 itemsp->push_back (item);
3453- ++item_links_inserted;
34543484 }
34553485 else
34563486 {
@@ -3491,19 +3521,13 @@ void LLInventoryModel::buildParentChildMap(bool run_validation)
34913521 }
34923522 }
34933523
3494- const size_t category_tree_nodes = mParentChildCategoryTree .size ();
3495- const size_t item_tree_nodes = mParentChildItemTree .size ();
3496- const S32 lost_items = lost;
3497-
34983524 LL_INFOS (" AsyncInventory" ) << " ParentChildMap summary"
34993525 << " category_map_size=" << mCategoryMap .size ()
3500- << " category_tree_nodes=" << category_tree_nodes
3501- << " category_links=" << tree_links_inserted
3526+ << " category_tree_nodes=" << mParentChildCategoryTree .size ()
35023527 << " item_map_size=" << mItemMap .size ()
3503- << " item_tree_nodes=" << item_tree_nodes
3504- << " item_links=" << item_links_inserted
3528+ << " item_tree_nodes=" << mParentChildItemTree .size ()
35053529 << " lost_categories=" << lost_categories
3506- << " lost_items=" << lost_items
3530+ << " lost_items=" << lost
35073531 << LL_ENDL;
35083532
35093533 if (run_validation)
0 commit comments