A game framework written with osu! in mind.
at master 173 lines 6.4 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.Threading; 7using NUnit.Framework; 8using osu.Framework.Allocation; 9using osu.Framework.Graphics; 10using osu.Framework.Graphics.Containers; 11using osu.Framework.Testing; 12using osu.Framework.Tests.Visual; 13using osu.Framework.Threading; 14 15namespace osu.Framework.Tests.Containers 16{ 17 [HeadlessTest] 18 public class TestSceneLongRunningLoad : FrameworkTestScene 19 { 20 /// <summary> 21 /// Tests that an exception is thrown when a long-running drawable is synchronously loaded. 22 /// </summary> 23 [Test] 24 public void TestSynchronousLoadLongRunningThrows() => testSynchronousLoad(() => new TestLoadBlockingDrawableLongRunning(true), true); 25 26 /// <summary> 27 /// Tests that an exception is not thrown when a long-running drawable is asynchronously loaded. 28 /// </summary> 29 [Test] 30 public void TestAsynchronousLoadLongRunningDoesNotThrow() => testAsynchronousLoad(() => new TestLoadBlockingDrawableLongRunning(true), false); 31 32 /// <summary> 33 /// Tests that an exception is thrown when a derived long-running drawable is synchronously loaded. 34 /// </summary> 35 [Test] 36 public void TestSynchronousLoadDerivedLongRunningThrows() => testSynchronousLoad(() => new TestLoadBlockingDrawableLongRunningDerived(true), true); 37 38 /// <summary> 39 /// Tests that an exception is not thrown when a derived long-running drawable is asynchronously loaded. 40 /// </summary> 41 [Test] 42 public void TestAsynchronousLoadDerivedLongRunningDoesNotThrow() => testAsynchronousLoad(() => new TestLoadBlockingDrawableLongRunningDerived(true), false); 43 44 /// <summary> 45 /// Tests that an exception is thrown when a parent is synchronously loaded and contains a long-running child. 46 /// </summary> 47 [Test] 48 public void TestLoadParentSynchronousThrows() => testSynchronousLoad(() => new Container 49 { 50 Child = new TestLoadBlockingDrawableLongRunningDerived(true) 51 }, true); 52 53 /// <summary> 54 /// Tests that an exception is thrown when a parent is asynchronously loaded and contains a long-running child. 55 /// </summary> 56 [Test] 57 public void TestLoadParentAsynchronousThrows() => testAsynchronousLoad(() => new Container 58 { 59 Child = new TestLoadBlockingDrawableLongRunningDerived(true) 60 }, true); 61 62 /// <summary> 63 /// Tests that long-running drawables don't block non-long running drawables from loading. 64 /// </summary> 65 [Test] 66 public void TestLongRunningLoadDoesNotBlock() 67 { 68 List<TestLoadBlockingDrawableLongRunning> longRunning = new List<TestLoadBlockingDrawableLongRunning>(); 69 70 // add enough drawables to saturate the task scheduler 71 AddRepeatStep("add long running", () => 72 { 73 var d = new TestLoadBlockingDrawableLongRunning(); 74 longRunning.Add(d); 75 LoadComponentAsync(d); 76 }, 10); 77 78 TestLoadBlockingDrawable normal = null; 79 80 AddStep("add normal", () => { LoadComponentAsync(normal = new TestLoadBlockingDrawable(), Add); }); 81 AddStep("allow normal load", () => normal.AllowLoad.Set()); 82 AddUntilStep("did load", () => normal.IsLoaded); 83 84 AddStep("allow long running load", () => longRunning.ForEach(d => d.AllowLoad.Set())); 85 } 86 87 private void testSynchronousLoad(Func<Drawable> context, bool shouldThrow) 88 { 89 AddAssert($"{(shouldThrow ? "has" : "has not")} thrown", () => 90 { 91 try 92 { 93 Add(context()); 94 } 95 catch (InvalidOperationException) 96 { 97 return shouldThrow; 98 } 99 100 return !shouldThrow; 101 }); 102 } 103 104 private void testAsynchronousLoad(Func<Drawable> context, bool shouldThrow) 105 { 106 Scheduler scheduler = null; 107 108 AddStep("begin long running", () => LoadComponentAsync(context(), scheduler: scheduler = new Scheduler())); 109 110 // Exceptions during async loads are thrown on the scheduler rather than on invocation 111 AddUntilStep("wait for load to complete", () => scheduler.HasPendingTasks); 112 113 AddAssert($"{(shouldThrow ? "has" : "has not")} thrown", () => 114 { 115 try 116 { 117 scheduler.Update(); 118 } 119 catch (InvalidOperationException) 120 { 121 return shouldThrow; 122 } 123 124 return !shouldThrow; 125 }); 126 } 127 128 private class TestLoadBlockingDrawableLongRunningDerived : TestLoadBlockingDrawableLongRunning 129 { 130 public TestLoadBlockingDrawableLongRunningDerived(bool allowLoad = false) 131 : base(allowLoad) 132 { 133 } 134 } 135 136 [LongRunningLoad] 137 private class TestLoadBlockingDrawableLongRunning : CompositeDrawable 138 { 139 public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); 140 141 public TestLoadBlockingDrawableLongRunning(bool allowLoad = false) 142 { 143 if (allowLoad) 144 AllowLoad.Set(); 145 } 146 147 [BackgroundDependencyLoader] 148 private void load() 149 { 150 if (!AllowLoad.Wait(TimeSpan.FromSeconds(10))) 151 throw new TimeoutException(); 152 } 153 } 154 155 private class TestLoadBlockingDrawable : CompositeDrawable 156 { 157 public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); 158 159 public TestLoadBlockingDrawable(bool allowLoad = false) 160 { 161 if (allowLoad) 162 AllowLoad.Set(); 163 } 164 165 [BackgroundDependencyLoader] 166 private void load() 167 { 168 if (!AllowLoad.Wait(TimeSpan.FromSeconds(10))) 169 throw new TimeoutException(); 170 } 171 } 172 } 173}