Browse Source

Add tests for the new RenderLoop logic.

pull/1715/head
Jeremy Koritzinsky 8 years ago
parent
commit
8357bc86b0
  1. 16
      src/Avalonia.Visuals/Rendering/RenderLoop.cs
  2. 64
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  3. 119
      tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs

16
src/Avalonia.Visuals/Rendering/RenderLoop.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Avalonia.Logging;
using Avalonia.Threading;
@ -89,18 +90,7 @@ namespace Avalonia.Rendering
{
try
{
var needsUpdate = false;
foreach (var i in _items)
{
if (i.NeedsUpdate)
{
needsUpdate = true;
break;
}
}
if (needsUpdate)
if (_items.Any(item => item.NeedsUpdate))
{
await _dispatcher.InvokeAsync(() =>
{
@ -108,7 +98,7 @@ namespace Avalonia.Rendering
{
i.Update(tickCount);
}
}).ConfigureAwait(false);
}, DispatcherPriority.Render).ConfigureAwait(false);
}
foreach (var i in _items)

64
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media;
@ -22,27 +22,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
public class DeferredRendererTests
{
[Fact]
public void First_Frame_Calls_UpdateScene_On_Dispatcher()
{
var root = new TestRoot();
var dispatcher = new Mock<IDispatcher>();
dispatcher.Setup(x => x.Post(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback<Action, DispatcherPriority>((a, p) => a());
CreateTargetAndRunFrame(root, dispatcher: dispatcher.Object);
dispatcher.Verify(x =>
x.Post(
It.Is<Action>(a => a.Method.Name == "UpdateScene"),
DispatcherPriority.Render));
}
[Fact]
public void First_Frame_Calls_SceneBuilder_UpdateAll()
{
var loop = new Mock<IRenderTimer>();
var root = new TestRoot();
var sceneBuilder = MockSceneBuilder(root);
@ -54,6 +36,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
[Fact]
public void Frame_Does_Not_Call_SceneBuilder_If_No_Dirty_Controls()
{
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
var root = new TestRoot();
var sceneBuilder = MockSceneBuilder(root);
@ -63,8 +46,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
sceneBuilder: sceneBuilder.Object);
target.Start();
IgnoreFirstFrame(loop, sceneBuilder);
RunFrame(loop);
IgnoreFirstFrame(target, sceneBuilder);
RunFrame(target);
sceneBuilder.Verify(x => x.UpdateAll(It.IsAny<Scene>()), Times.Never);
sceneBuilder.Verify(x => x.Update(It.IsAny<Scene>(), It.IsAny<Visual>()), Times.Never);
@ -73,8 +56,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
[Fact]
public void Should_Update_Dirty_Controls_In_Order()
{
var loop = new Mock<IRenderLoop>();
var dispatcher = new ImmediateDispatcher();
var loop = new Mock<IRenderLoop>();
Border border;
Decorator decorator;
@ -98,7 +81,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
dispatcher: dispatcher);
target.Start();
IgnoreFirstFrame(loop, sceneBuilder);
IgnoreFirstFrame(target, sceneBuilder);
target.AddDirty(border);
target.AddDirty(canvas);
target.AddDirty(root);
@ -108,7 +91,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
sceneBuilder.Setup(x => x.Update(It.IsAny<Scene>(), It.IsAny<IVisual>()))
.Callback<Scene, IVisual>((_, v) => result.Add(v));
RunFrame(loop);
RunFrame(target);
Assert.Equal(new List<IVisual> { root, decorator, border, canvas }, result);
}
@ -198,7 +181,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
[Fact]
public void Should_Create_Layer_For_Root()
{
var loop = new Mock<IRenderTimer>();
var root = new TestRoot();
var rootLayer = new Mock<IRenderTargetBitmapImpl>();
@ -239,19 +221,19 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>();
var target = CreateTargetAndRunFrame(root, loop: loop);
var timer = new Mock<IRenderTimer>();
var target = CreateTargetAndRunFrame(root, timer);
Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
var animation = new BehaviorSubject<double>(0.5);
border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
RunFrame(loop);
RunFrame(target);
Assert.Equal(new IVisual[] { root, border }, target.Layers.Select(x => x.LayerRoot));
animation.OnCompleted();
RunFrame(loop);
RunFrame(target);
Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
}
@ -280,8 +262,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>();
var target = CreateTargetAndRunFrame(root, loop: loop);
var timer = new Mock<IRenderTimer>();
var target = CreateTargetAndRunFrame(root, timer);
Assert.Single(target.Layers);
}
@ -345,19 +327,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering
private DeferredRenderer CreateTargetAndRunFrame(
TestRoot root,
Mock<IRenderLoop> loop = null,
Mock<IRenderTimer> timer = null,
ISceneBuilder sceneBuilder = null,
IDispatcher dispatcher = null)
{
loop = loop ?? new Mock<IRenderLoop>();
timer = timer ?? new Mock<IRenderTimer>();
dispatcher = dispatcher ?? new ImmediateDispatcher();
var target = new DeferredRenderer(
root,
loop.Object,
new RenderLoop(timer.Object, dispatcher),
sceneBuilder: sceneBuilder,
dispatcher: dispatcher ?? new ImmediateDispatcher());
dispatcher: dispatcher);
root.Renderer = target;
target.Start();
RunFrame(loop);
RunFrame(target);
return target;
}
@ -366,15 +349,16 @@ namespace Avalonia.Visuals.UnitTests.Rendering
return Mock.Get(renderer.Layers[layerRoot].Bitmap.Item.CreateDrawingContext(null));
}
private void IgnoreFirstFrame(Mock<IRenderLoop> loop, Mock<ISceneBuilder> sceneBuilder)
private void IgnoreFirstFrame(IRenderLoopTask task, Mock<ISceneBuilder> sceneBuilder)
{
RunFrame(loop);
RunFrame(task);
sceneBuilder.ResetCalls();
}
private void RunFrame(Mock<IRenderLoop> loop)
private void RunFrame(IRenderLoopTask task)
{
//loop.Raise(x => x.Tick += null, EventArgs.Empty);
task.Update(0);
task.Render();
}
private IRenderTargetBitmapImpl CreateLayer()

