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.Threading;
6using NUnit.Framework;
7using osu.Framework.Allocation;
8using osu.Framework.Graphics;
9using osu.Framework.Graphics.Containers;
10using osu.Framework.Graphics.Shapes;
11using osu.Framework.Graphics.Sprites;
12using osuTK;
13using osuTK.Graphics;
14
15namespace osu.Framework.Tests.Visual.Drawables
16{
17 public class TestSceneModelBackedDrawable : FrameworkTestScene
18 {
19 private TestModelBackedDrawable backedDrawable;
20
21 private void createModelBackedDrawable(bool hasIntermediate, bool showNullModel = false) =>
22 Child = backedDrawable = new TestModelBackedDrawable
23 {
24 Anchor = Anchor.Centre,
25 Origin = Anchor.Centre,
26 Size = new Vector2(200),
27 HasIntermediate = hasIntermediate,
28 ShowNullModel = showNullModel
29 };
30
31 [Test]
32 public void TestEmptyDefaultState()
33 {
34 AddStep("setup", () => createModelBackedDrawable(false));
35 AddAssert("nothing shown", () => backedDrawable.DisplayedDrawable == null);
36 }
37
38 [Test]
39 public void TestModelDefaultState()
40 {
41 TestDrawableModel drawableModel = null;
42
43 AddStep("setup", () =>
44 {
45 createModelBackedDrawable(false);
46 backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set()));
47 });
48
49 assertDrawableVisibility(1, () => drawableModel);
50 }
51
52 [TestCase(false)]
53 [TestCase(true)]
54 public void TestChangeModel(bool hasIntermediate)
55 {
56 TestDrawableModel firstModel = null;
57 TestDrawableModel secondModel = null;
58
59 AddStep("setup", () =>
60 {
61 createModelBackedDrawable(hasIntermediate);
62 backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set()));
63 });
64
65 assertDrawableVisibility(1, () => firstModel);
66
67 AddStep("set second model", () => backedDrawable.Model = new TestModel(secondModel = new TestDrawableModel(2)));
68 assertIntermediateVisibility(hasIntermediate, () => firstModel);
69
70 AddStep("allow second model to load", () => secondModel.AllowLoad.Set());
71 assertDrawableVisibility(2, () => secondModel);
72 }
73
74 [TestCase(false)]
75 [TestCase(true)]
76 public void TestChangeModelDuringLoad(bool hasIntermediate)
77 {
78 TestDrawableModel firstModel = null;
79 TestDrawableModel secondModel = null;
80 TestDrawableModel thirdModel = null;
81
82 AddStep("setup", () =>
83 {
84 createModelBackedDrawable(hasIntermediate);
85 backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set()));
86 });
87
88 assertDrawableVisibility(1, () => firstModel);
89
90 AddStep("set second model", () => backedDrawable.Model = new TestModel(secondModel = new TestDrawableModel(2)));
91 assertIntermediateVisibility(hasIntermediate, () => firstModel);
92
93 AddStep("set third model", () => backedDrawable.Model = new TestModel(thirdModel = new TestDrawableModel(3)));
94 assertIntermediateVisibility(hasIntermediate, () => firstModel);
95
96 AddStep("allow second model to load", () => secondModel.AllowLoad.Set());
97 assertIntermediateVisibility(hasIntermediate, () => firstModel);
98
99 AddStep("allow third model to load", () => thirdModel.AllowLoad.Set());
100 assertDrawableVisibility(3, () => thirdModel);
101 }
102
103 [TestCase(false)]
104 [TestCase(true)]
105 public void TestOutOfOrderLoad(bool hasIntermediate)
106 {
107 TestDrawableModel firstModel = null;
108 TestDrawableModel secondModel = null;
109
110 AddStep("setup", () =>
111 {
112 createModelBackedDrawable(hasIntermediate);
113 backedDrawable.Model = new TestModel(firstModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set()));
114 });
115
116 assertDrawableVisibility(1, () => firstModel);
117
118 AddStep("set second model", () => backedDrawable.Model = new TestModel(secondModel = new TestDrawableModel(2)));
119 assertIntermediateVisibility(hasIntermediate, () => firstModel);
120
121 AddStep("allow second model to load", () => secondModel.AllowLoad.Set());
122 assertDrawableVisibility(2, () => secondModel);
123
124 AddStep("allow first model to load", () => firstModel.AllowLoad.Set());
125 assertDrawableVisibility(2, () => secondModel);
126 }
127
128 [Test]
129 public void TestSetNullModel()
130 {
131 TestDrawableModel drawableModel = null;
132
133 AddStep("setup", () =>
134 {
135 createModelBackedDrawable(false, true);
136 backedDrawable.Model = new TestModel(drawableModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set()));
137 });
138
139 assertDrawableVisibility(1, () => drawableModel);
140
141 AddStep("set null model", () => backedDrawable.Model = null);
142 AddUntilStep("null model shown", () => backedDrawable.DisplayedDrawable is TestNullDrawableModel);
143 }
144
145 [Test]
146 public void TestInsideBufferedContainer()
147 {
148 TestDrawableModel drawableModel = null;
149
150 AddStep("setup", () =>
151 {
152 Child = new BufferedContainer
153 {
154 Anchor = Anchor.Centre,
155 Origin = Anchor.Centre,
156 Size = new Vector2(200),
157 Child = backedDrawable = new TestModelBackedDrawable
158 {
159 RelativeSizeAxes = Axes.Both,
160 HasIntermediate = false,
161 ShowNullModel = false,
162 Model = new TestModel(drawableModel = new TestDrawableModel(1).With(d => d.AllowLoad.Set()))
163 }
164 };
165 });
166
167 assertDrawableVisibility(1, () => drawableModel);
168 }
169
170 private void assertIntermediateVisibility(bool hasIntermediate, Func<Drawable> getLastFunc)
171 {
172 if (hasIntermediate)
173 AddAssert("no drawable visible", () => backedDrawable.DisplayedDrawable == null);
174 else
175 AddUntilStep("last drawable visible", () => backedDrawable.DisplayedDrawable == getLastFunc());
176 }
177
178 private void assertDrawableVisibility(int id, Func<Drawable> getFunc)
179 {
180 AddUntilStep($"model {id} visible", () => backedDrawable.DisplayedDrawable == getFunc());
181 }
182
183 private class TestModel
184 {
185 public readonly TestDrawableModel DrawableModel;
186
187 public TestModel(TestDrawableModel drawableModel)
188 {
189 DrawableModel = drawableModel;
190 }
191 }
192
193 private class TestDrawableModel : CompositeDrawable
194 {
195 private readonly int id;
196
197 public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false);
198
199 protected virtual Color4 BackgroundColour
200 {
201 get
202 {
203 switch (id % 5)
204 {
205 default:
206 return Color4.SkyBlue;
207
208 case 1:
209 return Color4.Tomato;
210
211 case 2:
212 return Color4.DarkGreen;
213
214 case 3:
215 return Color4.MediumPurple;
216
217 case 4:
218 return Color4.DarkOrchid;
219 }
220 }
221 }
222
223 public TestDrawableModel(int id)
224 {
225 this.id = id;
226
227 RelativeSizeAxes = Axes.Both;
228
229 InternalChildren = new Drawable[]
230 {
231 new Box
232 {
233 RelativeSizeAxes = Axes.Both,
234 Colour = BackgroundColour
235 },
236 new SpriteText
237 {
238 Anchor = Anchor.Centre,
239 Origin = Anchor.Centre,
240 Text = id > 0 ? $"model {id}" : "null"
241 }
242 };
243 }
244
245 [BackgroundDependencyLoader]
246 private void load()
247 {
248 if (!AllowLoad.Wait(TimeSpan.FromSeconds(10)))
249 {
250 }
251 }
252 }
253
254 private class TestNullDrawableModel : TestDrawableModel
255 {
256 protected override Color4 BackgroundColour => Color4.SlateGray;
257
258 public TestNullDrawableModel()
259 : base(0)
260 {
261 AllowLoad.Set();
262 }
263 }
264
265 private class TestModelBackedDrawable : ModelBackedDrawable<TestModel>
266 {
267 public bool ShowNullModel;
268
269 public bool HasIntermediate;
270
271 protected override Drawable CreateDrawable(TestModel model)
272 {
273 if (model == null && ShowNullModel)
274 return new TestNullDrawableModel();
275
276 return model?.DrawableModel;
277 }
278
279 public new Drawable DisplayedDrawable => base.DisplayedDrawable;
280
281 public new TestModel Model
282 {
283 set => base.Model = value;
284 }
285
286 protected override bool TransformImmediately => HasIntermediate;
287 }
288 }
289}