context: // MonoGame - Copyright (C) The MonoGame Team
  // This file is subject to the terms and conditions defined in
  // file 'LICENSE.txt', which is part of this source code package.
  
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  #if WINRT
  using System.Threading.Tasks;
  using Windows.ApplicationModel.Activation;
  #endif
  using Microsoft.Xna.Framework.Audio;
  using Microsoft.Xna.Framework.Content;
  using Microsoft.Xna.Framework.Graphics;
  using Microsoft.Xna.Framework.Input.Touch;
  
  
  namespace Microsoft.Xna.Framework
  {
      public class Game : IDisposable
      {
          private GameComponentCollection _components;
          private GameServiceContainer _services;
          private ContentManager _content;
          internal GamePlatform Platform;
  
          private SortingFilteringCollection<IDrawable> _drawables =
              new SortingFilteringCollection<IDrawable>(
                  d => d.Visible,
                  (d, handler) => d.VisibleChanged += handler,
                  (d, handler) => d.VisibleChanged -= handler,
                  (d1 ,d2) => Comparer<int>.Default.Compare(d1.DrawOrder, d2.DrawOrder),
                  (d, handler) => d.DrawOrderChanged += handler,
                  (d, handler) => d.DrawOrderChanged -= handler);
  
          private SortingFilteringCollection<IUpdateable> _updateables =
              new SortingFilteringCollection<IUpdateable>(
                  u => u.Enabled,
                  (u, handler) => u.EnabledChanged += handler,
                  (u, handler) => u.EnabledChanged -= handler,
                  (u1, u2) => Comparer<int>.Default.Compare(u1.UpdateOrder, u2.UpdateOrder),
                  (u, handler) => u.UpdateOrderChanged += handler,
                  (u, handler) => u.UpdateOrderChanged -= handler);
  
          private IGraphicsDeviceManager _graphicsDeviceManager;
          private IGraphicsDeviceService _graphicsDeviceService;
  
          private bool _initialized = false;
          private bool _isFixedTimeStep = true;
  
          private TimeSpan _targetElapsedTime = TimeSpan.FromTicks(166667); // 60fps
          private TimeSpan _inactiveSleepTime = TimeSpan.FromSeconds(0.02);
  
          private TimeSpan _maxElapsedTime = TimeSpan.FromMilliseconds(500);
  
  
          private bool _suppressDraw;
          
          public Game()
          {
              _instance = this;
  
              LaunchParameters = new LaunchParameters();
              _services = new GameServiceContainer();
              _components = new GameComponentCollection();
              _content = new ContentManager(_services);
  
              Platform = GamePlatform.PlatformCreate(this);
              Platform.Activated += OnActivated;
              Platform.Deactivated += OnDeactivated;
              _services.AddService(typeof(GamePlatform), Platform);
  
  #if WINDOWS_STOREAPP && !WINDOWS_PHONE81
              Platform.ViewStateChanged += Platform_ApplicationViewChanged;
  #endif
          }
  
          ~Game()
          {
              Dispose(false);
          }
  
  		[System.Diagnostics.Conditional("DEBUG")]
  		internal void Log(string Message)
  		{
  			if (Platform != null) Platform.Log(Message);
  		}
  
          #region IDisposable Implementation
  
          private bool _isDisposed;
          public void Dispose()
          {
              Dispose(true);
              GC.SuppressFinalize(this);
              Raise(Disposed, EventArgs.Empty);
          }
  
          protected virtual void Dispose(bool disposing)
          {
              if (!_isDisposed)
              {
                  if (disposing)
                  {
                      // Dispose loaded game components
                      for (int i = 0; i < _components.Count; i++)
                      {
                          var disposable = _components[i] as IDisposable;
                          if (disposable != null)
                              disposable.Dispose();
                      }
                      _components = null;
  
                      if (_content != null)
                      {
                          _content.Dispose();
                          _content = null;
                      }
  
                      if (_graphicsDeviceManager != null)
                      {
                          (_graphicsDeviceManager as GraphicsDeviceManager).Dispose();
                          _graphicsDeviceManager = null;
                      }
  
                      if (Platform != null)
                      {
                          Platform.Activated -= OnActivated;
                          Platform.Deactivated -= OnDeactivated;
                          _services.RemoveService(typeof(GamePlatform));
  #if WINDOWS_STOREAPP && !WINDOWS_PHONE81
                          Platform.ViewStateChanged -= Platform_ApplicationViewChanged;
  #endif
                          Platform.Dispose();
                          Platform = null;
                      }
  
                      ContentTypeReaderManager.ClearTypeCreators();
  
                      SoundEffect.PlatformShutdown();
                  }
  #if ANDROID
                  Activity = null;
  #endif
                  _isDisposed = true;
                  _instance = null;
              }
          }
  
          [System.Diagnostics.DebuggerNonUserCode]
          private void AssertNotDisposed()
          {
              if (_isDisposed)
              {
                  string name = GetType().Name;
                  throw new ObjectDisposedException(
                      name, string.Format("The {0} object was used after being Disposed.", name));
              }
          }
  
          #endregion IDisposable Implementation
  
          #region Properties
  
  #if ANDROID
          [CLSCompliant(false)]
          public static AndroidGameActivity Activity { get; internal set; }
  #endif
          private static Game _instance = null;
          internal static Game Instance { get { return Game._instance; } }
  
          public LaunchParameters LaunchParameters { get; private set; }
  
          public GameComponentCollection Components
          {
              get { return _components; }
          }
  
          public TimeSpan InactiveSleepTime
          {
              get { return _inactiveSleepTime; }
              set
              {
                  if (value < TimeSpan.Zero)
                      throw new ArgumentOutOfRangeException("The time must be positive.", default(Exception));
  
                  _inactiveSleepTime = value;
              }
          }
  
          /// <summary>
          /// The maximum amount of time we will frameskip over and only perform Update calls with no Draw calls.
          /// MonoGame extension.
          /// </summary>
          public TimeSpan MaxElapsedTime
          {
              get { return _maxElapsedTime; }
              set
              {
                  if (value < TimeSpan.Zero)
                      throw new ArgumentOutOfRangeException("The time must be positive.", default(Exception));
                  if (value < _targetElapsedTime)
                      throw new ArgumentOutOfRangeException("The time must be at least TargetElapsedTime", default(Exception));
  
                  _maxElapsedTime = value;
              }
          }
  
          public bool IsActive
          {
              get { return Platform.IsActive; }
          }
  
          public bool IsMouseVisible
          {
              get { return Platform.IsMouseVisible; }
              set { Platform.IsMouseVisible = value; }
          }
  
          public TimeSpan TargetElapsedTime
          {
              get { return _targetElapsedTime; }
              set
              {
                  // Give GamePlatform implementations an opportunity to override
                  // the new value.
                  value = Platform.TargetElapsedTimeChanging(value);
  
                  if (value <= TimeSpan.Zero)
                      throw new ArgumentOutOfRangeException(
                          "The time must be positive and non-zero.", default(Exception));
  
                  if (value != _targetElapsedTime)
                  {
                      _targetElapsedTime = value;
                      Platform.TargetElapsedTimeChanged();
                  }
              }
          }
  
          public bool IsFixedTimeStep
          {
              get { return _isFixedTimeStep; }
              set { _isFixedTimeStep = value; }
          }
  
          public GameServiceContainer Services {
              get { return _services; }
          }
  
          public ContentManager Content
          {
              get { return _content; }
              set
              {
                  if (value == null)
                      throw new ArgumentNullException();
  
                  _content = value;
              }
          }
  
          public GraphicsDevice GraphicsDevice
          {
              get
              {
                  if (_graphicsDeviceService == null)
                  {
                      _graphicsDeviceService = (IGraphicsDeviceService)
                          Services.GetService(typeof(IGraphicsDeviceService));
  
                      if (_graphicsDeviceService == null)
                          throw new InvalidOperationException("No Graphics Device Service");
                  }
                  return _graphicsDeviceService.GraphicsDevice;
              }
          }
  
          [CLSCompliant(false)]
          public GameWindow Window
          {
              get { return Platform.Window; }
          }
  
          #endregion Properties
  
          #region Internal Properties
  
          // FIXME: Internal members should be eliminated.
          // Currently Game.Initialized is used by the Mac game window class to
          // determine whether to raise DeviceResetting and DeviceReset on
          // GraphicsDeviceManager.
          internal bool Initialized
          {
              get { return _initialized; }
          }
  
          #endregion Internal Properties
  
          #region Events
  
          public event EventHandler<EventArgs> Activated;
          public event EventHandler<EventArgs> Deactivated;
          public event EventHandler<EventArgs> Disposed;
          public event EventHandler<EventArgs> Exiting;
  
  #if WINDOWS_STOREAPP && !WINDOWS_PHONE81
          [CLSCompliant(false)]
          public event EventHandler<ViewStateChangedEventArgs> ApplicationViewChanged;
  #endif
  
  #if WINRT
          [CLSCompliant(false)]
          public ApplicationExecutionState PreviousExecutionState { get; internal set; }
  #endif
  
          #endregion
  
          #region Public Methods
  
  #if IOS || WINDOWS_STOREAPP && !WINDOWS_PHONE81
          [Obsolete("This platform's policy does not allow programmatically closing.", true)]
  #endif
          public void Exit()
          {
              Platform.Exit();
              _suppressDraw = true;
          }
  
          public void ResetElapsedTime()
          {
              Platform.ResetElapsedTime();
              _gameTimer.Reset();
              _gameTimer.Start();
              _accumulatedElapsedTime = TimeSpan.Zero;
              _gameTime.ElapsedGameTime = TimeSpan.Zero;
              _previousTicks = 0L;
          }
  
          public void SuppressDraw()
          {
              _suppressDraw = true;
          }
          
          public void RunOneFrame()
          {
              if (Platform == null)
                  return;
  
              if (!Platform.BeforeRun())
                  return;
  
              if (!_initialized) {
                  DoInitialize ();
                  _gameTimer = Stopwatch.StartNew();
                  _initialized = true;
              }
  
              BeginRun();            
  
              //Not quite right..
              Tick ();
  
              EndRun ();
  
          }
  
          public void Run()
          {
              Run(Platform.DefaultRunBehavior);
          }
  
          public void Run(GameRunBehavior runBehavior)
          {
              AssertNotDisposed();
              if (!Platform.BeforeRun())
              {
                  BeginRun();
                  _gameTimer = Stopwatch.StartNew();
                  return;
              }
  
              if (!_initialized) {
                  DoInitialize ();
                  _initialized = true;
              }
  
              BeginRun();
              _gameTimer = Stopwatch.StartNew();
              switch (runBehavior)
              {
              case GameRunBehavior.Asynchronous:
                  Platform.AsyncRunLoopEnded += Platform_AsyncRunLoopEnded;
                  Platform.StartRunLoop();
                  break;
              case GameRunBehavior.Synchronous:
                  Platform.RunLoop();
                  EndRun();
  				DoExiting();
                  break;
              default:
                  throw new ArgumentException(string.Format(
                      "Handling for the run behavior {0} is not implemented.", runBehavior));
              }
          }
  
          private TimeSpan _accumulatedElapsedTime;
          private readonly GameTime _gameTime = new GameTime();
          private Stopwatch _gameTimer;
          private long _previousTicks = 0;
          private int _updateFrameLag;
  
          public void Tick()
          {
              // NOTE: This code is very sensitive and can break very badly
              // with even what looks like a safe change.  Be sure to test 
              // any change fully in both the fixed and variable timestep 
              // modes across multiple devices and platforms.
  
          RetryTick:
  
              // Advance the accumulated elapsed time.
              var currentTicks = _gameTimer.Elapsed.Ticks;
              _accumulatedElapsedTime += TimeSpan.FromTicks(currentTicks - _previousTicks);
              _previousTicks = currentTicks;
  
              // If we're in the fixed timestep mode and not enough time has elapsed
              // to perform an update we sleep off the the remaining time to save battery
              // life and/or release CPU time to other threads and processes.
              if (IsFixedTimeStep && _accumulatedElapsedTime < TargetElapsedTime)
              {
                  var sleepTime = (int)(TargetElapsedTime - _accumulatedElapsedTime).TotalMilliseconds;
  
                  // NOTE: While sleep can be inaccurate in general it is 
                  // accurate enough for frame limiting purposes if some
                  // fluctuation is an acceptable result.
  #if WINRT
                  Task.Delay(sleepTime).Wait();
  #else
                  System.Threading.Thread.Sleep(sleepTime);
  #endif
                  goto RetryTick;
              }
  
              // Do not allow any update to take longer than our maximum.
  
gt:             if (_accumulatedElapsedTime > _maxElapsedTime)