Browse Source

Merge pull request #3203 from MarchingCube/alloc-layoutloop

Optimizations to the main layout loop
pull/3227/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
31c0704891
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      src/Avalonia.Base/AvaloniaObject.cs
  2. 63
      src/Avalonia.Base/AvaloniaProperty.cs
  3. 7
      src/Avalonia.Layout/LayoutHelper.cs
  4. 8
      src/Avalonia.Layout/LayoutManager.cs
  5. 25
      src/Avalonia.Layout/LayoutQueue.cs
  6. 51
      src/Avalonia.Layout/Layoutable.cs

11
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);
}

63
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]

7
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);

8
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;
}
}

25
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();
}
}
}

51
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;

Loading…
Cancel
Save