1
0
Эх сурвалжийг харах

Merge pull request #3203 from MarchingCube/alloc-layoutloop

Optimizations to the main layout loop
Steven Kirk 6 жил өмнө
parent
commit
31c0704891

+ 9 - 2
src/Avalonia.Base/AvaloniaObject.cs

@@ -210,7 +210,11 @@ namespace Avalonia
         /// <returns>The value.</returns>
         public object GetValue(AvaloniaProperty property)
         {
-            Contract.Requires<ArgumentNullException>(property != null);
+            if (property is null)
+            {
+                throw new ArgumentNullException(nameof(property));
+            }
+
             VerifyAccess();
 
             if (property.IsDirect)
@@ -231,7 +235,10 @@ namespace Avalonia
         /// <returns>The value.</returns>
         public T GetValue<T>(AvaloniaProperty<T> property)
         {
-            Contract.Requires<ArgumentNullException>(property != null);
+            if (property is null)
+            {
+                throw new ArgumentNullException(nameof(property));
+            }
 
             return (T)GetValue((AvaloniaProperty)property);
         }

+ 41 - 22
src/Avalonia.Base/AvaloniaProperty.cs

@@ -28,6 +28,8 @@ namespace Avalonia
         private readonly Dictionary<Type, PropertyMetadata> _metadata;
         private readonly Dictionary<Type, PropertyMetadata> _metadataCache = new Dictionary<Type, PropertyMetadata>();
 
+        private bool _hasMetadataOverrides;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
         /// </summary>
@@ -92,6 +94,9 @@ namespace Avalonia
             Id = source.Id;
             _defaultMetadata = source._defaultMetadata;
 
+            // Properties that have different owner can't use fast path for metadata.
+            _hasMetadataOverrides = true;
+
             if (metadata != null)
             {
                 _metadata.Add(ownerType, metadata);
@@ -446,31 +451,12 @@ namespace Avalonia
         ///
         public PropertyMetadata GetMetadata(Type type)
         {
-            Contract.Requires<ArgumentNullException>(type != null);
-
-            PropertyMetadata result;
-            Type currentType = type;
-
-            if (_metadataCache.TryGetValue(type, out result))
+            if (!_hasMetadataOverrides)
             {
-                return result;
+                return _defaultMetadata;
             }
 
-            while (currentType != null)
-            {
-                if (_metadata.TryGetValue(currentType, out result))
-                {
-                    _metadataCache[type] = result;
-
-                    return result;
-                }
-
-                currentType = currentType.GetTypeInfo().BaseType;
-            }
-
-            _metadataCache[type] = _defaultMetadata;
-
-            return _defaultMetadata;
+            return GetMetadataWithOverrides(type);
         }
 
         /// <summary>
@@ -535,6 +521,39 @@ namespace Avalonia
             metadata.Merge(baseMetadata, this);
             _metadata.Add(type, metadata);
             _metadataCache.Clear();
+
+            _hasMetadataOverrides = true;
+        }
+
+        private PropertyMetadata GetMetadataWithOverrides(Type type)
+        {
+            if (type is null)
+            {
+                throw new ArgumentNullException(nameof(type));
+            }
+
+            if (_metadataCache.TryGetValue(type, out PropertyMetadata result))
+            {
+                return result;
+            }
+
+            Type currentType = type;
+
+            while (currentType != null)
+            {
+                if (_metadata.TryGetValue(currentType, out result))
+                {
+                    _metadataCache[type] = result;
+
+                    return result;
+                }
+
+                currentType = currentType.GetTypeInfo().BaseType;
+            }
+
+            _metadataCache[type] = _defaultMetadata;
+
+            return _defaultMetadata;
         }
 
         [DebuggerHidden]

+ 5 - 2
src/Avalonia.Layout/LayoutHelper.cs

@@ -21,8 +21,11 @@ namespace Avalonia.Layout
         /// <returns>The control's size.</returns>
         public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints)
         {
-            double width = (control.Width > 0) ? control.Width : constraints.Width;
-            double height = (control.Height > 0) ? control.Height : constraints.Height;
+            var controlWidth = control.Width;
+            var controlHeight = control.Height;
+
+            double width = (controlWidth > 0) ? controlWidth : constraints.Width;
+            double height = (controlHeight > 0) ? controlHeight : constraints.Height;
             width = Math.Min(width, control.MaxWidth);
             width = Math.Max(width, control.MinWidth);
             height = Math.Min(height, control.MaxHeight);

+ 7 - 1
src/Avalonia.Layout/LayoutManager.cs

@@ -15,9 +15,15 @@ namespace Avalonia.Layout
     {
         private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
         private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
+        private readonly Action _executeLayoutPass;
         private bool _queued;
         private bool _running;
 
+        public LayoutManager()
+        {
+            _executeLayoutPass = ExecuteLayoutPass;
+        }
+
         /// <inheritdoc/>
         public void InvalidateMeasure(ILayoutable control)
         {
@@ -215,7 +221,7 @@ namespace Avalonia.Layout
         {
             if (!_queued && !_running)
             {
-                Dispatcher.UIThread.Post(ExecuteLayoutPass, DispatcherPriority.Layout);
+                Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout);
                 _queued = true;
             }
         }

+ 17 - 8
src/Avalonia.Layout/LayoutQueue.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
-using System.Linq;
 
 namespace Avalonia.Layout
 {
@@ -18,9 +17,11 @@ namespace Avalonia.Layout
             _shouldEnqueue = shouldEnqueue;
         }
 
-        private Func<T, bool> _shouldEnqueue;
-        private Queue<T> _inner = new Queue<T>();
-        private Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
+        private readonly Func<T, bool> _shouldEnqueue;
+        private readonly Queue<T> _inner = new Queue<T>();
+        private readonly Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
+        private readonly List<KeyValuePair<T, Info>> _notFinalizedBuffer = new List<KeyValuePair<T, Info>>();
+
         private int _maxEnqueueCountPerLoop = 1;
 
         public int Count => _inner.Count;
@@ -60,13 +61,19 @@ namespace Avalonia.Layout
 
         public void EndLoop()
         {
-            var notfinalized = _loopQueueInfo.Where(v => v.Value.Count >= _maxEnqueueCountPerLoop).ToArray();
+            foreach (KeyValuePair<T, Info> info in _loopQueueInfo)
+            {
+                if (info.Value.Count >= _maxEnqueueCountPerLoop)
+                {
+                    _notFinalizedBuffer.Add(info);
+                }
+            }
 
             _loopQueueInfo.Clear();
 
-            //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
-            //one more time as a final attempt
-            foreach (var item in notfinalized)
+            // Prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
+            // one more time as a final attempt.
+            foreach (var item in _notFinalizedBuffer)
             {
                 if (_shouldEnqueue(item.Key))
                 {
@@ -74,6 +81,8 @@ namespace Avalonia.Layout
                     _inner.Enqueue(item.Key);
                 }
             }
+
+            _notFinalizedBuffer.Clear();
         }
     }
 }

