A game framework written with osu! in mind.
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 System.Threading.Tasks;
9using NUnit.Framework;
10using osu.Framework.Allocation;
11using osu.Framework.Graphics;
12using osu.Framework.Graphics.Containers;
13using osu.Framework.Graphics.Shapes;
14using osu.Framework.Graphics.Sprites;
15using osu.Framework.Logging;
16using osuTK;
17using osuTK.Graphics;
18
19namespace osu.Framework.Tests.Visual.Drawables
20{
21 public class TestSceneDrawableLoadCancellation : FrameworkTestScene
22 {
23 private readonly List<SlowLoader> loaders = new List<SlowLoader>();
24
25 [SetUp]
26 public void SetUp() => Schedule(() =>
27 {
28 loaders.Clear();
29 Child = createLoader();
30 });
31
32 [Test]
33 public void TestConcurrentLoad()
34 {
35 AddStep("replace slow loader", () => { Child = createLoader(); });
36 AddStep("replace slow loader", () => { Child = createLoader(); });
37 AddStep("replace slow loader", () => { Child = createLoader(); });
38
39 AddUntilStep("all but last loader cancelled", () => loaders.AsEnumerable().Reverse().Skip(1).All(l => l.WasCancelled));
40
41 AddUntilStep("last loader began loading", () => !loaders.Last().WasCancelled);
42
43 AddStep("allow load to complete", () => loaders.Last().AllowLoadCompletion());
44
45 AddUntilStep("last loader loaded", () => loaders.Last().HasLoaded);
46 }
47
48 [Test]
49 public void TestLoadAsyncCancel()
50 {
51 bool loaded = false;
52
53 PausableLoadDrawable loader = null;
54 CancellationTokenSource cancellationSource = null;
55
56 AddStep("start async load", () => LoadComponentAsync(loader = new PausableLoadDrawable(0), _ => loaded = true, (cancellationSource = new CancellationTokenSource()).Token));
57
58 AddUntilStep("load started", () => loader.IsLoading);
59
60 AddStep("cancel", () => cancellationSource.Cancel());
61
62 AddUntilStep("load cancelled", () => !loader.IsLoading);
63 AddAssert("didn't callback", () => !loaded);
64 }
65
66 private int id;
67
68 private SlowLoader createLoader()
69 {
70 var loader = new SlowLoader(id++);
71 loaders.Add(loader);
72 return loader;
73 }
74
75 public class SlowLoader : CompositeDrawable
76 {
77 private readonly int id;
78 private PausableLoadDrawable loadable;
79
80 public bool WasCancelled => loadable?.IsLoading == false;
81 public bool HasLoaded => loadable?.IsLoaded ?? false;
82
83 public void AllowLoadCompletion() => loadable?.AllowLoadCompletion();
84
85 public SlowLoader(int id)
86 {
87 this.id = id;
88 RelativeSizeAxes = Axes.Both;
89
90 Anchor = Anchor.Centre;
91 Origin = Anchor.Centre;
92
93 Size = new Vector2(0.9f);
94
95 InternalChildren = new Drawable[]
96 {
97 new Box
98 {
99 Colour = Color4.Navy,
100 RelativeSizeAxes = Axes.Both,
101 },
102 };
103 }
104
105 protected override void LoadComplete()
106 {
107 base.LoadComplete();
108
109 this.FadeInFromZero(200);
110 LoadComponentAsync(loadable = new PausableLoadDrawable(id), AddInternal);
111 }
112 }
113
114 public class PausableLoadDrawable : CompositeDrawable
115 {
116 private readonly int id;
117
118 public bool IsLoading;
119
120 public PausableLoadDrawable(int id)
121 {
122 this.id = id;
123
124 RelativeSizeAxes = Axes.Both;
125
126 Anchor = Anchor.Centre;
127 Origin = Anchor.Centre;
128
129 Size = new Vector2(0.9f);
130
131 InternalChildren = new Drawable[]
132 {
133 new Box
134 {
135 Colour = Color4.NavajoWhite,
136 RelativeSizeAxes = Axes.Both
137 },
138 new SpriteText
139 {
140 Text = id.ToString(),
141 Colour = Color4.Black,
142 Font = new FontUsage(size: 50),
143 Anchor = Anchor.Centre,
144 Origin = Anchor.Centre
145 }
146 };
147 }
148
149 private readonly CancellationTokenSource ourSource = new CancellationTokenSource();
150
151 [BackgroundDependencyLoader]
152 private void load(CancellationToken? cancellation)
153 {
154 IsLoading = true;
155
156 using (var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(ourSource.Token, cancellation ?? CancellationToken.None))
157 {
158 try
159 {
160 Task.Delay(99999, linkedSource.Token).Wait(linkedSource.Token);
161 }
162 catch (OperationCanceledException)
163 {
164 if (!ourSource.IsCancellationRequested)
165 {
166 IsLoading = false;
167 throw;
168 }
169 }
170 }
171
172 Logger.Log($"Load {id} complete!");
173 }
174
175 public void AllowLoadCompletion() => ourSource.Cancel();
176
177 protected override void LoadComplete()
178 {
179 base.LoadComplete();
180 this.FadeInFromZero(200);
181 }
182
183 protected override void Dispose(bool isDisposing)
184 {
185 base.Dispose(isDisposing);
186 ourSource.Dispose();
187 }
188 }
189 }
190}