Skip to content
Merged
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -664,17 +664,21 @@
// calls UpdateAdorner→TransformToAncestor→InvalidateMeasure on every
// pass, which schedules a new render via NeedsRecalc→PostRender,
// amplifying any forever-animation by ~17× (e.g. a perpetual busy
// spinner produces ~570 renders/sec instead of ~32). Clearing
// _layoutDirty before exit prevents stale-flag leak when the first
// adorner is later attached (oracle-panel correction, gemini 9/10).
// spinner produces ~570 renders/sec instead of ~32).
if (ElementMap.Count == 0)
{
_layoutDirty = false;
return;
}

if (!_layoutDirty) return; // existing dirty-bit guard from 5e7df8833 — keep
_layoutDirty = false;
// Non-empty layer: always run UpdateAdorner. A cached _layoutDirty flag
// cannot comprehensively observe every coordinate-space change that
// requires re-walking adorners (ancestor RenderTransform changes,
// ContentPresenter re-templating that swaps the subtree between an
// adorned element and the AdornerLayer parent without firing layout
// on the adorned element itself, ancestor scroll, layer-parent
// changes, ArrangeDirty propagation, stale-element cleanup, etc.).
// The transform/size/clip-change gates inside UpdateElementAdorners
// still suppress redundant Adorner.InvalidateMeasure calls.
UpdateAdorner(null);
}

Expand Down Expand Up @@ -969,55 +973,82 @@
return;
}

// Reuse pooled list to avoid per-call ArrayList allocation.
_removeList ??= new List<UIElement>(4);
_removeList.Clear();
List<UIElement> removeList = _removeList;
// Lease the pooled removeList: null the field so a re-entrant
// UpdateAdorner (triggered when a custom Adorner's InvalidateMeasure/
// InvalidateVisual override calls back into AdornerLayer.Add/Remove)
// allocates its own buffer instead of clobbering ours. Same pattern
// as _zOrderValuesSnapshotBuffer in Measure/ArrangeOverride.
List<UIElement> removeList = _removeList ?? new List<UIElement>(4);
_removeList = null;
removeList.Clear();

if (element != null)
{
// Make sure element is still beneath the adorner decorator
if (!element.IsDescendantOf(adornerLayerParent))
{
removeList.Add(element);
}
else
{
UpdateElementAdorners(element);
}
}
else
// Lease the pooled keys snapshot buffer for the same reason.
UIElement[] keysBuffer = null;
int leasedKeysCount = 0;

try
{
ICollection keyCollection = ElementMap.Keys;
int keysCount = keyCollection.Count;
// Reuse a grow-only snapshot buffer; min capacity 8.
if (_keysSnapshotBuffer == null || _keysSnapshotBuffer.Length < keysCount)
_keysSnapshotBuffer = new UIElement[Math.Max(keysCount, 8)];
keyCollection.CopyTo(_keysSnapshotBuffer, 0); // static snapshot to prevent enumerator exceptions

for (int i = 0; i < keysCount; i++)
if (element != null)
{
UIElement elTemp = _keysSnapshotBuffer[i];

// Make sure element is still beneath the adorner decorator
if (!elTemp.IsDescendantOf(adornerLayerParent))
if (!element.IsDescendantOf(adornerLayerParent))
{
removeList.Add(elTemp);
removeList.Add(element);
}
else
{
UpdateElementAdorners(elTemp);
UpdateElementAdorners(element);
}
}
else
{
ICollection keyCollection = ElementMap.Keys;
int keysCount = keyCollection.Count;

// Clear used slots to release UIElement refs; prevents the buffer from
// retaining strong references to elements after this call returns.
Array.Clear(_keysSnapshotBuffer, 0, keysCount);
}
keysBuffer = _keysSnapshotBuffer;
_keysSnapshotBuffer = null;
if (keysBuffer == null || keysBuffer.Length < keysCount)
keysBuffer = new UIElement[Math.Max(keysCount, 8)];
keyCollection.CopyTo(keysBuffer, 0); // static snapshot to prevent enumerator exceptions
leasedKeysCount = keysCount;

for (int i = 0; i < keysCount; i++)
{
UIElement elTemp = keysBuffer[i];

// Make sure element is still beneath the adorner decorator
if (!elTemp.IsDescendantOf(adornerLayerParent))
{
removeList.Add(elTemp);
}
else
{
UpdateElementAdorners(elTemp);
}
}
}

for (int i = 0; i < removeList.Count; i++)
for (int i = 0; i < removeList.Count; i++)
{
Clear(removeList[i]);
}
}
finally
{
Clear(removeList[i]);
// Clear used slots in the keys buffer before returning it to the
// pool so it doesn't retain UIElement references across calls.
if (keysBuffer != null)
{
Array.Clear(keysBuffer, 0, leasedKeysCount);
if (_keysSnapshotBuffer == null || _keysSnapshotBuffer.Length < keysBuffer.Length)
_keysSnapshotBuffer = keysBuffer;
}

// Return removeList to the pool only if a nested call hasn't
// already produced a (potentially larger) replacement.
removeList.Clear();
if (_removeList == null)
_removeList = removeList;
}
}

Expand Down Expand Up @@ -1237,7 +1268,7 @@
// per-element LayoutUpdated event; cleared at the top of UpdateAdorner so a
// re-entrant fire during the walk re-arms for the next pass.
// Starts true so the very first layout pass is never skipped.
private bool _layoutDirty = true;

Check failure on line 1271 in src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs

View workflow job for this annotation

GitHub Actions / Build WPF (arm64)

The field 'AdornerLayer._layoutDirty' is assigned but its value is never used

Check failure on line 1271 in src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs

View workflow job for this annotation

GitHub Actions / Build WPF (arm64)

The field 'AdornerLayer._layoutDirty' is assigned but its value is never used

Check failure on line 1271 in src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs

View workflow job for this annotation

GitHub Actions / Build WPF (x64)

The field 'AdornerLayer._layoutDirty' is assigned but its value is never used

Check failure on line 1271 in src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/AdornerLayer.cs

View workflow job for this annotation

GitHub Actions / Build WPF (x64)

The field 'AdornerLayer._layoutDirty' is assigned but its value is never used
// Set of elements for which we have a LayoutUpdated subscription.
// Maintained to ensure subscribe/unsubscribe are balanced.
private HashSet<UIElement> _subscribedElements;
Expand Down
Loading