+ 39 - 12
src/Avalonia.Layout/Layoutable.cs

@@ -518,17 +518,25 @@ namespace Avalonia.Layout
                 var width = measured.Width;
                 var height = measured.Height;
 
-                if (!double.IsNaN(Width))
                 {
-                    width = Width;
+                    double widthCache = Width;
+
+                    if (!double.IsNaN(widthCache))
+                    {
+                        width = widthCache;
+                    }
                 }
 
                 width = Math.Min(width, MaxWidth);
                 width = Math.Max(width, MinWidth);
 
-                if (!double.IsNaN(Height))
                 {
-                    height = Height;
+                    double heightCache = Height;
+
+                    if (!double.IsNaN(heightCache))
+                    {
+                        height = heightCache;
+                    }
                 }
 
                 height = Math.Min(height, MaxHeight);
@@ -562,11 +570,19 @@ namespace Avalonia.Layout
             double width = 0;
             double height = 0;
 
-            foreach (ILayoutable child in this.GetVisualChildren())
+            var visualChildren = VisualChildren;
+            var visualCount = visualChildren.Count;
+
+            for (var i = 0; i < visualCount; i++)
             {
-                child.Measure(availableSize);
-                width = Math.Max(width, child.DesiredSize.Width);
-                height = Math.Max(height, child.DesiredSize.Height);
+                IVisual visual = visualChildren[i];
+
+                if (visual is ILayoutable layoutable)
+                {
+                    layoutable.Measure(availableSize);
+                    width = Math.Max(width, layoutable.DesiredSize.Width);
+                    height = Math.Max(height, layoutable.DesiredSize.Height);
+                }
             }
 
             return new Size(width, height);
@@ -594,6 +610,7 @@ namespace Avalonia.Layout
                 var verticalAlignment = VerticalAlignment;
                 var size = availableSizeMinusMargins;
                 var scale = GetLayoutScale();
+                var useLayoutRounding = UseLayoutRounding;
 
                 if (horizontalAlignment != HorizontalAlignment.Stretch)
                 {
@@ -607,7 +624,7 @@ namespace Avalonia.Layout
 
                 size = LayoutHelper.ApplyLayoutConstraints(this, size);
 
-                if (UseLayoutRounding)
+                if (useLayoutRounding)
                 {
                     size = new Size(
                         Math.Ceiling(size.Width * scale) / scale, 
@@ -641,7 +658,7 @@ namespace Avalonia.Layout
                         break;
                 }
 
-                if (UseLayoutRounding)
+                if (useLayoutRounding)
                 {
                     originX = Math.Floor(originX * scale) / scale;
                     originY = Math.Floor(originY * scale) / scale;
@@ -658,9 +675,19 @@ namespace Avalonia.Layout
         /// <returns>The actual size used.</returns>
         protected virtual Size ArrangeOverride(Size finalSize)
         {
-            foreach (ILayoutable child in this.GetVisualChildren().OfType<ILayoutable>())
+            var arrangeRect = new Rect(finalSize);
+
+            var visualChildren = VisualChildren;
+            var visualCount = visualChildren.Count;
+
+            for (var i = 0; i < visualCount; i++)
             {
-                child.Arrange(new Rect(finalSize));
+                IVisual visual = visualChildren[i];
+
+                if (visual is ILayoutable layoutable)
+                {
+                    layoutable.Arrange(arrangeRect);
+                }
             }
 
             return finalSize;