소스 검색

Add tests for the new RenderLoop logic.

Jeremy Koritzinsky 7 년 전
부모
커밋
8357bc86b0

+ 3 - 13
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)

+ 24 - 40
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 - 0
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());
+        }
+    }
+}