Browse Source

Merge branch 'master' into fix-cue-parse-xamlil

pull/2324/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
1f60decb13
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      src/Avalonia.Layout/LayoutManager.cs
  2. 79
      src/Avalonia.Layout/LayoutQueue.cs
  3. 4
      src/Avalonia.Layout/Properties/AssemblyInfo.cs
  4. 68
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  5. 119
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  6. 196
      tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs

15
src/Avalonia.Layout/LayoutManager.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Logging;
using Avalonia.Threading;
@ -13,8 +12,8 @@ namespace Avalonia.Layout
/// </summary>
public class LayoutManager : ILayoutManager
{
private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
private bool _queued;
private bool _running;
@ -80,6 +79,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 +100,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 +117,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 +138,7 @@ namespace Avalonia.Layout
private void ExecuteArrangePass()
{
while (_toArrange.Count > 0 && _toMeasure.Count == 0)
while (_toArrange.Count > 0)
{
var control = _toArrange.Dequeue();

79
src/Avalonia.Layout/LayoutQueue.cs

@ -0,0 +1,79 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Layout
{
internal class LayoutQueue<T> : IReadOnlyCollection<T>
{
private struct Info
{
public bool Active;
public int Count;
}
public LayoutQueue(Func<T, bool> shouldEnqueue)
{
_shouldEnqueue = shouldEnqueue;
}
private Func<T, bool> _shouldEnqueue;
private Queue<T> _inner = new Queue<T>();
private Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
private int _maxEnqueueCountPerLoop = 1;
public int Count => _inner.Count;
public IEnumerator<T> GetEnumerator() => (_inner as IEnumerable<T>).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
public T Dequeue()
{
var result = _inner.Dequeue();
if (_loopQueueInfo.TryGetValue(result, out var info))
{
info.Active = false;
_loopQueueInfo[result] = info;
}
return result;
}
public void Enqueue(T item)
{
_loopQueueInfo.TryGetValue(item, out var info);
if (!info.Active && info.Count < _maxEnqueueCountPerLoop)
{
_inner.Enqueue(item);
_loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
}
}
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))
{
_loopQueueInfo[item.Key] = new Info() { Active = true, Count = item.Value.Count + 1 };
_inner.Enqueue(item.Key);
}
}
}
}
}

4
src/Avalonia.Layout/Properties/AssemblyInfo.cs

@ -1,6 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")]

68
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -267,6 +267,74 @@ namespace Avalonia.Controls.UnitTests
Assert.True(true);
}
[Fact]
public void LayoutManager_Should_Measure_Arrange_All()
{
var virtualizationMode = ItemVirtualizationMode.Simple;
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var items = new AvaloniaList<string>(Enumerable.Range(1, 7).Select(v => v.ToString()));
var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight };
wnd.IsVisible = true;
var target = new ListBox();
wnd.Content = target;
var lm = wnd.LayoutManager;
target.Height = 110;
target.Width = 50;
target.DataContext = items;
target.VirtualizationMode = virtualizationMode;
target.ItemTemplate = new FuncDataTemplate<object>(c =>
{
var tb = new TextBlock() { Height = 10, Width = 30 };
tb.Bind(TextBlock.TextProperty, new Data.Binding());
return tb;
}, true);
lm.ExecuteInitialLayoutPass(wnd);
target.Items = items;
lm.ExecuteLayoutPass();
items.Insert(3, "3+");
lm.ExecuteLayoutPass();
items.Insert(4, "4+");
lm.ExecuteLayoutPass();
//RESET
items.Clear();
foreach (var i in Enumerable.Range(1, 7))
{
items.Add(i.ToString());
}
//working bit better with this line no outof memory or remaining to arrange/measure ???
//lm.ExecuteLayoutPass();
items.Insert(2, "2+");
lm.ExecuteLayoutPass();
//after few more layout cycles layoutmanager shouldn't hold any more visual for measure/arrange
lm.ExecuteLayoutPass();
lm.ExecuteLayoutPass();
var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
var toMeasure = lm.GetType().GetField("_toMeasure", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
var toArrange = lm.GetType().GetField("_toArrange", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
Assert.Equal(0, toMeasure.Count());
Assert.Equal(0, toArrange.Count());
}
}
private FuncControlTemplate ListBoxTemplate()
{
return new FuncControlTemplate<ListBox>(parent =>

119
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@ -1,11 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.UnitTests;
using System;
using Xunit;
using System.Collections.Generic;
namespace Avalonia.Layout.UnitTests
{
@ -74,7 +73,6 @@ namespace Avalonia.Layout.UnitTests
}
};
var order = new List<ILayoutable>();
Size MeasureOverride(ILayoutable control, Size size)
{
@ -110,7 +108,6 @@ namespace Avalonia.Layout.UnitTests
}
};
var order = new List<ILayoutable>();
Size MeasureOverride(ILayoutable control, Size size)
{
@ -196,9 +193,9 @@ namespace Avalonia.Layout.UnitTests
Width = 100,
Height = 100,
};
var arrangeSize = default(Size);
root.DoArrangeOverride = (_, s) =>
{
arrangeSize = s;
@ -207,7 +204,7 @@ namespace Avalonia.Layout.UnitTests
root.LayoutManager.ExecuteInitialLayoutPass(root);
Assert.Equal(new Size(100, 100), arrangeSize);
root.Width = 120;
root.LayoutManager.ExecuteLayoutPass();
@ -238,7 +235,111 @@ namespace Avalonia.Layout.UnitTests
border.Height = 100;
root.LayoutManager.ExecuteLayoutPass();
Assert.Equal(new Size(100, 100), panel.DesiredSize);
Assert.Equal(new Size(100, 100), panel.DesiredSize);
}
[Fact]
public void LayoutManager_Should_Prevent_Infinite_Loop_On_Measure()
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
control.Measured = false;
int cnt = 0;
int maxcnt = 100;
control.DoMeasureOverride = (l, s) =>
{
//emulate a problem in the logic of a control that triggers
//invalidate measure during measure
//it can lead to an infinite loop in layoutmanager
if (++cnt < maxcnt)
{
control.InvalidateMeasure();
}
return new Size(100, 100);
};
control.InvalidateMeasure();
root.LayoutManager.ExecuteLayoutPass();
Assert.True(cnt < 100);
}
[Fact]
public void LayoutManager_Should_Prevent_Infinite_Loop_On_Arrange()
{
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
root.LayoutManager.ExecuteInitialLayoutPass(root);
control.Arranged = false;
int cnt = 0;
int maxcnt = 100;
control.DoArrangeOverride = (l, s) =>
{
//emulate a problem in the logic of a control that triggers
//invalidate measure during arrange
//it can lead to infinity loop in layoutmanager
if (++cnt < maxcnt)
{
control.InvalidateArrange();
}
return new Size(100, 100);
};
control.InvalidateArrange();
root.LayoutManager.ExecuteLayoutPass();
Assert.True(cnt < 100);
}
[Fact]
public void LayoutManager_Should_Properly_Arrange_Visuals_Even_When_There_Are_Issues_With_Previous_Arranged()
{
var nonArrageableTargets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
var targets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
StackPanel panel;
var root = new LayoutTestRoot
{
Child = panel = new StackPanel()
};
panel.Children.AddRange(nonArrageableTargets);
panel.Children.AddRange(targets);
root.LayoutManager.ExecuteInitialLayoutPass(root);
foreach (var c in panel.Children.OfType<LayoutTestControl>())
{
c.Measured = c.Arranged = false;
c.InvalidateMeasure();
}
foreach (var c in nonArrageableTargets)
{
c.DoArrangeOverride = (l, s) =>
{
//emulate a problem in the logic of a control that triggers
//invalidate measure during arrange
c.InvalidateMeasure();
return new Size(100, 100);
};
}
root.LayoutManager.ExecuteLayoutPass();
//altough nonArrageableTargets has rubbish logic and can't be measured/arranged properly
//layoutmanager should process properly other visuals
Assert.All(targets, c => Assert.True(c.Arranged));
}
}
}

