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.Collections.Generic;
5using System.Threading;
6using System.Threading.Tasks;
7using NUnit.Framework;
8using osu.Framework.Bindables;
9using osu.Framework.Configuration;
10using osu.Framework.Development;
11using osu.Framework.Platform;
12using osu.Framework.Testing;
13using osu.Framework.Threading;
14
15namespace osu.Framework.Tests.Platform
16{
17 [TestFixture]
18 public class GameHostSuspendTest
19 {
20 private TestTestGame game;
21 private HeadlessGameHost host;
22
23 private const int timeout = 10000;
24
25 [TestCase(ExecutionMode.SingleThread)]
26 [TestCase(ExecutionMode.MultiThreaded)]
27 public void TestPauseResume(ExecutionMode threadMode)
28 {
29 var gameCreated = new ManualResetEventSlim();
30
31 IBindable<GameThreadState> updateThreadState = null;
32
33 var task = Task.Run(() =>
34 {
35 using (host = new ExecutionModeGameHost(@"host", threadMode))
36 {
37 game = new TestTestGame();
38 gameCreated.Set();
39 host.Run(game);
40 }
41 });
42
43 Assert.IsTrue(gameCreated.Wait(timeout));
44 Assert.IsTrue(game.BecameAlive.Wait(timeout));
45
46 // check scheduling is working before suspend
47 var completed = new ManualResetEventSlim();
48 game.Schedule(() =>
49 {
50 updateThreadState = host.UpdateThread.State.GetBoundCopy();
51 updateThreadState.BindValueChanged(state =>
52 {
53 if (state.NewValue != GameThreadState.Starting)
54 Assert.IsTrue(ThreadSafety.IsUpdateThread);
55 });
56 completed.Set();
57 });
58
59 Assert.IsTrue(completed.Wait(timeout / 10));
60 Assert.AreEqual(GameThreadState.Running, updateThreadState.Value);
61
62 host.Suspend();
63
64 // in single-threaded execution, the main thread may already be in the process of updating one last time.
65 int gameUpdates = 0;
66 game.Scheduler.AddDelayed(() => ++gameUpdates, 0, true);
67 Assert.That(() => gameUpdates, Is.LessThan(2).After(timeout / 10));
68 Assert.AreEqual(GameThreadState.Paused, updateThreadState.Value);
69
70 // check that scheduler doesn't process while suspended..
71 completed.Reset();
72 game.Schedule(() => completed.Set());
73 Assert.IsFalse(completed.Wait(timeout / 10));
74
75 // ..and does after resume.
76 host.Resume();
77 Assert.IsTrue(completed.Wait(timeout / 10));
78 Assert.AreEqual(GameThreadState.Running, updateThreadState.Value);
79
80 game.Exit();
81 Assert.IsTrue(task.Wait(timeout));
82 Assert.AreEqual(GameThreadState.Exited, updateThreadState.Value);
83 }
84
85 private class ExecutionModeGameHost : TestRunHeadlessGameHost
86 {
87 private readonly ExecutionMode threadMode;
88
89 public ExecutionModeGameHost(string name, ExecutionMode threadMode)
90 : base(name)
91 {
92 this.threadMode = threadMode;
93 }
94
95 protected override void SetupConfig(IDictionary<FrameworkSetting, object> defaultOverrides)
96 {
97 base.SetupConfig(defaultOverrides);
98 Config.SetValue(FrameworkSetting.ExecutionMode, threadMode);
99 }
100 }
101
102 private class TestTestGame : TestGame
103 {
104 public readonly ManualResetEventSlim BecameAlive = new ManualResetEventSlim();
105
106 protected override void LoadComplete()
107 {
108 BecameAlive.Set();
109 }
110 }
111 }
112}