LoadingScreen.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // LoadingScreen.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Threading;
  12. using System.Diagnostics;
  13. using Microsoft.Xna.Framework;
  14. using Microsoft.Xna.Framework.Graphics;
  15. using Microsoft.Xna.Framework.Net;
  16. #endregion
  17. namespace NetworkStateManagement
  18. {
  19. /// <summary>
  20. /// The loading screen coordinates transitions between the menu system and the
  21. /// game itself. Normally one screen will transition off at the same time as
  22. /// the next screen is transitioning on, but for larger transitions that can
  23. /// take a longer time to load their data, we want the menu system to be entirely
  24. /// gone before we start loading the game. This is done as follows:
  25. ///
  26. /// - Tell all the existing screens to transition off.
  27. /// - Activate a loading screen, which will transition on at the same time.
  28. /// - The loading screen watches the state of the previous screens.
  29. /// - When it sees they have finished transitioning off, it activates the real
  30. /// next screen, which may take a long time to load its data. The loading
  31. /// screen will be the only thing displayed while this load is taking place.
  32. /// </summary>
  33. class LoadingScreen : GameScreen
  34. {
  35. #region Fields
  36. bool loadingIsSlow;
  37. bool otherScreensAreGone;
  38. GameScreen[] screensToLoad;
  39. Thread backgroundThread;
  40. EventWaitHandle backgroundThreadExit;
  41. GraphicsDevice graphicsDevice;
  42. NetworkSession networkSession;
  43. IMessageDisplay messageDisplay;
  44. GameTime loadStartTime;
  45. TimeSpan loadAnimationTimer;
  46. #endregion
  47. #region Initialization
  48. /// <summary>
  49. /// The constructor is private: loading screens should
  50. /// be activated via the static Load method instead.
  51. /// </summary>
  52. private LoadingScreen(ScreenManager screenManager, bool loadingIsSlow,
  53. GameScreen[] screensToLoad)
  54. {
  55. this.loadingIsSlow = loadingIsSlow;
  56. this.screensToLoad = screensToLoad;
  57. TransitionOnTime = TimeSpan.FromSeconds(0.5);
  58. // If this is going to be a slow load operation, create a background
  59. // thread that will update the network session and draw the load screen
  60. // animation while the load is taking place.
  61. if (loadingIsSlow)
  62. {
  63. backgroundThread = new Thread(BackgroundWorkerThread);
  64. backgroundThreadExit = new ManualResetEvent(false);
  65. graphicsDevice = screenManager.GraphicsDevice;
  66. // Look up some services that will be used by the background thread.
  67. IServiceProvider services = screenManager.Game.Services;
  68. networkSession = (NetworkSession)services.GetService(
  69. typeof(NetworkSession));
  70. messageDisplay = (IMessageDisplay)services.GetService(
  71. typeof(IMessageDisplay));
  72. }
  73. }
  74. /// <summary>
  75. /// Activates the loading screen.
  76. /// </summary>
  77. public static void Load(ScreenManager screenManager, bool loadingIsSlow,
  78. PlayerIndex? controllingPlayer,
  79. params GameScreen[] screensToLoad)
  80. {
  81. // Tell all the current screens to transition off.
  82. foreach (GameScreen screen in screenManager.GetScreens())
  83. screen.ExitScreen();
  84. // Create and activate the loading screen.
  85. LoadingScreen loadingScreen = new LoadingScreen(screenManager,
  86. loadingIsSlow,
  87. screensToLoad);
  88. screenManager.AddScreen(loadingScreen, controllingPlayer);
  89. }
  90. #endregion
  91. #region Update and Draw
  92. /// <summary>
  93. /// Updates the loading screen.
  94. /// </summary>
  95. public override void Update(GameTime gameTime, bool otherScreenHasFocus,
  96. bool coveredByOtherScreen)
  97. {
  98. base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
  99. // If all the previous screens have finished transitioning
  100. // off, it is time to actually perform the load.
  101. if (otherScreensAreGone)
  102. {
  103. // Start up the background thread, which will update the network
  104. // session and draw the animation while we are loading.
  105. if (backgroundThread != null)
  106. {
  107. loadStartTime = gameTime;
  108. backgroundThread.Start();
  109. }
  110. // Perform the load operation.
  111. ScreenManager.RemoveScreen(this);
  112. foreach (GameScreen screen in screensToLoad)
  113. {
  114. if (screen != null)
  115. {
  116. ScreenManager.AddScreen(screen, ControllingPlayer);
  117. }
  118. }
  119. // Signal the background thread to exit, then wait for it to do so.
  120. if (backgroundThread != null)
  121. {
  122. backgroundThreadExit.Set();
  123. backgroundThread.Join();
  124. }
  125. // Once the load has finished, we use ResetElapsedTime to tell
  126. // the game timing mechanism that we have just finished a very
  127. // long frame, and that it should not try to catch up.
  128. ScreenManager.Game.ResetElapsedTime();
  129. }
  130. }
  131. /// <summary>
  132. /// Draws the loading screen.
  133. /// </summary>
  134. public override void Draw(GameTime gameTime)
  135. {
  136. // If we are the only active screen, that means all the previous screens
  137. // must have finished transitioning off. We check for this in the Draw
  138. // method, rather than in Update, because it isn't enough just for the
  139. // screens to be gone: in order for the transition to look good we must
  140. // have actually drawn a frame without them before we perform the load.
  141. if ((ScreenState == ScreenState.Active) &&
  142. (ScreenManager.GetScreens().Length == 1))
  143. {
  144. otherScreensAreGone = true;
  145. }
  146. // The gameplay screen takes a while to load, so we display a loading
  147. // message while that is going on, but the menus load very quickly, and
  148. // it would look silly if we flashed this up for just a fraction of a
  149. // second while returning from the game to the menus. This parameter
  150. // tells us how long the loading is going to take, so we know whether
  151. // to bother drawing the message.
  152. if (loadingIsSlow)
  153. {
  154. SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
  155. SpriteFont font = ScreenManager.Font;
  156. string message = Resources.Loading;
  157. // Center the text in the viewport.
  158. Viewport viewport = ScreenManager.GraphicsDevice.Viewport;
  159. Vector2 viewportSize = new Vector2(viewport.Width, viewport.Height);
  160. Vector2 textSize = font.MeasureString(message);
  161. Vector2 textPosition = (viewportSize - textSize) / 2;
  162. Color color = Color.White * TransitionAlpha;
  163. // Animate the number of dots after our "Loading..." message.
  164. loadAnimationTimer += gameTime.ElapsedGameTime;
  165. int dotCount = (int)(loadAnimationTimer.TotalSeconds * 5) % 10;
  166. message += new string('.', dotCount);
  167. // Draw the text.
  168. spriteBatch.Begin();
  169. spriteBatch.DrawString(font, message, textPosition, color);
  170. spriteBatch.End();
  171. }
  172. }
  173. #endregion
  174. #region Background Thread
  175. /// <summary>
  176. /// Worker thread draws the loading animation and updates the network
  177. /// session while the load is taking place.
  178. /// </summary>
  179. void BackgroundWorkerThread()
  180. {
  181. long lastTime = Stopwatch.GetTimestamp();
  182. // EventWaitHandle.WaitOne will return true if the exit signal has
  183. // been triggered, or false if the timeout has expired. We use the
  184. // timeout to update at regular intervals, then break out of the
  185. // loop when we are signalled to exit.
  186. while (!backgroundThreadExit.WaitOne(1000 / 30))
  187. {
  188. GameTime gameTime = GetGameTime(ref lastTime);
  189. DrawLoadAnimation(gameTime);
  190. UpdateNetworkSession();
  191. }
  192. }
  193. /// <summary>
  194. /// Works out how long it has been since the last background thread update.
  195. /// </summary>
  196. GameTime GetGameTime(ref long lastTime)
  197. {
  198. long currentTime = Stopwatch.GetTimestamp();
  199. long elapsedTicks = currentTime - lastTime;
  200. lastTime = currentTime;
  201. TimeSpan elapsedTime = TimeSpan.FromTicks(elapsedTicks *
  202. TimeSpan.TicksPerSecond /
  203. Stopwatch.Frequency);
  204. return new GameTime(loadStartTime.TotalGameTime + elapsedTime, elapsedTime);
  205. }
  206. /// <summary>
  207. /// Calls directly into our Draw method from the background worker thread,
  208. /// so as to update the load animation in parallel with the actual loading.
  209. /// </summary>
  210. void DrawLoadAnimation(GameTime gameTime)
  211. {
  212. if ((graphicsDevice == null) || graphicsDevice.IsDisposed)
  213. return;
  214. try
  215. {
  216. graphicsDevice.Clear(Color.Black);
  217. // Draw the loading screen.
  218. Draw(gameTime);
  219. // If we have a message display component, we want to display
  220. // that over the top of the loading screen, too.
  221. if (messageDisplay != null)
  222. {
  223. messageDisplay.Update(gameTime);
  224. messageDisplay.Draw(gameTime);
  225. }
  226. graphicsDevice.Present();
  227. }
  228. catch
  229. {
  230. // If anything went wrong (for instance the graphics device was lost
  231. // or reset) we don't have any good way to recover while running on a
  232. // background thread. Setting the device to null will stop us from
  233. // rendering, so the main game can deal with the problem later on.
  234. graphicsDevice = null;
  235. }
  236. }
  237. /// <summary>
  238. /// Updates the network session from the background worker thread, to avoid
  239. /// disconnecting due to network timeouts even if loading takes a long time.
  240. /// </summary>
  241. void UpdateNetworkSession()
  242. {
  243. if ((networkSession == null) ||
  244. (networkSession.SessionState == NetworkSessionState.Ended))
  245. return;
  246. try
  247. {
  248. networkSession.Update();
  249. }
  250. catch
  251. {
  252. // If anything went wrong, we don't have a good way to report that
  253. // error while running on a background thread. Setting the session to
  254. // null will stop us from updating it, so the main game can deal with
  255. // the problem later on.
  256. networkSession = null;
  257. }
  258. }
  259. #endregion
  260. }
  261. }