LoadingScreen.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. backgroundThread = new Thread (BackgroundWorkerThread);
  63. backgroundThreadExit = new ManualResetEvent (false);
  64. graphicsDevice = screenManager.GraphicsDevice;
  65. // Look up some services that will be used by the background thread.
  66. IServiceProvider services = screenManager.Game.Services;
  67. networkSession = (NetworkSession)services.GetService (
  68. typeof(NetworkSession));
  69. messageDisplay = (IMessageDisplay)services.GetService (
  70. typeof(IMessageDisplay));
  71. }
  72. }
  73. /// <summary>
  74. /// Activates the loading screen.
  75. /// </summary>
  76. public static void Load (ScreenManager screenManager, bool loadingIsSlow,
  77. PlayerIndex? controllingPlayer,
  78. params GameScreen[] screensToLoad)
  79. {
  80. // Tell all the current screens to transition off.
  81. foreach (GameScreen screen in screenManager.GetScreens ())
  82. screen.ExitScreen ();
  83. // Create and activate the loading screen.
  84. LoadingScreen loadingScreen = new LoadingScreen (screenManager,
  85. loadingIsSlow,
  86. screensToLoad);
  87. screenManager.AddScreen (loadingScreen, controllingPlayer);
  88. }
  89. #endregion
  90. #region Update and Draw
  91. /// <summary>
  92. /// Updates the loading screen.
  93. /// </summary>
  94. public override void Update (GameTime gameTime, bool otherScreenHasFocus,
  95. bool coveredByOtherScreen)
  96. {
  97. base.Update (gameTime, otherScreenHasFocus, coveredByOtherScreen);
  98. // If all the previous screens have finished transitioning
  99. // off, it is time to actually perform the load.
  100. if (otherScreensAreGone) {
  101. // Start up the background thread, which will update the network
  102. // session and draw the animation while we are loading.
  103. if (backgroundThread != null) {
  104. loadStartTime = gameTime;
  105. backgroundThread.Start ();
  106. }
  107. // Perform the load operation.
  108. ScreenManager.RemoveScreen (this);
  109. foreach (GameScreen screen in screensToLoad) {
  110. if (screen != null) {
  111. ScreenManager.AddScreen (screen, ControllingPlayer);
  112. }
  113. }
  114. // Signal the background thread to exit, then wait for it to do so.
  115. if (backgroundThread != null) {
  116. backgroundThreadExit.Set ();
  117. backgroundThread.Join ();
  118. }
  119. // Once the load has finished, we use ResetElapsedTime to tell
  120. // the game timing mechanism that we have just finished a very
  121. // long frame, and that it should not try to catch up.
  122. ScreenManager.Game.ResetElapsedTime ();
  123. }
  124. }
  125. /// <summary>
  126. /// Draws the loading screen.
  127. /// </summary>
  128. public override void Draw (GameTime gameTime)
  129. {
  130. // If we are the only active screen, that means all the previous screens
  131. // must have finished transitioning off. We check for this in the Draw
  132. // method, rather than in Update, because it isn't enough just for the
  133. // screens to be gone: in order for the transition to look good we must
  134. // have actually drawn a frame without them before we perform the load.
  135. if ((ScreenState == ScreenState.Active) &&
  136. (ScreenManager.GetScreens ().Length == 1)) {
  137. otherScreensAreGone = true;
  138. }
  139. // The gameplay screen takes a while to load, so we display a loading
  140. // message while that is going on, but the menus load very quickly, and
  141. // it would look silly if we flashed this up for just a fraction of a
  142. // second while returning from the game to the menus. This parameter
  143. // tells us how long the loading is going to take, so we know whether
  144. // to bother drawing the message.
  145. if (loadingIsSlow) {
  146. SpriteBatch spriteBatch = ScreenManager.SpriteBatch;
  147. SpriteFont font = ScreenManager.Font;
  148. string message = Resources.Loading;
  149. // Center the text in the viewport.
  150. Viewport viewport = ScreenManager.GraphicsDevice.Viewport;
  151. Vector2 viewportSize = new Vector2 (viewport.Width, viewport.Height);
  152. Vector2 textSize = font.MeasureString (message);
  153. Vector2 textPosition = (viewportSize - textSize) / 2;
  154. Color color = Color.White * TransitionAlpha;
  155. // Animate the number of dots after our "Loading..." message.
  156. loadAnimationTimer += gameTime.ElapsedGameTime;
  157. int dotCount = (int)(loadAnimationTimer.TotalSeconds * 5) % 10;
  158. message += new string ('.', dotCount);
  159. // Draw the text.
  160. spriteBatch.Begin ();
  161. spriteBatch.DrawString (font, message, textPosition, color);
  162. spriteBatch.End ();
  163. }
  164. }
  165. #endregion
  166. #region Background Thread
  167. /// <summary>
  168. /// Worker thread draws the loading animation and updates the network
  169. /// session while the load is taking place.
  170. /// </summary>
  171. void BackgroundWorkerThread ()
  172. {
  173. long lastTime = Stopwatch.GetTimestamp ();
  174. // EventWaitHandle.WaitOne will return true if the exit signal has
  175. // been triggered, or false if the timeout has expired. We use the
  176. // timeout to update at regular intervals, then break out of the
  177. // loop when we are signalled to exit.
  178. while (!backgroundThreadExit.WaitOne (1000 / 30)) {
  179. //GameTime gameTime = GetGameTime (ref lastTime);
  180. //DrawLoadAnimation (gameTime);
  181. UpdateNetworkSession ();
  182. }
  183. }
  184. // /// <summary>
  185. // /// Works out how long it has been since the last background thread update.
  186. // /// </summary>
  187. // GameTime GetGameTime (ref long lastTime)
  188. // {
  189. // long currentTime = Stopwatch.GetTimestamp ();
  190. // long elapsedTicks = currentTime - lastTime;
  191. // lastTime = currentTime;
  192. //
  193. // TimeSpan elapsedTime = TimeSpan.FromTicks (elapsedTicks *
  194. // TimeSpan.TicksPerSecond /
  195. // Stopwatch.Frequency);
  196. //
  197. // return new GameTime (loadStartTime.TotalGameTime + elapsedTime, elapsedTime);
  198. // }
  199. /// <summary>
  200. /// Calls directly into our Draw method from the background worker thread,
  201. /// so as to update the load animation in parallel with the actual loading.
  202. /// </summary>
  203. void DrawLoadAnimation (GameTime gameTime)
  204. {
  205. if ((graphicsDevice == null) || graphicsDevice.IsDisposed)
  206. return;
  207. try {
  208. graphicsDevice.Clear (Color.Black);
  209. // Draw the loading screen.
  210. Draw (gameTime);
  211. // If we have a message display component, we want to display
  212. // that over the top of the loading screen, too.
  213. if (messageDisplay != null) {
  214. messageDisplay.Update (gameTime);
  215. messageDisplay.Draw (gameTime);
  216. }
  217. graphicsDevice.Present ();
  218. } catch {
  219. // If anything went wrong (for instance the graphics device was lost
  220. // or reset) we don't have any good way to recover while running on a
  221. // background thread. Setting the device to null will stop us from
  222. // rendering, so the main game can deal with the problem later on.
  223. graphicsDevice = null;
  224. }
  225. }
  226. /// <summary>
  227. /// Updates the network session from the background worker thread, to avoid
  228. /// disconnecting due to network timeouts even if loading takes a long time.
  229. /// </summary>
  230. void UpdateNetworkSession ()
  231. {
  232. if ((networkSession == null) ||
  233. (networkSession.SessionState == NetworkSessionState.Ended))
  234. return;
  235. try {
  236. networkSession.Update ();
  237. } catch {
  238. // If anything went wrong, we don't have a good way to report that
  239. // error while running on a background thread. Setting the session to
  240. // null will stop us from updating it, so the main game can deal with
  241. // the problem later on.
  242. networkSession = null;
  243. }
  244. }
  245. #endregion
  246. }
  247. }