diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index f3540ea631..3d390f46e6 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; using Avalonia.Logging; using Avalonia.Threading; @@ -13,8 +15,85 @@ namespace Avalonia.Layout /// public class LayoutManager : ILayoutManager { - private readonly Queue _toMeasure = new Queue(); - private readonly Queue _toArrange = new Queue(); + private class LayoutQueue : IReadOnlyCollection + { + private class Info + { + public bool Active; + public int Count; + } + + public LayoutQueue(Func shouldEnqueue) + { + _shouldEnqueue = shouldEnqueue; + } + + private Func _shouldEnqueue; + private Queue _inner = new Queue(); + private Dictionary _loopQueueInfo = new Dictionary(); + private int _maxEnqueueCountPerLoop = 1; + + public int Count => _inner.Count; + + public IEnumerator GetEnumerator() => (_inner as IEnumerable).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); + + public T Dequeue() + { + var result = _inner.Dequeue(); + + if (_loopQueueInfo.TryGetValue(result, out var info)) + { + info.Active = false; + } + + return result; + } + + public void Enqueue(T item) + { + if (!_loopQueueInfo.TryGetValue(item, out var info)) + { + _loopQueueInfo[item] = info = new Info(); + } + + if (!info.Active && info.Count < _maxEnqueueCountPerLoop) + { + _inner.Enqueue(item); + info.Active = true; + info.Count++; + } + } + + public void BeginLoop(int maxEnqueueCountPerLoop) + { + _maxEnqueueCountPerLoop = maxEnqueueCountPerLoop; + } + + public void EndLoop() + { + var notfinalized = _loopQueueInfo.Where(v => v.Value.Count == _maxEnqueueCountPerLoop).ToArray(); + + _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) + { + if (_shouldEnqueue(item.Key)) + { + item.Value.Active = true; + item.Value.Count++; + _loopQueueInfo[item.Key] = item.Value; + _inner.Enqueue(item.Key); + } + } + } + } + + private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); + private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private bool _queued; private bool _running; @@ -80,6 +159,9 @@ namespace Avalonia.Layout var stopwatch = new System.Diagnostics.Stopwatch(); stopwatch.Start(); + _toMeasure.BeginLoop(MaxPasses); + _toArrange.BeginLoop(MaxPasses); + try { for (var pass = 0; pass < MaxPasses; ++pass) @@ -98,6 +180,9 @@ namespace Avalonia.Layout _running = false; } + _toMeasure.EndLoop(); + _toArrange.EndLoop(); + stopwatch.Stop(); Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); } @@ -112,7 +197,7 @@ namespace Avalonia.Layout Arrange(root); // Running the initial layout pass may have caused some control to be invalidated - // so run a full layout pass now (this usually due to scrollbars; its not known + // so run a full layout pass now (this usually due to scrollbars; its not known // whether they will need to be shown until the layout pass has run and if the // first guess was incorrect the layout will need to be updated). ExecuteLayoutPass(); @@ -133,7 +218,7 @@ namespace Avalonia.Layout private void ExecuteArrangePass() { - while (_toArrange.Count > 0 && _toMeasure.Count == 0) + while (_toArrange.Count > 0) { var control = _toArrange.Dequeue();