diff --git a/MonoGame.Framework/ConcreteGraphicsDeviceManager.Blazor.cs b/MonoGame.Framework/ConcreteGraphicsDeviceManager.Blazor.cs index ee1145f168c..cb5adab290c 100644 --- a/MonoGame.Framework/ConcreteGraphicsDeviceManager.Blazor.cs +++ b/MonoGame.Framework/ConcreteGraphicsDeviceManager.Blazor.cs @@ -168,7 +168,7 @@ private void GraphicsDevice_DeviceReset_UpdateTouchPanel(object sender, EventArg private void GraphicsDevice_PresentationChanged_UpdateGamePlatform(object sender, PresentationEventArgs args) { - base.Game.Platform.OnPresentationChanged(args.PresentationParameters); + base.Game.Strategy.OnPresentationChanged(args.PresentationParameters); } } diff --git a/MonoGame.Framework/ConcreteGraphicsDeviceManager.SDL.cs b/MonoGame.Framework/ConcreteGraphicsDeviceManager.SDL.cs index d9485e5f47e..ee02a9e24e8 100644 --- a/MonoGame.Framework/ConcreteGraphicsDeviceManager.SDL.cs +++ b/MonoGame.Framework/ConcreteGraphicsDeviceManager.SDL.cs @@ -254,7 +254,7 @@ private void GraphicsDevice_DeviceReset_UpdateTouchPanel(object sender, EventArg private void GraphicsDevice_PresentationChanged_UpdateGamePlatform(object sender, PresentationEventArgs args) { - base.Game.Platform.OnPresentationChanged(args.PresentationParameters); + base.Game.Strategy.OnPresentationChanged(args.PresentationParameters); } } diff --git a/MonoGame.Framework/ConcreteGraphicsDeviceManager.UWP.cs b/MonoGame.Framework/ConcreteGraphicsDeviceManager.UWP.cs index 39333925267..28bc31d63c0 100644 --- a/MonoGame.Framework/ConcreteGraphicsDeviceManager.UWP.cs +++ b/MonoGame.Framework/ConcreteGraphicsDeviceManager.UWP.cs @@ -193,7 +193,7 @@ private void GraphicsDevice_DeviceReset_UpdateTouchPanel(object sender, EventArg private void GraphicsDevice_PresentationChanged_UpdateGamePlatform(object sender, PresentationEventArgs args) { - base.Game.Platform.OnPresentationChanged(args.PresentationParameters); + base.Game.Strategy.OnPresentationChanged(args.PresentationParameters); } } diff --git a/MonoGame.Framework/ConcreteGraphicsDeviceManager.WindowsDX11.cs b/MonoGame.Framework/ConcreteGraphicsDeviceManager.WindowsDX11.cs index ee1145f168c..cb5adab290c 100644 --- a/MonoGame.Framework/ConcreteGraphicsDeviceManager.WindowsDX11.cs +++ b/MonoGame.Framework/ConcreteGraphicsDeviceManager.WindowsDX11.cs @@ -168,7 +168,7 @@ private void GraphicsDevice_DeviceReset_UpdateTouchPanel(object sender, EventArg private void GraphicsDevice_PresentationChanged_UpdateGamePlatform(object sender, PresentationEventArgs args) { - base.Game.Platform.OnPresentationChanged(args.PresentationParameters); + base.Game.Strategy.OnPresentationChanged(args.PresentationParameters); } } diff --git a/MonoGame.Framework/Game.cs b/MonoGame.Framework/Game.cs index 24fbe2adc91..5b24e004516 100644 --- a/MonoGame.Framework/Game.cs +++ b/MonoGame.Framework/Game.cs @@ -1,16 +1,13 @@ // 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. + +// Copyright (C)2023 Nick Kastellanos using System; using System.Collections.Generic; using System.Diagnostics; -#if WINDOWS_UAP -using System.Threading.Tasks; -using Windows.ApplicationModel.Activation; -#endif - using Microsoft.Xna.Platform; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; @@ -24,12 +21,9 @@ namespace Microsoft.Xna.Framework /// This class is the entry point for most games. Handles setting up /// a window and graphics and runs a game loop that calls and . /// - public partial class Game : IDisposable + public class Game : IDisposable { - private GameComponentCollection _components; - private GameServiceContainer _services; - private ContentManager _content; - internal GamePlatform Platform; + internal GameStrategy Strategy { get; private set; } private DrawableComponents _drawableComponents = new DrawableComponents(); private UpdateableComponents _updateableComponents = new UpdateableComponents(); @@ -38,17 +32,6 @@ public partial class Game : IDisposable private IGraphicsDeviceService _graphicsDeviceService; private bool _initialized = false; - private bool _isFixedTimeStep = true; - - private TimeSpan _targetElapsedTime = TimeSpan.FromTicks(166666); // 60fps - private TimeSpan _inactiveSleepTime = TimeSpan.FromSeconds(0.02); - - private TimeSpan _maxElapsedTime = TimeSpan.FromMilliseconds(500); - - private bool _shouldExit; - private bool _suppressDraw; - - partial void PlatformConstruct(); /// /// Create a . @@ -58,17 +41,12 @@ public Game() _instance = this; LaunchParameters = new LaunchParameters(); - _services = new GameServiceContainer(); - _components = new GameComponentCollection(); - _content = new ContentManager(_services); - Platform = GamePlatform.PlatformCreate(this); - Platform.Activated += Platform_Activated; - Platform.Deactivated += Platform_Deactivated; - _services.AddService(typeof(GamePlatform), Platform); + Strategy = new ConcreteGame(this); - // Allow some optional per-platform construction to occur too. - PlatformConstruct(); + Strategy.Activated += Platform_Activated; + Strategy.Deactivated += Platform_Deactivated; + Services.AddService(typeof(GameStrategy), Strategy); } @@ -80,7 +58,7 @@ public Game() [System.Diagnostics.Conditional("DEBUG")] internal void Log(string Message) { - if (Platform != null) Platform.Log(Message); + if (Strategy != null) Strategy.Log(Message); } void Platform_Activated(object sender, EventArgs e) { OnActivated(e); } @@ -106,18 +84,18 @@ protected virtual void Dispose(bool disposing) if (disposing) { // Dispose loaded game components - for (int i = 0; i < _components.Count; i++) + for (int i = 0; i < Strategy._components.Count; i++) { - var disposable = _components[i] as IDisposable; + var disposable = Strategy._components[i] as IDisposable; if (disposable != null) disposable.Dispose(); } - _components = null; + Strategy._components = null; - if (_content != null) + if (Strategy._content != null) { - _content.Dispose(); - _content = null; + Strategy._content.Dispose(); + Strategy._content = null; } if (_graphicsDeviceManager != null) @@ -126,14 +104,14 @@ protected virtual void Dispose(bool disposing) _graphicsDeviceManager = null; } - if (Platform != null) + if (Strategy != null) { - Platform.Activated -= Platform_Activated; - Platform.Deactivated -= Platform_Deactivated; - _services.RemoveService(typeof(GamePlatform)); + Strategy.Activated -= Platform_Activated; + Strategy.Deactivated -= Platform_Deactivated; + Services.RemoveService(typeof(GameStrategy)); - Platform.Dispose(); - Platform = null; + Strategy.Dispose(); + Strategy = null; } AudioService.Shutdown(); @@ -176,21 +154,12 @@ private void AssertNotDisposed() /// /// A collection of game components attached to this . /// - public GameComponentCollection Components - { - get { return _components; } - } + public GameComponentCollection Components { get { return Strategy.Components; } } public TimeSpan InactiveSleepTime { - get { return _inactiveSleepTime; } - set - { - if (value < TimeSpan.Zero) - throw new ArgumentOutOfRangeException("InactiveSleepTime must be positive."); - - _inactiveSleepTime = value; - } + get { return Strategy.InactiveSleepTime; } + set { Strategy.InactiveSleepTime = value; } } /// @@ -199,14 +168,8 @@ public TimeSpan InactiveSleepTime /// public TimeSpan MaxElapsedTime { - get { return _maxElapsedTime; } - set - { - if (value < TimeSpan.FromMilliseconds(500)) - throw new ArgumentOutOfRangeException("MaxElapsedTime must be at least 0.5s"); - - _maxElapsedTime = value; - } + get { return Strategy.MaxElapsedTime; } + set { Strategy.MaxElapsedTime = value; } } /// @@ -214,13 +177,13 @@ public TimeSpan MaxElapsedTime /// public bool IsActive { - get { return Platform.IsActive; } + get { return Strategy.IsActive; } } public bool IsVisible { - get { return Platform.IsVisible; } + get { return Strategy.IsVisible; } } /// @@ -228,8 +191,8 @@ public bool IsVisible /// public bool IsMouseVisible { - get { return Platform.IsMouseVisible; } - set { Platform.IsMouseVisible = value; } + get { return Strategy.IsMouseVisible; } + set { Strategy.IsMouseVisible = value; } } /// @@ -238,23 +201,8 @@ public bool IsMouseVisible /// Target elapsed time must be strictly larger than zero. 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( - "TargetElapsedTime must be positive and non-zero."); - - if (value != _targetElapsedTime) - { - _targetElapsedTime = value; - Platform.TargetElapsedTimeChanged(); - } - } + get { return Strategy.TargetElapsedTime; } + set { Strategy.TargetElapsedTime = value; } } @@ -266,16 +214,14 @@ public TimeSpan TargetElapsedTime /// public bool IsFixedTimeStep { - get { return _isFixedTimeStep; } - set { _isFixedTimeStep = value; } + get { return Strategy.IsFixedTimeStep; } + set { Strategy.IsFixedTimeStep = value; } } /// /// Get a container holding service providers attached to this . /// - public GameServiceContainer Services { - get { return _services; } - } + public GameServiceContainer Services { get { return Strategy.Services; } } /// @@ -284,14 +230,8 @@ public GameServiceContainer Services { /// If Content is set to null. public ContentManager Content { - get { return _content; } - set - { - if (value == null) - throw new ArgumentNullException(); - - _content = value; - } + get { return Strategy.Content; } + set { Strategy.Content = value; } } /// @@ -322,7 +262,7 @@ public GraphicsDevice GraphicsDevice [CLSCompliant(false)] public GameWindow Window { - get { return Platform.Window; } + get { return Strategy.Window; } } #endregion Properties @@ -364,7 +304,7 @@ internal bool Initialized #if WINDOWS_UAP [CLSCompliant(false)] - public ApplicationExecutionState PreviousExecutionState { get; internal set; } + public Windows.ApplicationModel.Activation.ApplicationExecutionState PreviousExecutionState { get; internal set; } #endif #endregion @@ -376,11 +316,7 @@ internal bool Initialized /// public void Exit() { -#if ANDROID || IOS || TVOS - throw new InvalidOperationException("This platform's policy does not allow programmatically closing."); -#endif - _shouldExit = true; - _suppressDraw = true; + Strategy.Exit(); } /// @@ -388,10 +324,7 @@ public void Exit() /// public void ResetElapsedTime() { - Platform.ResetElapsedTime(); - - _accumulatedElapsedTime = TimeSpan.Zero; - _previousElapsedTime = TimeSpan.Zero; + Strategy.ResetElapsedTime(); } /// @@ -399,7 +332,7 @@ public void ResetElapsedTime() /// public void SuppressDraw() { - _suppressDraw = true; + Strategy.SuppressDraw(); } /// @@ -407,16 +340,16 @@ public void SuppressDraw() /// public void RunOneFrame() { - if (Platform == null) + if (Strategy == null) return; - if (!Platform.ANDROID_BeforeRun()) + if (!Strategy.ANDROID_BeforeRun()) return; if (!Initialized) { DoInitialize(); - Platform.Timer = Stopwatch.StartNew(); + Strategy.Timer = Stopwatch.StartNew(); } BeginRun(); @@ -435,7 +368,7 @@ public void Run() { AssertNotDisposed(); - Platform.Run(); + Strategy.Run(); } /// @@ -445,27 +378,20 @@ internal void Run_UAP_XAML() { AssertNotDisposed(); - Platform.Run_UAP_XAML(); + Strategy.Run_UAP_XAML(); } internal void DoBeginRun() { BeginRun(); - Platform.Timer = Stopwatch.StartNew(); + Strategy.Timer = Stopwatch.StartNew(); } internal void DoEndRun() { EndRun(); } - - private TimeSpan _accumulatedElapsedTime; - private TimeSpan _previousElapsedTime; - private int _updateFrameLag; -#if WINDOWS_UAP - private readonly object _locker = new object(); -#endif - + /// /// Run one iteration of the game loop. /// @@ -475,116 +401,11 @@ internal void DoEndRun() /// make exactly one call to . /// 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. - - // implement InactiveSleepTime to save battery life - // and/or release CPU time to other threads and processes. - if (!IsActive) - { -#if WINDOWS_UAP - lock (_locker) - System.Threading.Monitor.Wait(_locker, (int)InactiveSleepTime.TotalMilliseconds); -#else - System.Threading.Thread.Sleep((int)InactiveSleepTime.TotalMilliseconds); -#endif - } - - RetryTick: - - // Advance the accumulated elapsed time. - TimeSpan elapsedTime = Platform.Timer.Elapsed; - TimeSpan elapsedTimeDiff = TimeSpan.FromTicks(elapsedTime.Ticks - _previousElapsedTime.Ticks); - _previousElapsedTime = elapsedTime; - - _accumulatedElapsedTime += elapsedTimeDiff; - - if (IsFixedTimeStep && _accumulatedElapsedTime < TargetElapsedTime) - { - // When game IsActive use CPU Spin. - /* - if ((TargetElapsedTime - _accumulatedElapsedTime).TotalMilliseconds >= 2.0) - { -#if WINDOWS || DESKTOPGL || ANDROID || IOS || TVOS - System.Threading.Thread.Sleep(0); -#elif WINDOWS_UAP - lock (_locker) - System.Threading.Monitor.Wait(_locker, 0); -#endif - } - */ - - // Keep looping until it's time to perform the next update - goto RetryTick; - } + { + Strategy.Tick(); - // Do not allow any update to take longer than our maximum. - var maxElapsedTime = TimeSpan.FromTicks(Math.Max(_maxElapsedTime.Ticks, _targetElapsedTime.Ticks)); - if (_accumulatedElapsedTime > maxElapsedTime) - _accumulatedElapsedTime = maxElapsedTime; - - if (IsFixedTimeStep) - { - Platform.Time.ElapsedGameTime = TargetElapsedTime; - int stepCount = 0; - - // Perform as many full fixed length time steps as we can. - while (_accumulatedElapsedTime >= TargetElapsedTime && !_shouldExit) - { - Platform.Time.TotalGameTime += TargetElapsedTime; - _accumulatedElapsedTime -= TargetElapsedTime; - stepCount++; - - DoUpdate(Platform.Time); - } - - //Every update after the first accumulates lag - _updateFrameLag += Math.Max(0, stepCount - 1); - _updateFrameLag = Math.Min(_updateFrameLag, 5); - - //If we think we are running slowly, wait until the lag clears before resetting it - if (Platform.Time.IsRunningSlowly) - { - if (_updateFrameLag == 0) - Platform.Time.IsRunningSlowly = false; - } - else if (_updateFrameLag >= 5) - { - //If we lag more than 5 frames, start thinking we are running slowly - Platform.Time.IsRunningSlowly = true; - } - - //Every time we just do one update and one draw, then we are not running slowly, so decrease the lag - if (stepCount == 1 && _updateFrameLag > 0) - _updateFrameLag--; - - // Draw needs to know the total elapsed time - // that occured for the fixed length updates. - Platform.Time.ElapsedGameTime = TimeSpan.FromTicks(TargetElapsedTime.Ticks * stepCount); - } - else - { - // Perform a single variable length update. - Platform.Time.ElapsedGameTime = _accumulatedElapsedTime; - Platform.Time.TotalGameTime += _accumulatedElapsedTime; - _accumulatedElapsedTime = TimeSpan.Zero; - - DoUpdate(Platform.Time); - } - - // Draw unless the update suppressed it. - if (_suppressDraw) - _suppressDraw = false; - else - { - DoDraw(Platform.Time); - } - - if (_shouldExit) - Platform.Exit(); + if (Strategy.ShouldExit) + Strategy.TickExiting(); } #endregion @@ -606,7 +427,7 @@ public void Tick() /// protected virtual void EndDraw() { - Platform.Present(); + Strategy.EndDraw(); } /// @@ -640,18 +461,18 @@ protected virtual void Initialize() #if ANDROID || IOS || TVOS // applyChanges { - Platform.BeginScreenDeviceChange(GraphicsDevice.PresentationParameters.IsFullScreen); + Strategy.BeginScreenDeviceChange(GraphicsDevice.PresentationParameters.IsFullScreen); if (GraphicsDevice.PresentationParameters.IsFullScreen) - Platform.EnterFullScreen(); + Strategy.EnterFullScreen(); else - Platform.ExitFullScreen(); + Strategy.ExitFullScreen(); var viewport = new Viewport(0, 0, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight); GraphicsDevice.Viewport = viewport; - Platform.EndScreenDeviceChange(string.Empty, viewport.Width, viewport.Height); + Strategy.EndScreenDeviceChange(string.Empty, viewport.Width, viewport.Height); } #endif @@ -738,8 +559,7 @@ protected virtual void OnDeactivated(EventArgs args) #region Event Handlers - private void Components_ComponentAdded( - object sender, GameComponentCollectionEventArgs e) + private void Components_ComponentAdded(object sender, GameComponentCollectionEventArgs e) { // Since we only subscribe to ComponentAdded after the graphics // devices are set up, it is safe to just blindly call Initialize. @@ -751,8 +571,7 @@ private void Components_ComponentAdded( _drawableComponents.AddDrawable((IDrawable)e.GameComponent); } - private void Components_ComponentRemoved( - object sender, GameComponentCollectionEventArgs e) + private void Components_ComponentRemoved(object sender, GameComponentCollectionEventArgs e) { if (e.GameComponent is IUpdateable) _updateableComponents.RemoveUpdatable((IUpdateable)e.GameComponent); @@ -764,15 +583,11 @@ private void Components_ComponentRemoved( #region Internal Methods - // FIXME: We should work toward eliminating internal methods. They - // break entirely the possibility that additional platforms could - // be added by third parties without changing MonoGame itself. - internal void DoUpdate(GameTime gameTime) { AssertNotDisposed(); - if (Platform.BeforeUpdate(gameTime)) + if (Strategy.BeforeUpdate(gameTime)) { ((IFrameworkDispatcher)FrameworkDispatcher.Current).Update(); @@ -790,7 +605,7 @@ internal void DoDraw(GameTime gameTime) // Draw and EndDraw should not be called if BeginDraw returns false. // http://stackoverflow.com/questions/4054936/manual-control-over-when-to-redraw-the-screen/4057180#4057180 // http://stackoverflow.com/questions/4235439/xna-3-1-to-4-0-requires-constant-redraw-or-will-display-a-purple-screen - if (Platform.BeforeDraw(gameTime) && BeginDraw()) + if (Strategy.BeforeDraw(gameTime) && BeginDraw()) { Draw(gameTime); EndDraw(); @@ -804,7 +619,7 @@ internal void DoInitialize() if (GraphicsDevice == null && graphicsDeviceManager != null) ((IGraphicsDeviceManager)graphicsDeviceManager).CreateDevice(); - Platform.BeforeInitialize(); + Strategy.BeforeInitialize(); Initialize(); // We need to do this after virtual Initialize(...) is called. @@ -823,8 +638,8 @@ internal void DoInitialize() _drawableComponents.AddDrawable((IDrawable)Components[i]); } - _components.ComponentAdded += Components_ComponentAdded; - _components.ComponentRemoved += Components_ComponentRemoved; + Components.ComponentAdded += Components_ComponentAdded; + Components.ComponentRemoved += Components_ComponentRemoved; _initialized = true; } diff --git a/MonoGame.Framework/GamePlatform.Blazor.cs b/MonoGame.Framework/GamePlatform.Blazor.cs deleted file mode 100644 index 928fc301a00..00000000000 --- a/MonoGame.Framework/GamePlatform.Blazor.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C)2022 Nick Kastellanos - -using System; - - -namespace Microsoft.Xna.Framework -{ - partial class GamePlatform - { - internal static GamePlatform PlatformCreate(Game game) - { - return new MonoGame.Framework.BlazorGamePlatform(game); - } - } -} diff --git a/MonoGame.Framework/GamePlatform.Desktop.cs b/MonoGame.Framework/GamePlatform.Desktop.cs deleted file mode 100644 index 6a03d7d5421..00000000000 --- a/MonoGame.Framework/GamePlatform.Desktop.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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; - -#if WINDOWS_UAP -using Windows.UI.ViewManagement; -#endif - -namespace Microsoft.Xna.Framework -{ - partial class GamePlatform - { - internal static GamePlatform PlatformCreate(Game game) - { -#if DESKTOPGL - return new SdlGamePlatform(game); -#elif WINDOWS - return new MonoGame.Framework.WinFormsGamePlatform(game); -#elif WINDOWS_UAP - return new UAPGamePlatform(game); -#endif - } - } -} diff --git a/MonoGame.Framework/GamePlatform.Mobile.cs b/MonoGame.Framework/GamePlatform.Mobile.cs deleted file mode 100644 index 96e4ed8949a..00000000000 --- a/MonoGame.Framework/GamePlatform.Mobile.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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; - -namespace Microsoft.Xna.Framework -{ - partial class GamePlatform - { - internal static GamePlatform PlatformCreate(Game game) - { -#if IOS || TVOS - return new iOSGamePlatform(game); -#elif ANDROID - return new AndroidGamePlatform(game); -#endif - } - } -} diff --git a/MonoGame.Framework/GamePlatform.Ref.cs b/MonoGame.Framework/GamePlatform.Ref.cs index d51a5ed288c..fad02a95150 100644 --- a/MonoGame.Framework/GamePlatform.Ref.cs +++ b/MonoGame.Framework/GamePlatform.Ref.cs @@ -1,16 +1,80 @@ -// Copyright (C)2022 Nick Kastellanos +// Copyright (C)2023 Nick Kastellanos using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; -namespace Microsoft.Xna.Framework +namespace Microsoft.Xna.Platform { - partial class GamePlatform + sealed class ConcreteGame : GameStrategy { + public ConcreteGame(Game game) : base(game) + { + } - internal static GamePlatform PlatformCreate(Game game) + internal override void Run() { throw new PlatformNotSupportedException(); } + public override void Tick() + { + throw new PlatformNotSupportedException(); + } + + public override void BeforeInitialize() + { + throw new PlatformNotSupportedException(); + } + + public override void TickExiting() + { + throw new PlatformNotSupportedException(); + } + + public override bool BeforeUpdate(GameTime gameTime) + { + throw new PlatformNotSupportedException(); + } + + public override bool BeforeDraw(GameTime gameTime) + { + throw new PlatformNotSupportedException(); + } + + public override void EnterFullScreen() + { + throw new PlatformNotSupportedException(); + } + + public override void ExitFullScreen() + { + throw new PlatformNotSupportedException(); + } + + internal override void OnPresentationChanged(PresentationParameters pp) + { + throw new PlatformNotSupportedException(); + } + + public override void EndScreenDeviceChange(string screenDeviceName, int clientWidth, int clientHeight) + { + throw new PlatformNotSupportedException(); + } + + public override void BeginScreenDeviceChange(bool willBeFullScreen) + { + throw new PlatformNotSupportedException(); + } + + public override void Log(string message) + { + throw new PlatformNotSupportedException(); + } + + public override void EndDraw() + { + throw new PlatformNotSupportedException(); + } } } diff --git a/MonoGame.Framework/GamePlatform.cs b/MonoGame.Framework/GamePlatform.cs index 50773280a2e..a9fc2335262 100644 --- a/MonoGame.Framework/GamePlatform.cs +++ b/MonoGame.Framework/GamePlatform.cs @@ -2,38 +2,59 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. +// Copyright (C)2022 Nick Kastellanos + using System; using System.Diagnostics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; -namespace Microsoft.Xna.Framework +namespace Microsoft.Xna.Platform { - abstract partial class GamePlatform : IDisposable + abstract class GameStrategy : IDisposable { #region Fields - protected TimeSpan _inactiveSleepTime = TimeSpan.FromMilliseconds(20.0); - protected bool _needsToResetElapsedTime = false; - bool disposed; + private GameServiceContainer _services; + internal GameComponentCollection _components; + internal ContentManager _content; + + private TimeSpan _targetElapsedTime = TimeSpan.FromTicks(166666); // 60fps + private TimeSpan _inactiveSleepTime = TimeSpan.FromMilliseconds(20.0); + + private TimeSpan _maxElapsedTime = TimeSpan.FromMilliseconds(500); + + private bool _isFixedTimeStep = true; + + private bool _shouldExit; + private bool _suppressDraw; + + bool _isDisposed; + protected bool InFullScreenMode = false; - protected bool IsDisposed { get { return disposed; } } + protected bool IsDisposed { get { return _isDisposed; } } #endregion #region Construction/Destruction - protected GamePlatform(Game game) + protected GameStrategy(Game game) { if (game == null) throw new ArgumentNullException("game"); Game = game; + + _services = new GameServiceContainer(); + _components = new GameComponentCollection(); + _content = new ContentManager(_services); } - ~GamePlatform() + ~GameStrategy() { Dispose(false); } @@ -42,14 +63,39 @@ protected GamePlatform(Game game) #region Public Properties + /// + /// Get a container holding service providers attached to this . + /// + public GameServiceContainer Services { get { return _services; } } + + /// + /// A collection of game components attached to this . + /// + public GameComponentCollection Components { get { return _components; } } + + /// + /// The of this . + /// + /// If Content is set to null. + public ContentManager Content + { + get { return _content; } + set + { + if (value == null) + throw new ArgumentNullException(); + + _content = value; + } + } /// /// Gets the Game instance that owns this GamePlatform instance. /// public readonly Game Game; - public readonly GameTime Time = new GameTime(); - public Stopwatch Timer; + private readonly GameTime Time = new GameTime(); + internal Stopwatch Timer; private bool _isActive; public bool IsActive @@ -76,17 +122,10 @@ public bool IsVisible } private bool _isMouseVisible; - public bool IsMouseVisible + public virtual bool IsMouseVisible { get { return _isMouseVisible; } - set - { - if (_isMouseVisible != value) - { - _isMouseVisible = value; - OnIsMouseVisibleChanged(); - } - } + set { _isMouseVisible = value; } } private GameWindow _window; @@ -105,6 +144,64 @@ protected set } } + public TimeSpan InactiveSleepTime + { + get { return _inactiveSleepTime; } + set + { + if (value < TimeSpan.Zero) + throw new ArgumentOutOfRangeException("InactiveSleepTime must be positive."); + + _inactiveSleepTime = value; + } + } + + /// + /// The maximum amount of time we will frameskip over and only perform Update calls with no Draw calls. + /// MonoGame extension. + /// + public TimeSpan MaxElapsedTime + { + get { return _maxElapsedTime; } + set + { + if (value < TimeSpan.FromMilliseconds(500)) + throw new ArgumentOutOfRangeException("MaxElapsedTime must be at least 0.5s"); + + _maxElapsedTime = value; + } + } + + /// + /// The time between frames when running with a fixed time step. + /// + /// Target elapsed time must be strictly larger than zero. + public virtual TimeSpan TargetElapsedTime + { + get { return _targetElapsedTime; } + set + { + if (value <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException("TargetElapsedTime must be positive and non-zero."); + + _targetElapsedTime = value; + } + } + + /// + /// Indicates if this game is running with a fixed time between frames. + /// + /// When set to true the target time between frames is + /// given by . + /// + public bool IsFixedTimeStep + { + get { return _isFixedTimeStep; } + set { _isFixedTimeStep = value; } + } + + public bool ShouldExit { get { return _shouldExit; } } + #endregion #region Events @@ -148,7 +245,13 @@ public virtual bool ANDROID_BeforeRun() /// /// When implemented in a derived, ends the active run loop. /// - public abstract void Exit(); + public virtual void Exit() + { + _shouldExit = true; + _suppressDraw = true; + } + + public abstract void TickExiting(); /// /// Gives derived classes an opportunity to do work just before Update @@ -180,25 +283,13 @@ public virtual bool ANDROID_BeforeRun() /// public abstract void ExitFullScreen(); - /// - /// Gives derived classes an opportunity to modify - /// Game.TargetElapsedTime before it is set. - /// - /// The proposed new value of TargetElapsedTime. - /// The new value of TargetElapsedTime that will be set. - public virtual TimeSpan TargetElapsedTimeChanging(TimeSpan value) - { - return value; - } /// /// Starts a device transition (windowed to full screen or vice versa). /// /// /// Specifies whether the device will be in full-screen mode upon completion of the change. /// - public abstract void BeginScreenDeviceChange ( - bool willBeFullScreen - ); + public abstract void BeginScreenDeviceChange(bool willBeFullScreen); /// /// Completes a device transition. @@ -212,18 +303,12 @@ bool willBeFullScreen /// /// The new height of the game's client window. /// - public abstract void EndScreenDeviceChange ( + public abstract void EndScreenDeviceChange( string screenDeviceName, int clientWidth, int clientHeight ); - /// - /// Gives derived classes an opportunity to take action after - /// Game.TargetElapsedTime has been set. - /// - public virtual void TargetElapsedTimeChanged() {} - /// /// MSDN: Use this method if your game is recovering from a slow-running state, and ElapsedGameTime is too large to be useful. /// Frame timing is generally handled by the Game class, but some platforms still handle it elsewhere. Once all platforms @@ -238,11 +323,20 @@ public virtual void ResetElapsedTime() } Time.ElapsedGameTime = TimeSpan.Zero; - } - public virtual void Present() { } + _currElapsedTime = TimeSpan.Zero; + _prevElapsedTime = TimeSpan.Zero; + } + + /// + /// Supress calling in the game loop. + /// + public void SuppressDraw() + { + _suppressDraw = true; + } - protected virtual void OnIsMouseVisibleChanged() {} + public virtual void EndDraw() { } /// /// Called by the GraphicsDeviceManager to notify the platform @@ -253,6 +347,123 @@ internal virtual void OnPresentationChanged(PresentationParameters pp) { } +#if WINDOWS_UAP + private readonly object _locker = new object(); +#endif + + private TimeSpan _currElapsedTime; + private TimeSpan _prevElapsedTime; + private int _updateFrameLag; + + /// + /// Run one iteration of the game loop. + /// + /// Makes at least one call to + /// and exactly one call to if drawing is not supressed. + /// When is set to false this will + /// make exactly one call to . + /// + public virtual void Tick() + { + // implement InactiveSleepTime to save battery life + // and/or release CPU time to other threads and processes. + if (!IsActive) + { +#if WINDOWS_UAP + lock (_locker) + System.Threading.Monitor.Wait(_locker, (int)InactiveSleepTime.TotalMilliseconds); +#else + System.Threading.Thread.Sleep((int)InactiveSleepTime.TotalMilliseconds); +#endif + } + + RetryTick: + + // Advance the accumulated elapsed time. + TimeSpan elapsedTime = Timer.Elapsed; + TimeSpan dt = elapsedTime - _prevElapsedTime; + _currElapsedTime += dt; + _prevElapsedTime = elapsedTime; + + if (IsFixedTimeStep && _currElapsedTime < TargetElapsedTime) + { + // When game IsActive use CPU Spin. + /* + if ((TargetElapsedTime - _accumulatedElapsedTime).TotalMilliseconds >= 2.0) + { +#if WINDOWS || DESKTOPGL || ANDROID || IOS || TVOS + System.Threading.Thread.Sleep(0); +#elif WINDOWS_UAP + lock (_locker) + System.Threading.Monitor.Wait(_locker, 0); +#endif + } + */ + + // Keep looping until it's time to perform the next update + goto RetryTick; + } + + // Do not allow any update to take longer than our maximum. + var maxElapsedTime = TimeSpan.FromTicks(Math.Max(MaxElapsedTime.Ticks, TargetElapsedTime.Ticks)); + if (_currElapsedTime > maxElapsedTime) + _currElapsedTime = maxElapsedTime; + + if (IsFixedTimeStep) + { + Time.ElapsedGameTime = TargetElapsedTime; + int stepCount = 0; + + // Perform as many full fixed length time steps as we can. + while (_currElapsedTime >= TargetElapsedTime && !_shouldExit) + { + Time.TotalGameTime += TargetElapsedTime; + _currElapsedTime -= TargetElapsedTime; + stepCount++; + + Game.DoUpdate(Time); + } + + //Every update after the first accumulates lag + _updateFrameLag += Math.Max(0, stepCount - 1); + _updateFrameLag = Math.Min(_updateFrameLag, 5); + + //If we think we are running slowly, wait until the lag clears before resetting it + if (Time.IsRunningSlowly) + { + if (_updateFrameLag == 0) + Time.IsRunningSlowly = false; + } + else if (_updateFrameLag >= 5) + { + //If we lag more than 5 frames, start thinking we are running slowly + Time.IsRunningSlowly = true; + } + + //Every time we just do one update and one draw, then we are not running slowly, so decrease the lag + if (stepCount == 1 && _updateFrameLag > 0) + _updateFrameLag--; + + // Draw needs to know the total elapsed time + // that occured for the fixed length updates. + Time.ElapsedGameTime = TimeSpan.FromTicks(TargetElapsedTime.Ticks * stepCount); + } + else + { + // Perform a single variable length update. + Time.ElapsedGameTime = _currElapsedTime; + Time.TotalGameTime += _currElapsedTime; + _currElapsedTime = TimeSpan.Zero; + + Game.DoUpdate(Time); + } + + // Draw unless the update suppressed it. + if (!_suppressDraw) + Game.DoDraw(Time); + _suppressDraw = false; + } + #endregion Methods #region IDisposable implementation @@ -269,12 +480,12 @@ public void Dispose() protected virtual void Dispose(bool disposing) { - if (!disposed) + if (!_isDisposed) { Mouse.PrimaryWindow = null; TouchPanel.PrimaryWindow = null; - disposed = true; + _isDisposed = true; } } diff --git a/MonoGame.Framework/Media/VideoPlayer.Android.cs b/MonoGame.Framework/Media/VideoPlayer.Android.cs index 8a79e9660bb..136aad5d282 100644 --- a/MonoGame.Framework/Media/VideoPlayer.Android.cs +++ b/MonoGame.Framework/Media/VideoPlayer.Android.cs @@ -5,6 +5,7 @@ using System; using Android.Widget; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Platform; namespace Microsoft.Xna.Framework.Media { @@ -40,15 +41,15 @@ private void PlatformPlay() { _currentVideo.Player.SetDisplay(((AndroidGameWindow)_game.Window).GameView.Holder); _currentVideo.Player.Start(); - - AndroidGamePlatform.IsPlayingVdeo = true; + + ConcreteGame.IsPlayingVdeo = true; } private void PlatformStop() { _currentVideo.Player.Stop(); - AndroidGamePlatform.IsPlayingVdeo = false; + ConcreteGame.IsPlayingVdeo = false; _currentVideo.Player.SetDisplay(null); } diff --git a/MonoGame.Framework/Media/VideoPlayer.iOS.cs b/MonoGame.Framework/Media/VideoPlayer.iOS.cs index c222095c79d..5da7ecab025 100644 --- a/MonoGame.Framework/Media/VideoPlayer.iOS.cs +++ b/MonoGame.Framework/Media/VideoPlayer.iOS.cs @@ -4,6 +4,7 @@ using System; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Platform; using MediaPlayer; using Foundation; using UIKit; @@ -13,13 +14,13 @@ namespace Microsoft.Xna.Framework.Media public sealed partial class VideoPlayer : IDisposable { private Game _game; - private iOSGamePlatform _platform; + private ConcreteGame _platform; private NSObject _playbackDidFinishObserver; private void PlatformInitialize() { _game = Game.Instance; - _platform = (iOSGamePlatform)_game.Services.GetService(typeof(iOSGamePlatform)); + _platform = (ConcreteGame)_game.Services.GetService(typeof(ConcreteGame)); if (_platform == null) throw new InvalidOperationException("No iOSGamePlatform instance was available"); diff --git a/MonoGame.Framework/MonoGame.Framework.Android.csproj b/MonoGame.Framework/MonoGame.Framework.Android.csproj index 79bbfa3fdb1..137fd79154e 100644 --- a/MonoGame.Framework/MonoGame.Framework.Android.csproj +++ b/MonoGame.Framework/MonoGame.Framework.Android.csproj @@ -51,7 +51,6 @@ - diff --git a/MonoGame.Framework/MonoGame.Framework.DesktopGL.csproj b/MonoGame.Framework/MonoGame.Framework.DesktopGL.csproj index 6b5adbf311f..8aeb16f4d35 100644 --- a/MonoGame.Framework/MonoGame.Framework.DesktopGL.csproj +++ b/MonoGame.Framework/MonoGame.Framework.DesktopGL.csproj @@ -71,7 +71,6 @@ - diff --git a/MonoGame.Framework/MonoGame.Framework.WindowsDX.csproj b/MonoGame.Framework/MonoGame.Framework.WindowsDX.csproj index 39da9e867fe..7ee9a5ace02 100644 --- a/MonoGame.Framework/MonoGame.Framework.WindowsDX.csproj +++ b/MonoGame.Framework/MonoGame.Framework.WindowsDX.csproj @@ -40,7 +40,6 @@ - diff --git a/MonoGame.Framework/MonoGame.Framework.WindowsUniversal.csproj b/MonoGame.Framework/MonoGame.Framework.WindowsUniversal.csproj index 38acc547808..3f46eb70bbe 100644 --- a/MonoGame.Framework/MonoGame.Framework.WindowsUniversal.csproj +++ b/MonoGame.Framework/MonoGame.Framework.WindowsUniversal.csproj @@ -50,7 +50,6 @@ - diff --git a/MonoGame.Framework/MonoGame.Framework.iOS.csproj b/MonoGame.Framework/MonoGame.Framework.iOS.csproj index 3687ff06e0f..2f63d42bd53 100644 --- a/MonoGame.Framework/MonoGame.Framework.iOS.csproj +++ b/MonoGame.Framework/MonoGame.Framework.iOS.csproj @@ -44,7 +44,6 @@ - diff --git a/MonoGame.Framework/Platform/Android/AndroidGameActivity.cs b/MonoGame.Framework/Platform/Android/AndroidGameActivity.cs index 16cd6392d91..0fd1b24f106 100644 --- a/MonoGame.Framework/Platform/Android/AndroidGameActivity.cs +++ b/MonoGame.Framework/Platform/Android/AndroidGameActivity.cs @@ -7,6 +7,7 @@ using Android.Content; using Android.OS; using Android.Views; +using Microsoft.Xna.Platform; namespace Microsoft.Xna.Framework @@ -94,7 +95,7 @@ protected override void OnResume() public override void OnWindowFocusChanged(bool hasFocus) { base.OnWindowFocusChanged(hasFocus); - ((AndroidGamePlatform)Game.Platform).OnWindowFocusChanged(hasFocus); + ((ConcreteGame)Game.Strategy).OnWindowFocusChanged(hasFocus); } protected override void OnDestroy() diff --git a/MonoGame.Framework/Platform/Android/AndroidGamePlatform.cs b/MonoGame.Framework/Platform/Android/AndroidGamePlatform.cs index 91dc1fbc1b2..a1db078d152 100644 --- a/MonoGame.Framework/Platform/Android/AndroidGamePlatform.cs +++ b/MonoGame.Framework/Platform/Android/AndroidGamePlatform.cs @@ -2,17 +2,20 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. +// Copyright (C)2023 Nick Kastellanos + using System; using Android.Views; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Media; -namespace Microsoft.Xna.Framework +namespace Microsoft.Xna.Platform { - class AndroidGamePlatform : GamePlatform + sealed class ConcreteGame : GameStrategy { - public AndroidGamePlatform(Game game) + public ConcreteGame(Game game) : base(game) { System.Diagnostics.Debug.Assert(Game.Activity != null, "Must set Game.Activity before creating the Game instance"); @@ -41,6 +44,11 @@ protected override void Dispose(bool disposing) private AndroidGameWindow _gameWindow; public override void Exit() + { + throw new InvalidOperationException("This platform's policy does not allow programmatically closing."); + } + + public override void TickExiting() { // Do Nothing: Android games do not "exit" or shut down. throw new NotImplementedException(); @@ -170,6 +178,11 @@ internal override void Run() //Game.DoExiting(); } + public override void Tick() + { + base.Tick(); + } + public override void Log(string Message) { #if LOGGING @@ -177,15 +190,13 @@ public override void Log(string Message) #endif } - public override void Present() + public override void EndDraw() { try { var device = Game.GraphicsDevice; if (device != null) - { device.Present(); - } _gameWindow.GameView.SwapBuffers(); } diff --git a/MonoGame.Framework/Platform/Android/AndroidGameWindow.cs b/MonoGame.Framework/Platform/Android/AndroidGameWindow.cs index 4343655c651..0231c3238a7 100644 --- a/MonoGame.Framework/Platform/Android/AndroidGameWindow.cs +++ b/MonoGame.Framework/Platform/Android/AndroidGameWindow.cs @@ -8,7 +8,9 @@ using Android.OS; using Android.Views; using Microsoft.Xna.Framework.Input.Touch; -using MonoGame.OpenGL; +using Microsoft.Xna.Platform; +using MonoGame.OpenGL; + namespace Microsoft.Xna.Framework { @@ -76,7 +78,7 @@ private void OnTick(object sender, EventArgs args) if (_game != null) { - if (!GameView.IsResuming && ((AndroidGamePlatform)_game.Platform).IsActivityActive && !ScreenReceiver.ScreenLocked) //Only call draw if an update has occured + if (!GameView.IsResuming && ((ConcreteGame)_game.Strategy).IsActivityActive && !ScreenReceiver.ScreenLocked) //Only call draw if an update has occured { _game.Tick(); } @@ -87,7 +89,7 @@ private void OnTick(object sender, EventArgs args) { Resumer.Draw(); } - _game.Platform.Present(); + _game.Strategy.EndDraw(); } } diff --git a/MonoGame.Framework/Platform/Blazor/BlazorGamePlatform.cs b/MonoGame.Framework/Platform/Blazor/BlazorGamePlatform.cs index 3ddae8d041b..1b6adc08fe6 100644 --- a/MonoGame.Framework/Platform/Blazor/BlazorGamePlatform.cs +++ b/MonoGame.Framework/Platform/Blazor/BlazorGamePlatform.cs @@ -2,21 +2,24 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. +// Copyright (C)2023 Nick Kastellanos + using System; using System.Diagnostics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; -namespace MonoGame.Framework + +namespace Microsoft.Xna.Platform { - class BlazorGamePlatform : GamePlatform + sealed class ConcreteGame : GameStrategy { //internal static string LaunchParameters; private BlazorGameWindow _window; - public BlazorGamePlatform(Game game) + public ConcreteGame(Game game) : base(game) { IsActive = true; @@ -43,9 +46,22 @@ internal override void Run() //Game.DoExiting(); } - protected override void OnIsMouseVisibleChanged() + public override void Tick() + { + base.Tick(); + } + + public override bool IsMouseVisible { - _window.MouseVisibleToggled(); + get { return base.IsMouseVisible; } + set + { + if (base.IsMouseVisible != value) + { + base.IsMouseVisible = value; + _window.MouseVisibleToggled(); + } + } } public override void BeforeInitialize() @@ -64,10 +80,11 @@ public override void BeforeInitialize() } } - public override void Exit() + public override void TickExiting() { if (_window != null) _window.Dispose(); + _window = null; Window = null; } @@ -108,10 +125,10 @@ public override void Log(string message) Debug.WriteLine(message); } - public override void Present() + public override void EndDraw() { var device = Game.GraphicsDevice; - if ( device != null ) + if (device != null) device.Present(); } diff --git a/MonoGame.Framework/Platform/Blazor/BlazorGameWindow.cs b/MonoGame.Framework/Platform/Blazor/BlazorGameWindow.cs index 20f4065d3c6..b30e5761ec9 100644 --- a/MonoGame.Framework/Platform/Blazor/BlazorGameWindow.cs +++ b/MonoGame.Framework/Platform/Blazor/BlazorGameWindow.cs @@ -12,7 +12,7 @@ using nkast.Wasm.Canvas; using nkast.Wasm.Dom; -namespace MonoGame.Framework +namespace Microsoft.Xna.Framework { // TODO: BlazorGameWindow should be internal public class BlazorGameWindow : GameWindow, IDisposable @@ -25,7 +25,7 @@ internal static BlazorGameWindow FromHandle(IntPtr handle) } private Window _window; - private BlazorGamePlatform _platform; + private ConcreteGame _platform; private bool _isResizable; private bool _isBorderless; @@ -115,7 +115,7 @@ public override bool IsBorderless internal Canvas _canvas { get; private set; } internal Window wasmWindow { get { return _window; } } - internal BlazorGameWindow(BlazorGamePlatform platform) + internal BlazorGameWindow(ConcreteGame platform) { _platform = platform; diff --git a/MonoGame.Framework/Platform/SDL/SDLGamePlatform.cs b/MonoGame.Framework/Platform/SDL/SDLGamePlatform.cs index 57afe25968c..52b2bbfd811 100644 --- a/MonoGame.Framework/Platform/SDL/SDLGamePlatform.cs +++ b/MonoGame.Framework/Platform/SDL/SDLGamePlatform.cs @@ -2,19 +2,23 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. +// Copyright (C)2023 Nick Kastellanos + using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading; using System.Runtime.InteropServices; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using MonoGame.Framework.Utilities; -namespace Microsoft.Xna.Framework + +namespace Microsoft.Xna.Platform { - internal class SdlGamePlatform : GamePlatform + sealed class ConcreteGame : GameStrategy { internal override void Run() { @@ -33,6 +37,11 @@ internal override void Run() Game.DoExiting(); } + public override void Tick() + { + base.Tick(); + } + private readonly List _keys; private int _isExiting; @@ -40,7 +49,7 @@ internal override void Run() private readonly List _dropList; - public SdlGamePlatform(Game game) + public ConcreteGame(Game game) : base(game) { _keys = new List(); @@ -84,9 +93,17 @@ public override void BeforeInitialize() base.BeforeInitialize(); } - protected override void OnIsMouseVisibleChanged() + public override bool IsMouseVisible { - _view.SetCursorVisible(Game.IsMouseVisible); + get { return base.IsMouseVisible; } + set + { + if (base.IsMouseVisible != value) + { + base.IsMouseVisible = value; + _view.SetCursorVisible(Game.IsMouseVisible); + } + } } internal override void OnPresentationChanged(PresentationParameters pp) @@ -292,7 +309,7 @@ private int UTF8ToUnicode(int utf8) return -1; } - public override void Exit() + public override void TickExiting() { Interlocked.Increment(ref _isExiting); } @@ -330,10 +347,11 @@ public override void Log(string message) Console.WriteLine(message); } - public override void Present() + public override void EndDraw() { - if (Game.GraphicsDevice != null) - Game.GraphicsDevice.Present(); + var device = Game.GraphicsDevice; + if (device != null) + device.Present(); } protected override void Dispose(bool disposing) diff --git a/MonoGame.Framework/Platform/Windows/WinFormsGameForm.cs b/MonoGame.Framework/Platform/Windows/WinFormsGameForm.cs index 15aa1806e2b..f17d55a3f10 100644 --- a/MonoGame.Framework/Platform/Windows/WinFormsGameForm.cs +++ b/MonoGame.Framework/Platform/Windows/WinFormsGameForm.cs @@ -11,7 +11,6 @@ using Microsoft.Xna.Framework.Input.Touch; using MonoGame.Framework; - namespace Microsoft.Xna.Framework.Windows { internal static class MessageExtensions diff --git a/MonoGame.Framework/Platform/Windows/WinFormsGamePlatform.cs b/MonoGame.Framework/Platform/Windows/WinFormsGamePlatform.cs index 76ff7bc8d5b..95ec5e767c5 100644 --- a/MonoGame.Framework/Platform/Windows/WinFormsGamePlatform.cs +++ b/MonoGame.Framework/Platform/Windows/WinFormsGamePlatform.cs @@ -11,16 +11,17 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using MonoGame.Framework; -namespace MonoGame.Framework +namespace Microsoft.Xna.Platform { - class WinFormsGamePlatform : GamePlatform + sealed class ConcreteGame : GameStrategy { //internal static string LaunchParameters; private WinFormsGameWindow _window; - public WinFormsGamePlatform(Game game) + public ConcreteGame(Game game) : base(game) { _window = new WinFormsGameWindow(this); @@ -45,9 +46,22 @@ internal override void Run() Game.DoExiting(); } - protected override void OnIsMouseVisibleChanged() + public override void Tick() { - _window.MouseVisibleToggled(); + base.Tick(); + } + + public override bool IsMouseVisible + { + get { return base.IsMouseVisible; } + set + { + if (base.IsMouseVisible != value) + { + base.IsMouseVisible = value; + _window.MouseVisibleToggled(); + } + } } public override void BeforeInitialize() @@ -66,10 +80,11 @@ public override void BeforeInitialize() } } - public override void Exit() + public override void TickExiting() { if (_window != null) _window.Dispose(); + _window = null; Window = null; } @@ -110,10 +125,10 @@ public override void Log(string message) Debug.WriteLine(message); } - public override void Present() + public override void EndDraw() { var device = Game.GraphicsDevice; - if ( device != null ) + if (device != null) device.Present(); } diff --git a/MonoGame.Framework/Platform/Windows/WinFormsGameWindow.cs b/MonoGame.Framework/Platform/Windows/WinFormsGameWindow.cs index 50d2118f6f5..23eb9c41f43 100644 --- a/MonoGame.Framework/Platform/Windows/WinFormsGameWindow.cs +++ b/MonoGame.Framework/Platform/Windows/WinFormsGameWindow.cs @@ -16,6 +16,7 @@ using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; using Microsoft.Xna.Framework.Windows; +using Microsoft.Xna.Platform; using ButtonState = Microsoft.Xna.Framework.Input.ButtonState; using Keys = Microsoft.Xna.Framework.Input.Keys; using Point = System.Drawing.Point; @@ -28,7 +29,7 @@ class WinFormsGameWindow : GameWindow, IDisposable { internal WinFormsGameForm Form; - private WinFormsGamePlatform _platform; + private ConcreteGame _platform; private bool _isResizable; private bool _isBorderless; @@ -133,7 +134,7 @@ public override bool IsBorderless #endregion - internal WinFormsGameWindow(WinFormsGamePlatform platform) + internal WinFormsGameWindow(ConcreteGame platform) { _platform = platform; Game = platform.Game; diff --git a/MonoGame.Framework/Platform/WindowsUniversal/UAPFrameworkView.cs b/MonoGame.Framework/Platform/WindowsUniversal/UAPFrameworkView.cs index 5d04926b350..e5bfda8e2b8 100644 --- a/MonoGame.Framework/Platform/WindowsUniversal/UAPFrameworkView.cs +++ b/MonoGame.Framework/Platform/WindowsUniversal/UAPFrameworkView.cs @@ -4,12 +4,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Windows.ApplicationModel.Core; using Windows.UI.Core; using Windows.ApplicationModel.Activation; +using Microsoft.Xna.Platform; namespace Microsoft.Xna.Framework { @@ -37,8 +35,8 @@ private void ViewActivated(CoreApplicationView sender, IActivatedEventArgs args) if (args.Kind == ActivationKind.Launch) { // Save any launch parameters to be parsed by the platform. - UAPGamePlatform.LaunchParameters = ((LaunchActivatedEventArgs)args).Arguments; - UAPGamePlatform.PreviousExecutionState = ((LaunchActivatedEventArgs)args).PreviousExecutionState; + ConcreteGame.LaunchParameters = ((LaunchActivatedEventArgs)args).Arguments; + ConcreteGame.PreviousExecutionState = ((LaunchActivatedEventArgs)args).PreviousExecutionState; // Construct the game. _game = new T(); @@ -52,8 +50,8 @@ private void ViewActivated(CoreApplicationView sender, IActivatedEventArgs args) { // Save any protocol launch parameters to be parsed by the platform. var protocolArgs = args as ProtocolActivatedEventArgs; - UAPGamePlatform.LaunchParameters = protocolArgs.Uri.AbsoluteUri; - UAPGamePlatform.PreviousExecutionState = protocolArgs.PreviousExecutionState; + ConcreteGame.LaunchParameters = protocolArgs.Uri.AbsoluteUri; + ConcreteGame.PreviousExecutionState = protocolArgs.PreviousExecutionState; // Construct the game if it does not exist // Protocol can be used to reactivate a suspended game @@ -81,7 +79,7 @@ public void Run() public void SetWindow(CoreWindow window) { // Initialize the singleton window. - UAPGameWindow.Instance.Initialize(window, null, UAPGamePlatform.TouchQueue); + UAPGameWindow.Instance.Initialize(window, null, ConcreteGame.TouchQueue); } public void Uninitialize() diff --git a/MonoGame.Framework/Platform/WindowsUniversal/UAPGamePlatform.cs b/MonoGame.Framework/Platform/WindowsUniversal/UAPGamePlatform.cs index 480abd2ab20..81c6e96ae18 100644 --- a/MonoGame.Framework/Platform/WindowsUniversal/UAPGamePlatform.cs +++ b/MonoGame.Framework/Platform/WindowsUniversal/UAPGamePlatform.cs @@ -2,6 +2,8 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE.txt', which is part of this source code package. +// Copyright (C)2023 Nick Kastellanos + using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,14 +15,16 @@ using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; +using Microsoft.Xna.Framework; //using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; -namespace Microsoft.Xna.Framework + +namespace Microsoft.Xna.Platform { - class UAPGamePlatform : GamePlatform + sealed class ConcreteGame : GameStrategy { internal static string LaunchParameters; @@ -28,7 +32,7 @@ class UAPGamePlatform : GamePlatform internal static ApplicationExecutionState PreviousExecutionState { get; set; } - public UAPGamePlatform(Game game) + public ConcreteGame(Game game) : base(game) { // Setup the game window. @@ -139,6 +143,11 @@ internal override void Run() Game.DoExiting(); } + public override void Tick() + { + base.Tick(); + } + //TODO: merge Run_UAP_XAML() with Run() internal override void Run_UAP_XAML() { @@ -212,7 +221,7 @@ private void OnRenderFrame(bool isQueueEmpty) dispatcher.RunIdleAsync(OnRenderFrame); } - public override void Exit() + public override void TickExiting() { if (!UAPGameWindow.Instance.IsExiting) { @@ -277,16 +286,25 @@ public override void Log(string Message) Debug.WriteLine(Message); } - public override void Present() + public override void EndDraw() { var device = Game.GraphicsDevice; - if ( device != null ) + if (device != null) device.Present(); } - protected override void OnIsMouseVisibleChanged() + public override bool IsMouseVisible { - UAPGameWindow.Instance.SetCursor(Game.IsMouseVisible); + get { return base.IsMouseVisible; } + set + { + if (base.IsMouseVisible != value) + { + base.IsMouseVisible = value; + UAPGameWindow.Instance.SetCursor(Game.IsMouseVisible); + } + else base.IsMouseVisible = value; + } } protected override void Dispose(bool disposing) diff --git a/MonoGame.Framework/Platform/WindowsUniversal/UAPGameWindow.cs b/MonoGame.Framework/Platform/WindowsUniversal/UAPGameWindow.cs index acdc0ffa57b..d86a871e273 100644 --- a/MonoGame.Framework/Platform/WindowsUniversal/UAPGameWindow.cs +++ b/MonoGame.Framework/Platform/WindowsUniversal/UAPGameWindow.cs @@ -18,6 +18,7 @@ using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; using Windows.UI.Xaml.Controls; +using Microsoft.Xna.Platform; namespace Microsoft.Xna.Framework { @@ -75,7 +76,7 @@ public override DisplayOrientation CurrentOrientation get { return _orientation; } } - private UAPGamePlatform Platform { get { return Game.Instance.Platform as UAPGamePlatform; } } + private ConcreteGame Strategy { get { return Game.Instance.Strategy as ConcreteGame; } } protected internal override void SetSupportedOrientations(DisplayOrientation orientations) { @@ -148,7 +149,7 @@ internal void RegisterCoreWindowService() void Window_VisibilityChanged(CoreWindow sender, VisibilityChangedEventArgs args) { - Platform.IsVisible = args.Visible; + Strategy.IsVisible = args.Visible; } private void Window_FocusChanged(CoreWindow sender, WindowActivatedEventArgs args) @@ -167,16 +168,16 @@ private void UpdateFocus() _isFocusChanged = false; if (_newActivationState == CoreWindowActivationState.Deactivated) - Platform.IsActive = false; + Strategy.IsActive = false; else - Platform.IsActive = true; + Strategy.IsActive = true; } } private void Window_Closed(CoreWindow sender, CoreWindowEventArgs args) { Game.SuppressDraw(); - Game.Platform.Exit(); + Game.Strategy.TickExiting(); } private void SetViewBounds(double width, double height) diff --git a/MonoGame.Framework/Platform/WindowsUniversal/XamlGame.cs b/MonoGame.Framework/Platform/WindowsUniversal/XamlGame.cs index 1f0f55bdb46..bbc180c9eb7 100644 --- a/MonoGame.Framework/Platform/WindowsUniversal/XamlGame.cs +++ b/MonoGame.Framework/Platform/WindowsUniversal/XamlGame.cs @@ -3,9 +3,10 @@ // file 'LICENSE.txt', which is part of this source code package. using System; -using Microsoft.Xna.Framework; using Windows.UI.Core; using Windows.UI.Xaml.Controls; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Platform; namespace MonoGame.Framework @@ -50,10 +51,10 @@ static public T Create(Func gameConstructor, string launchParameters, CoreWin throw new NullReferenceException("The swap chain panel cannot be null!"); // Save any launch parameters to be parsed by the platform. - UAPGamePlatform.LaunchParameters = launchParameters; + ConcreteGame.LaunchParameters = launchParameters; // Setup the window class. - UAPGameWindow.Instance.Initialize(window, swapChainPanel, UAPGamePlatform.TouchQueue); + UAPGameWindow.Instance.Initialize(window, swapChainPanel, ConcreteGame.TouchQueue); // Construct the game. var game = gameConstructor(); diff --git a/MonoGame.Framework/Platform/iOS/iOSGamePlatform.cs b/MonoGame.Framework/Platform/iOS/iOSGamePlatform.cs index 581e46b7a04..dd2ad239105 100644 --- a/MonoGame.Framework/Platform/iOS/iOSGamePlatform.cs +++ b/MonoGame.Framework/Platform/iOS/iOSGamePlatform.cs @@ -66,6 +66,8 @@ additional consumer rights under your local laws which this license cannot */ #endregion License +// Copyright (C)2023 Nick Kastellanos + using System; using System.Collections.Generic; using System.IO; @@ -76,25 +78,27 @@ additional consumer rights under your local laws which this license cannot using CoreAnimation; using ObjCRuntime; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; //using Microsoft.Xna.Framework.GamerServices; -namespace Microsoft.Xna.Framework + +namespace Microsoft.Xna.Platform { - class iOSGamePlatform : GamePlatform + sealed class ConcreteGame : GameStrategy { private iOSGameViewController _viewController; private UIWindow _mainWindow; private List _applicationObservers; private CADisplayLink _displayLink; - public iOSGamePlatform(Game game) : + public ConcreteGame(Game game) : base(game) { - game.Services.AddService(typeof(iOSGamePlatform), this); + game.Services.AddService(typeof(ConcreteGame), this); //This also runs the TitleContainer static constructor, ensuring it is done on the main thread Directory.SetCurrentDirectory(TitleContainer.Location); @@ -123,9 +127,17 @@ public iOSGamePlatform(Game game) : //Guide.Initialise(game); } - public override void TargetElapsedTimeChanged () + public override TimeSpan TargetElapsedTime { - CreateDisplayLink(); + get { return base.TargetElapsedTime; } + set + { + if (base.TargetElapsedTime != value) + { + base.TargetElapsedTime = value; + CreateDisplayLink(); + } + } } private void CreateDisplayLink() @@ -158,8 +170,13 @@ internal override void Run() //Game.DoExiting(); } + public override void Tick() + { + base.Tick(); + } + [Obsolete( - "iOSGamePlatform.IsPlayingVideo must be removed when MonoGame " + + "ConcreteGame.IsPlayingVideo must be removed when MonoGame " + "fully implements the XNA VideoPlayer contract.")] public bool IsPlayingVideo { get; set; } @@ -214,7 +231,7 @@ private void StartRunLoop() CreateDisplayLink(); } - internal void Tick() + internal void iOSTick() { if (!Game.IsActive) return; @@ -270,6 +287,11 @@ public override void ExitFullScreen() } public override void Exit() + { + throw new InvalidOperationException("This platform's policy does not allow programmatically closing."); + } + + public override void TickExiting() { // Do Nothing: iOS games do not "exit" or shut down. throw new NotImplementedException(); @@ -340,8 +362,7 @@ private void ViewController_InterfaceOrientationChanged (object sender, EventArg var orientation = CurrentOrientation; // FIXME: The presentation parameters for the GraphicsDevice should - // be managed by the GraphicsDevice itself. Not by - // iOSGamePlatform. + // be managed by the GraphicsDevice itself. Not by ConcreteGame. var gdm = (GraphicsDeviceManager) Game.Services.GetService (typeof (IGraphicsDeviceManager)); TouchPanel.DisplayOrientation = orientation; diff --git a/MonoGame.Framework/Platform/iOS/iOSGameView.cs b/MonoGame.Framework/Platform/iOS/iOSGameView.cs index 66ed0c1eab2..f6b2d62c9d1 100644 --- a/MonoGame.Framework/Platform/iOS/iOSGameView.cs +++ b/MonoGame.Framework/Platform/iOS/iOSGameView.cs @@ -82,18 +82,20 @@ additional consumer rights under your local laws which this license cannot using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input.Touch; +using Microsoft.Xna.Platform; + namespace Microsoft.Xna.Framework { [Register("iOSGameView")] partial class iOSGameView : UIView { - private readonly iOSGamePlatform _platform; + private readonly ConcreteGame _platform; private int _colorbuffer; private int _depthbuffer; private int _framebuffer; #region Construction/Destruction - public iOSGameView (iOSGamePlatform platform, CGRect frame) + public iOSGameView (ConcreteGame platform, CGRect frame) : base(frame) { if (platform == null) @@ -200,7 +202,7 @@ private void DestroyContext () [Export("doTick")] void DoTick() { - _platform.Tick(); + _platform.iOSTick(); } private void CreateFramebuffer () @@ -325,7 +327,7 @@ private void DestroyFramebuffer () // FIXME: This logic belongs in GraphicsDevice.Present, not // here. If it can someday be moved there, then the // normal call to Present in Game.Tick should cover - // this. For now, iOSGamePlatform will call Present + // this. For now, ConcreteGame will call Present // in the Draw/Update loop handler. public void Present () { diff --git a/MonoGame.Framework/Platform/iOS/iOSGameViewController.cs b/MonoGame.Framework/Platform/iOS/iOSGameViewController.cs index d59bd3d113b..0530d3bd452 100644 --- a/MonoGame.Framework/Platform/iOS/iOSGameViewController.cs +++ b/MonoGame.Framework/Platform/iOS/iOSGameViewController.cs @@ -9,6 +9,8 @@ using Foundation; using CoreGraphics; using ObjCRuntime; +using Microsoft.Xna.Platform; + namespace Microsoft.Xna.Framework { @@ -19,12 +21,12 @@ class iOSGameViewController : UIViewController #endif { - iOSGamePlatform _platform; + ConcreteGame _platform; #if TVOS IPlatformBackButton platformBackButton; #endif - public iOSGameViewController(iOSGamePlatform platform) + public iOSGameViewController(ConcreteGame platform) { if (platform == null) throw new ArgumentNullException("platform"); @@ -131,7 +133,7 @@ public override void ViewWillTransitionToSize(CGSize toSize, IUIViewControllerTr UIInterfaceOrientation prevOrientation = InterfaceOrientation; // In iOS 8+ DidRotate is no longer called after a rotation - // But we need to notify iOSGamePlatform to update back buffer so we explicitly call it + // But we need to notify ConcreteGame to update back buffer so we explicitly call it // We do this within the animateAlongside action, which at the point of calling // will have the new InterfaceOrientation set diff --git a/MonoGame.Framework/XNA.Framework.Android.csproj b/MonoGame.Framework/XNA.Framework.Android.csproj index 1f44b13d9b4..6ce5a4440a8 100644 --- a/MonoGame.Framework/XNA.Framework.Android.csproj +++ b/MonoGame.Framework/XNA.Framework.Android.csproj @@ -153,7 +153,6 @@ - diff --git a/MonoGame.Framework/XNA.Framework.Blazor.csproj b/MonoGame.Framework/XNA.Framework.Blazor.csproj index 4f1c86cf3a2..dfc1348e119 100644 --- a/MonoGame.Framework/XNA.Framework.Blazor.csproj +++ b/MonoGame.Framework/XNA.Framework.Blazor.csproj @@ -50,7 +50,6 @@ - diff --git a/MonoGame.Framework/XNA.Framework.DesktopGL.csproj b/MonoGame.Framework/XNA.Framework.DesktopGL.csproj index f1985d7da85..c2efc8d0719 100644 --- a/MonoGame.Framework/XNA.Framework.DesktopGL.csproj +++ b/MonoGame.Framework/XNA.Framework.DesktopGL.csproj @@ -150,7 +150,6 @@ - diff --git a/MonoGame.Framework/XNA.Framework.UAP.csproj b/MonoGame.Framework/XNA.Framework.UAP.csproj index 8a2f02b7ea0..00292df630d 100644 --- a/MonoGame.Framework/XNA.Framework.UAP.csproj +++ b/MonoGame.Framework/XNA.Framework.UAP.csproj @@ -160,7 +160,6 @@ - diff --git a/MonoGame.Framework/XNA.Framework.WindowsDX11.csproj b/MonoGame.Framework/XNA.Framework.WindowsDX11.csproj index 9f6ac9f1df6..fc20137a2b4 100644 --- a/MonoGame.Framework/XNA.Framework.WindowsDX11.csproj +++ b/MonoGame.Framework/XNA.Framework.WindowsDX11.csproj @@ -168,7 +168,6 @@ - diff --git a/MonoGame.Framework/XNA.Framework.iOS.csproj b/MonoGame.Framework/XNA.Framework.iOS.csproj index 13c1ad75aae..b58b867cfec 100644 --- a/MonoGame.Framework/XNA.Framework.iOS.csproj +++ b/MonoGame.Framework/XNA.Framework.iOS.csproj @@ -197,7 +197,6 @@ - diff --git a/Tests/Framework/TestGameBase.cs b/Tests/Framework/TestGameBase.cs index 68b4196da39..7fd090cb39a 100644 --- a/Tests/Framework/TestGameBase.cs +++ b/Tests/Framework/TestGameBase.cs @@ -238,7 +238,7 @@ protected void DoExit() // complete running all the unit tests. So we do the // next best thing can call the interal platform code // directly which produces the same result. - Platform.Exit(); + Strategy.Exit(); SuppressDraw(); #endif }