119
tests/Avalonia.Visuals.UnitTests/Rendering/RenderLoopTests.cs

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Rendering;
using Avalonia.Threading;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Rendering
{
public class RenderLoopTests
{
[Fact]
public void RenderLoop_Update_Runs_On_Dispatcher()
{
var dispatcher = new Mock<IDispatcher>();
bool inDispatcher = false;
dispatcher.Setup(
d => d.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) =>
{
inDispatcher = true;
a();
inDispatcher = false;
})
.Returns(Task.CompletedTask);
var timer = new Mock<IRenderTimer>();
var loop = new RenderLoop(timer.Object, dispatcher.Object);
var renderTask = new Mock<IRenderLoopTask>();
renderTask.Setup(t => t.NeedsUpdate).Returns(true);
renderTask.Setup(t => t.Update(It.IsAny<long>()))
.Callback((long _) => Assert.True(inDispatcher));
loop.Add(renderTask.Object);
timer.Raise(t => t.Tick += null, 0L);
renderTask.Verify(t => t.Update(It.IsAny<long>()), Times.Once());
}
[Fact]
public void RenderLoop_Does_Not_Update_When_No_Tasks_Need_Update()
{
var dispatcher = new Mock<IDispatcher>();
dispatcher.Setup(
d => d.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) => a())
.Returns(Task.CompletedTask);
var timer = new Mock<IRenderTimer>();
var loop = new RenderLoop(timer.Object, dispatcher.Object);
var renderTask = new Mock<IRenderLoopTask>();
renderTask.Setup(t => t.NeedsUpdate).Returns(false);
loop.Add(renderTask.Object);
timer.Raise(t => t.Tick += null, 0L);
renderTask.Verify(t => t.Update(It.IsAny<long>()), Times.Never());
}
[Fact]
public void RenderLoop_Render_Runs_Off_Dispatcher()
{
var dispatcher = new Mock<IDispatcher>();
bool inDispatcher = false;
dispatcher.Setup(
d => d.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) =>
{
inDispatcher = true;
a();
inDispatcher = false;
})
.Returns(Task.CompletedTask);
var timer = new Mock<IRenderTimer>();
var loop = new RenderLoop(timer.Object, dispatcher.Object);
var renderTask = new Mock<IRenderLoopTask>();
renderTask.Setup(t => t.NeedsUpdate).Returns(true);
renderTask.Setup(t => t.Render())
.Callback(() => Assert.False(inDispatcher));
loop.Add(renderTask.Object);
timer.Raise(t => t.Tick += null, 0L);
renderTask.Verify(t => t.Update(It.IsAny<long>()), Times.Once());
}
[Fact]
public void RenderLoop_Passes_Tick_Count_To_Update()
{
var dispatcher = new Mock<IDispatcher>();
dispatcher.Setup(
d => d.InvokeAsync(It.IsAny<Action>(), DispatcherPriority.Render))
.Callback((Action a, DispatcherPriority _) => a())
.Returns(Task.CompletedTask);
var timer = new Mock<IRenderTimer>();
var loop = new RenderLoop(timer.Object, dispatcher.Object);
var renderTask = new Mock<IRenderLoopTask>();
renderTask.Setup(t => t.NeedsUpdate).Returns(true);
loop.Add(renderTask.Object);
var tickCount = 12345L;
timer.Raise(t => t.Tick += null, tickCount);
renderTask.Verify(t => t.Update(tickCount), Times.Once());
}
}
}
Loading…
Cancel
Save