A game framework written with osu! in mind.
at master 6.7 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.Linq; 7using System.Threading; 8using NUnit.Framework; 9using osu.Framework.Allocation; 10using osu.Framework.Graphics; 11using osu.Framework.Graphics.Containers; 12using osu.Framework.Testing; 13using osu.Framework.Tests.Visual; 14 15namespace osu.Framework.Tests.Containers 16{ 17 [HeadlessTest] 18 public class TestSceneLoadComponentAsync : FrameworkTestScene 19 { 20 [Test] 21 public void TestEnumerableOnlyInvokedOnce() 22 { 23 int invocationCount = 0; 24 25 IEnumerable<AsyncChildLoadingComposite> composite = getEnumerableComponent(() => 26 { 27 invocationCount++; 28 29 var result = new AsyncChildLoadingComposite(); 30 result.AllowChildLoad(); 31 32 return result; 33 }); 34 35 AddStep("clear all children", () => Clear()); 36 37 AddStep("load async", () => LoadComponentsAsync(composite, AddRange)); 38 39 AddUntilStep("component loaded", () => Children.Count == 1); 40 41 AddAssert("invocation count is 1", () => invocationCount == 1); 42 } 43 44 private IEnumerable<AsyncChildLoadingComposite> getEnumerableComponent(Func<AsyncChildLoadingComposite> createComponent) 45 { 46 yield return createComponent(); 47 } 48 49 [Test] 50 public void TestUnpublishedChildDisposal() 51 { 52 AsyncChildLoadingComposite composite = null; 53 54 AddStep("Add new composite", () => { Child = composite = new AsyncChildLoadingComposite(); }); 55 56 AddStep("Allow load", () => composite.AllowChildLoad()); 57 58 AddUntilStep("Wait for child load", () => composite.AsyncChild.LoadState == LoadState.Ready); 59 60 AddStep("Dispose composite", Clear); 61 62 AddUntilStep("Child was disposed", () => composite.AsyncChildDisposed); 63 } 64 65 [Test] 66 public void TestUnpublishedChildLoadBlockDisposal() 67 { 68 AsyncChildLoadingComposite composite = null; 69 70 AddStep("Add new composite", () => { Child = composite = new AsyncChildLoadingComposite(); }); 71 72 AddUntilStep("Wait for child load began", () => composite.AsyncChildLoadBegan); 73 74 AddStep("Dispose composite", Clear); 75 76 AddWaitStep("Wait for potential disposal", 50); 77 78 AddAssert("Composite not yet disposed", () => !composite.IsDisposed); 79 80 AddAssert("Child not yet disposed", () => !composite.AsyncChildDisposed); 81 82 AddStep("Allow load", () => composite.AllowChildLoad()); 83 84 AddUntilStep("Wait for child load", () => composite.AsyncChild.LoadState == LoadState.Ready); 85 86 AddUntilStep("Composite was disposed", () => composite.IsDisposed); 87 88 AddUntilStep("Child was disposed", () => composite.AsyncChildDisposed); 89 } 90 91 [Test] 92 public void TestDisposalDuringAsyncLoad() 93 { 94 AsyncChildrenLoadingComposite composite = null; 95 96 AddStep("Add new composite", () => { Child = composite = new AsyncChildrenLoadingComposite(); }); 97 98 AddStep("Dispose child 2", () => composite.AsyncChild2.Dispose()); 99 100 AddStep("Allow child 1 load", () => composite.AllowChild1Load()); 101 102 AddUntilStep("Wait for child load", () => composite.AsyncChild1.LoadState == LoadState.Ready); 103 104 AddUntilStep("Wait for loaded callback", () => composite.LoadedChildren != null); 105 106 AddAssert("Only child1 loaded", () => composite.LoadedChildren.Count() == 1 107 && composite.LoadedChildren.First() == composite.AsyncChild1); 108 } 109 110 [Test] 111 public void TestScheduleDuringAsyncLoad() 112 { 113 TestLoadBlockingDrawable composite = null; 114 115 bool scheduleRun = false; 116 117 AddStep("Async load drawable", () => 118 { 119 LoadComponentAsync(composite = new TestLoadBlockingDrawable(), d => Child = d); 120 }); 121 122 AddStep("Attempt to schedule on child 1", () => 123 { 124 composite.Schedule(() => scheduleRun = true); 125 }); 126 127 AddStep("Allow child 1 load", () => composite.AllowLoad.Set()); 128 129 AddUntilStep("Scheduled content run", () => scheduleRun); 130 } 131 132 private class AsyncChildrenLoadingComposite : CompositeDrawable 133 { 134 public IEnumerable<TestLoadBlockingDrawable> LoadedChildren; 135 136 public TestLoadBlockingDrawable AsyncChild1 { get; } = new TestLoadBlockingDrawable(); 137 138 public TestLoadBlockingDrawable AsyncChild2 { get; } = new TestLoadBlockingDrawable(); 139 140 public bool AsyncChild1LoadBegan => AsyncChild1.LoadState > LoadState.NotLoaded; 141 142 public void AllowChild1Load() => AsyncChild1.AllowLoad.Set(); 143 144 public void AllowChild2Load() => AsyncChild2.AllowLoad.Set(); 145 146 public new bool IsDisposed => base.IsDisposed; 147 148 protected override void LoadComplete() 149 { 150 // load but never add to hierarchy 151 LoadComponentsAsync(new[] { AsyncChild1, AsyncChild2 }, loadComplete); 152 153 base.LoadComplete(); 154 } 155 156 private void loadComplete(IEnumerable<TestLoadBlockingDrawable> loadedChildren) => LoadedChildren = loadedChildren; 157 } 158 159 private class AsyncChildLoadingComposite : CompositeDrawable 160 { 161 public TestLoadBlockingDrawable AsyncChild { get; } = new TestLoadBlockingDrawable(); 162 163 public bool AsyncChildDisposed { get; private set; } 164 165 public bool AsyncChildLoadBegan => AsyncChild.LoadState > LoadState.NotLoaded; 166 167 public void AllowChildLoad() => AsyncChild.AllowLoad.Set(); 168 169 public new bool IsDisposed => base.IsDisposed; 170 171 protected override void LoadComplete() 172 { 173 AsyncChild.OnDispose += () => AsyncChildDisposed = true; 174 175 // load but never add to hierarchy 176 LoadComponentAsync(AsyncChild); 177 178 base.LoadComplete(); 179 } 180 } 181 182 private class TestLoadBlockingDrawable : CompositeDrawable 183 { 184 public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); 185 186 [BackgroundDependencyLoader] 187 private void load() 188 { 189 if (!AllowLoad.Wait(TimeSpan.FromSeconds(10))) 190 throw new TimeoutException(); 191 } 192 } 193 } 194}