196
tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs

@ -0,0 +1,196 @@
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Avalonia.Layout.UnitTests
{
public class LayoutQueueTests
{
[Fact]
public void Should_Enqueue()
{
var target = new LayoutQueue<string>(_ => true);
var refQueue = new Queue<string>();
var items = new[] { "1", "2", "3" };
foreach (var item in items)
{
target.Enqueue(item);
refQueue.Enqueue(item);
}
Assert.Equal(refQueue, target);
}
[Fact]
public void Should_Dequeue()
{
var target = new LayoutQueue<string>(_ => true);
var refQueue = new Queue<string>();
var items = new[] { "1", "2", "3" };
foreach (var item in items)
{
target.Enqueue(item);
refQueue.Enqueue(item);
}
while (refQueue.Count > 0)
{
Assert.Equal(refQueue.Dequeue(), target.Dequeue());
}
}
[Fact]
public void Should_Enqueue_UniqueElements()
{
var target = new LayoutQueue<string>(_ => true);
var items = new[] { "1", "2", "3", "1" };
foreach (var item in items)
{
target.Enqueue(item);
}
Assert.Equal(3, target.Count);
Assert.Equal(items.Take(3), target);
}
[Fact]
public void Shouldnt_Enqueue_More_Than_Limit_In_Loop()
{
var target = new LayoutQueue<string>(_ => true);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
}
[Fact]
public void Shouldnt_Count_Unique_Enqueue_For_Limit_In_Loop()
{
var target = new LayoutQueue<string>(_ => true);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
}
[Fact]
public void Should_Enqueue_When_Condition_True_After_Loop_When_Limit_Met()
{
var target = new LayoutQueue<string>(_ => true);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added to queue
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
target.EndLoop();
//after loop should be added once
Assert.Equal(1, target.Count);
Assert.Equal("Foo", target.First());
}
[Fact]
public void Shouldnt_Enqueue_When_Condition_False_After_Loop_When_Limit_Met()
{
var target = new LayoutQueue<string>(_ => false);
//1
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.BeginLoop(3);
target.Dequeue();
//2
target.Enqueue("Foo");
target.Dequeue();
//3
target.Enqueue("Foo");
Assert.Equal(1, target.Count);
target.Dequeue();
//4 more than limit shouldn't be added
target.Enqueue("Foo");
Assert.Equal(0, target.Count);
target.EndLoop();
Assert.Equal(0, target.Count);
}
}
}
Loading…
Cancel
Save