From 461d1f3f4c1479a79fc88edb1a24227723467271 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 10 Jun 2024 13:24:00 +0300 Subject: [PATCH] feat: Basic implementation for detecting layout cycles (cherry picked from commit f2b46b39e4e2e6635e5ee005c1d14b66696869fb) --- .../Microsoft.UI.Xaml/DebugSettings.cs | 14 -------------- src/Uno.UI/UI/Xaml/DebugSettings.cs | 2 ++ .../FrameworkElement.Layout.crossruntime.cs | 19 +++++++++++++++++++ .../UI/Xaml/UIElement.Layout.crossruntime.cs | 10 ++++++++++ src/Uno.UI/UI/Xaml/UIElement.cs | 13 +++++++++++++ 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/DebugSettings.cs b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/DebugSettings.cs index cf5b652221c4..a65445030aac 100644 --- a/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/DebugSettings.cs +++ b/src/Uno.UI/Generated/3.0.0.0/Microsoft.UI.Xaml/DebugSettings.cs @@ -39,20 +39,6 @@ public bool IsXamlResourceReferenceTracingEnabled } } #endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ - [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] - public global::Microsoft.UI.Xaml.LayoutCycleTracingLevel LayoutCycleTracingLevel - { - get - { - throw new global::System.NotImplementedException("The member LayoutCycleTracingLevel DebugSettings.LayoutCycleTracingLevel is not implemented. For more information, visit https://aka.platform.uno/notimplemented#m=LayoutCycleTracingLevel%20DebugSettings.LayoutCycleTracingLevel"); - } - set - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Microsoft.UI.Xaml.DebugSettings", "LayoutCycleTracingLevel DebugSettings.LayoutCycleTracingLevel"); - } - } -#endif #if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ [global::Uno.NotImplemented("__ANDROID__", "__IOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public global::Microsoft.UI.Xaml.LayoutCycleDebugBreakLevel LayoutCycleDebugBreakLevel diff --git a/src/Uno.UI/UI/Xaml/DebugSettings.cs b/src/Uno.UI/UI/Xaml/DebugSettings.cs index d9903f776bac..c8526c3745b4 100644 --- a/src/Uno.UI/UI/Xaml/DebugSettings.cs +++ b/src/Uno.UI/UI/Xaml/DebugSettings.cs @@ -5,6 +5,8 @@ namespace Microsoft.UI.Xaml { public sealed partial class DebugSettings { + public LayoutCycleTracingLevel LayoutCycleTracingLevel { get; set; } + [Uno.NotImplemented] public bool EnableFrameRateCounter { get; set; } diff --git a/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs b/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs index 05f16b19fd15..e422b19c28d0 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs @@ -168,6 +168,11 @@ void MeasureCoreWithTrace(Size availableSize) private void InnerMeasureCore(Size availableSize) { + if (_traceLayoutCycle && this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().LogError($"[LayoutCycleTracing] Measuring {this},{this.GetDebugName()} with availableSize {availableSize}."); + } + // Uno TODO //CLayoutManager* pLayoutManager = VisualTree::GetLayoutManagerForElement(this); //bool bInLayoutTransition = pLayoutManager ? pLayoutManager->GetTransitioningElement() == this : false; @@ -351,6 +356,11 @@ private void InnerMeasureCore(Size availableSize) desiredSize.Height = LayoutRound(desiredSize.Height); } + if (_traceLayoutCycle && this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().LogError($"[LayoutCycleTracing] Measured {this},{this.GetDebugName()}: desiredSize is {desiredSize}."); + } + #if __SKIA__ if (desiredSize != DesiredSize) #endif @@ -432,6 +442,10 @@ void ArrangeCoreWithTrace(Rect finalRect) private void InnerArrangeCore(Rect finalRect) { _logDebug?.Debug($"{DepthIndentation}{FormatDebugName()}: InnerArrangeCore({finalRect})"); + if (_traceLayoutCycle && this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().LogError($"[LayoutCycleTracing] Arranging {this},{this.GetDebugName()} with finalRect {finalRect}."); + } // Uno TODO: //CLayoutManager* pLayoutManager = VisualTree::GetLayoutManagerForElement(this); @@ -719,6 +733,11 @@ private void InnerArrangeCore(Rect finalRect) var clippedFrame = GetClipRect(needsClipBounds, visualOffset, finalRect, new Size(maxWidth, maxHeight), margin); ArrangeNative(visualOffset, clippedFrame); + if (_traceLayoutCycle && this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().LogError($"[LayoutCycleTracing] Arranged {this},{this.GetDebugName()}: {clippedFrame}."); + } + AfterArrange(); } diff --git a/src/Uno.UI/UI/Xaml/UIElement.Layout.crossruntime.cs b/src/Uno.UI/UI/Xaml/UIElement.Layout.crossruntime.cs index 6b66e6ddc705..1712dac4c45f 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.Layout.crossruntime.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.Layout.crossruntime.cs @@ -27,6 +27,11 @@ public void InvalidateMeasure() return; } + if (_traceLayoutCycle && this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().LogError($"[LayoutCycleTracing] InvalidateMeasure {this},{this.GetDebugName()}"); + } + SetLayoutFlags(LayoutFlag.MeasureDirty); if (FeatureConfiguration.UIElement.UseInvalidateMeasurePath && !IsMeasureDirtyPathDisabled) @@ -81,6 +86,11 @@ public void InvalidateArrange() return; // Already dirty } + if (_traceLayoutCycle && this.Log().IsEnabled(LogLevel.Error)) + { + this.Log().LogError($"[LayoutCycleTracing] InvalidateArrange {this},{this.GetDebugName()}"); + } + SetLayoutFlags(LayoutFlag.ArrangeDirty); if (FeatureConfiguration.UIElement.UseInvalidateArrangePath && !IsArrangeDirtyPathDisabled) diff --git a/src/Uno.UI/UI/Xaml/UIElement.cs b/src/Uno.UI/UI/Xaml/UIElement.cs index 5150663a3629..6b2f82b80f5f 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.cs @@ -44,6 +44,7 @@ namespace Microsoft.UI.Xaml { public partial class UIElement : DependencyObject, IXUidProvider, IUIElement { + private protected static bool _traceLayoutCycle; private static readonly TypedEventHandler OnBringIntoViewRequestedHandler = (UIElement sender, BringIntoViewRequestedEventArgs args) => sender.OnBringIntoViewRequested(args); @@ -824,6 +825,15 @@ private static void InnerUpdateLayout(UIElement root) for (var i = MaxLayoutIterations; i > 0; i--) { + if (i <= 10 && Application.Current is { DebugSettings.LayoutCycleTracingLevel: not LayoutCycleTracingLevel.None }) + { + _traceLayoutCycle = true; + if (typeof(UIElement).Log().IsEnabled(LogLevel.Error)) + { + typeof(UIElement).Log().LogError($"[LayoutCycleTracing] Low on countdown ({i})."); + } + } + if (root.IsMeasureDirtyOrMeasureDirtyPath) { root.Measure(bounds.Size); @@ -863,17 +873,20 @@ private static void InnerUpdateLayout(UIElement root) !root.IsArrangeDirtyOrArrangeDirtyPath && !eventManager.HasPendingViewportChangedEvents) { + _traceLayoutCycle = false; return; } } #else else { + _traceLayoutCycle = false; return; } #endif } + _traceLayoutCycle = false; throw new InvalidOperationException("Layout cycle detected."); #endif }