LoadingScreen.cs 9.3 KB

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