|
|
#region License
|
|
|
/* FNA - XNA4 Reimplementation for Desktop Platforms
|
|
|
* Copyright 2009-2020 Ethan Lee and the MonoGame Team
|
|
|
*
|
|
|
* Released under the Microsoft Public License.
|
|
|
* See LICENSE for details.
|
|
|
*/
|
|
|
#endregion
|
|
|
|
|
|
#region Using Statements
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using Microsoft.Xna.Framework.Audio;
|
|
|
using Microsoft.Xna.Framework.Input;
|
|
|
using Microsoft.Xna.Framework.Content;
|
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
using Microsoft.Xna.Framework.Input.Touch;
|
|
|
#endregion
|
|
|
|
|
|
namespace Microsoft.Xna.Framework
|
|
|
{
|
|
|
public class Game : IDisposable
|
|
|
{
|
|
|
#region Public Properties
|
|
|
|
|
|
public GameComponentCollection Components
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
private ContentManager INTERNAL_content;
|
|
|
public ContentManager Content
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return INTERNAL_content;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
if (value == null)
|
|
|
{
|
|
|
throw new ArgumentNullException();
|
|
|
}
|
|
|
INTERNAL_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;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private TimeSpan INTERNAL_inactiveSleepTime;
|
|
|
public TimeSpan InactiveSleepTime
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return INTERNAL_inactiveSleepTime;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
if (value < TimeSpan.Zero)
|
|
|
{
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
"The time must be positive.",
|
|
|
default(Exception)
|
|
|
);
|
|
|
}
|
|
|
if (INTERNAL_inactiveSleepTime != value)
|
|
|
{
|
|
|
INTERNAL_inactiveSleepTime = value;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private bool INTERNAL_isActive;
|
|
|
public bool IsActive
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return INTERNAL_isActive;
|
|
|
}
|
|
|
internal set
|
|
|
{
|
|
|
if (INTERNAL_isActive != value)
|
|
|
{
|
|
|
INTERNAL_isActive = value;
|
|
|
if (INTERNAL_isActive)
|
|
|
{
|
|
|
OnActivated(this, EventArgs.Empty);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
OnDeactivated(this, EventArgs.Empty);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public bool IsFixedTimeStep
|
|
|
{
|
|
|
get;
|
|
|
set;
|
|
|
}
|
|
|
|
|
|
private bool INTERNAL_isMouseVisible;
|
|
|
public bool IsMouseVisible
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return INTERNAL_isMouseVisible;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
if (INTERNAL_isMouseVisible != value)
|
|
|
{
|
|
|
INTERNAL_isMouseVisible = value;
|
|
|
FNAPlatform.OnIsMouseVisibleChanged(value);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public LaunchParameters LaunchParameters
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
private TimeSpan INTERNAL_targetElapsedTime;
|
|
|
public TimeSpan TargetElapsedTime
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return INTERNAL_targetElapsedTime;
|
|
|
}
|
|
|
set
|
|
|
{
|
|
|
if (value <= TimeSpan.Zero)
|
|
|
{
|
|
|
throw new ArgumentOutOfRangeException(
|
|
|
"The time must be positive and non-zero.",
|
|
|
default(Exception)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
INTERNAL_targetElapsedTime = value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public GameServiceContainer Services
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public GameWindow Window
|
|
|
{
|
|
|
get;
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Internal Variables
|
|
|
|
|
|
internal bool RunApplication;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Variables
|
|
|
|
|
|
/* You will notice that these lists have some locks on them in the code.
|
|
|
* Technically this is not accurate to XNA4, as they just happily crash
|
|
|
* whenever there's an Add/Remove happening mid-copy.
|
|
|
*
|
|
|
* But do you really think I want to get reports about that crap?
|
|
|
* -flibit
|
|
|
*/
|
|
|
private List<IUpdateable> updateableComponents;
|
|
|
private List<IUpdateable> currentlyUpdatingComponents;
|
|
|
private List<IDrawable> drawableComponents;
|
|
|
private List<IDrawable> currentlyDrawingComponents;
|
|
|
|
|
|
private IGraphicsDeviceService graphicsDeviceService;
|
|
|
private IGraphicsDeviceManager graphicsDeviceManager;
|
|
|
private bool hasInitialized;
|
|
|
private bool suppressDraw;
|
|
|
private bool isDisposed;
|
|
|
|
|
|
private readonly GameTime gameTime;
|
|
|
private Stopwatch gameTimer;
|
|
|
private TimeSpan accumulatedElapsedTime;
|
|
|
private long previousTicks = 0;
|
|
|
private int updateFrameLag;
|
|
|
private bool forceElapsedTimeToZero = false;
|
|
|
|
|
|
private static readonly TimeSpan MaxElapsedTime = TimeSpan.FromMilliseconds(500);
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Events
|
|
|
|
|
|
public event EventHandler<EventArgs> Activated;
|
|
|
public event EventHandler<EventArgs> Deactivated;
|
|
|
public event EventHandler<EventArgs> Disposed;
|
|
|
public event EventHandler<EventArgs> Exiting;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Public Constructor
|
|
|
|
|
|
public Game()
|
|
|
{
|
|
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
|
|
|
|
|
LaunchParameters = new LaunchParameters();
|
|
|
Components = new GameComponentCollection();
|
|
|
Services = new GameServiceContainer();
|
|
|
Content = new ContentManager(Services);
|
|
|
|
|
|
updateableComponents = new List<IUpdateable>();
|
|
|
currentlyUpdatingComponents = new List<IUpdateable>();
|
|
|
drawableComponents = new List<IDrawable>();
|
|
|
currentlyDrawingComponents = new List<IDrawable>();
|
|
|
|
|
|
IsMouseVisible = false;
|
|
|
IsFixedTimeStep = true;
|
|
|
TargetElapsedTime = TimeSpan.FromTicks(166667); // 60fps
|
|
|
InactiveSleepTime = TimeSpan.FromSeconds(0.02);
|
|
|
|
|
|
hasInitialized = false;
|
|
|
suppressDraw = false;
|
|
|
isDisposed = false;
|
|
|
|
|
|
gameTime = new GameTime();
|
|
|
|
|
|
Window = FNAPlatform.CreateWindow();
|
|
|
Mouse.WindowHandle = Window.Handle;
|
|
|
TouchPanel.WindowHandle = Window.Handle;
|
|
|
|
|
|
FrameworkDispatcher.Update();
|
|
|
|
|
|
// Ready to run the loop!
|
|
|
RunApplication = true;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Deconstructor
|
|
|
|
|
|
~Game()
|
|
|
{
|
|
|
Dispose(false);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region IDisposable Implementation
|
|
|
|
|
|
public void Dispose()
|
|
|
{
|
|
|
Dispose(true);
|
|
|
GC.SuppressFinalize(this);
|
|
|
if (Disposed != null)
|
|
|
{
|
|
|
Disposed(this, EventArgs.Empty);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
{
|
|
|
if (!isDisposed)
|
|
|
{
|
|
|
if (disposing)
|
|
|
{
|
|
|
// Dispose loaded game components.
|
|
|
for (int i = 0; i < Components.Count; i += 1)
|
|
|
{
|
|
|
IDisposable disposable = Components[i] as IDisposable;
|
|
|
if (disposable != null)
|
|
|
{
|
|
|
disposable.Dispose();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (Content != null)
|
|
|
{
|
|
|
Content.Dispose();
|
|
|
}
|
|
|
|
|
|
if (graphicsDeviceService != null)
|
|
|
{
|
|
|
// FIXME: Does XNA4 require the GDM to be disposable? -flibit
|
|
|
(graphicsDeviceService as IDisposable).Dispose();
|
|
|
}
|
|
|
|
|
|
if (Window != null)
|
|
|
{
|
|
|
FNAPlatform.DisposeWindow(Window);
|
|
|
}
|
|
|
|
|
|
ContentTypeReaderManager.ClearTypeCreators();
|
|
|
}
|
|
|
|
|
|
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
|
|
|
|
|
|
isDisposed = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
[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
|
|
|
|
|
|
#region Public Methods
|
|
|
|
|
|
public void Exit()
|
|
|
{
|
|
|
RunApplication = false;
|
|
|
suppressDraw = true;
|
|
|
}
|
|
|
|
|
|
public void ResetElapsedTime()
|
|
|
{
|
|
|
/* This only matters the next tick, and ONLY when
|
|
|
* IsFixedTimeStep is false!
|
|
|
* For fixed timestep, this is totally ignored.
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (!IsFixedTimeStep)
|
|
|
{
|
|
|
forceElapsedTimeToZero = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void SuppressDraw()
|
|
|
{
|
|
|
suppressDraw = true;
|
|
|
}
|
|
|
|
|
|
public void RunOneFrame()
|
|
|
{
|
|
|
if (!hasInitialized)
|
|
|
{
|
|
|
DoInitialize();
|
|
|
gameTimer = Stopwatch.StartNew();
|
|
|
hasInitialized = true;
|
|
|
}
|
|
|
|
|
|
BeginRun();
|
|
|
|
|
|
// FIXME: Not quite right..
|
|
|
Tick();
|
|
|
|
|
|
EndRun();
|
|
|
}
|
|
|
|
|
|
public void Run()
|
|
|
{
|
|
|
AssertNotDisposed();
|
|
|
|
|
|
if (!hasInitialized)
|
|
|
{
|
|
|
DoInitialize();
|
|
|
hasInitialized = true;
|
|
|
}
|
|
|
|
|
|
BeginRun();
|
|
|
gameTimer = Stopwatch.StartNew();
|
|
|
|
|
|
FNAPlatform.RunLoop(this);
|
|
|
|
|
|
EndRun();
|
|
|
|
|
|
OnExiting(this, EventArgs.Empty);
|
|
|
}
|
|
|
|
|
|
public void Tick()
|
|
|
{
|
|
|
/* NOTE: This code is very sensitive and can break very badly,
|
|
|
* even with 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.
|
|
|
long 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)
|
|
|
{
|
|
|
int 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.
|
|
|
*/
|
|
|
System.Threading.Thread.Sleep(sleepTime);
|
|
|
|
|
|
goto RetryTick;
|
|
|
}
|
|
|
|
|
|
// Do not allow any update to take longer than our maximum.
|
|
|
if (accumulatedElapsedTime > MaxElapsedTime)
|
|
|
{
|
|
|
accumulatedElapsedTime = MaxElapsedTime;
|
|
|
}
|
|
|
|
|
|
if (IsFixedTimeStep)
|
|
|
{
|
|
|
gameTime.ElapsedGameTime = TargetElapsedTime;
|
|
|
int stepCount = 0;
|
|
|
|
|
|
// Perform as many full fixed length time steps as we can.
|
|
|
while (accumulatedElapsedTime >= TargetElapsedTime)
|
|
|
{
|
|
|
gameTime.TotalGameTime += TargetElapsedTime;
|
|
|
accumulatedElapsedTime -= TargetElapsedTime;
|
|
|
stepCount += 1;
|
|
|
|
|
|
AssertNotDisposed();
|
|
|
Update(gameTime);
|
|
|
}
|
|
|
|
|
|
// Every update after the first accumulates lag
|
|
|
updateFrameLag += Math.Max(0, stepCount - 1);
|
|
|
|
|
|
/* If we think we are running slowly, wait
|
|
|
* until the lag clears before resetting it
|
|
|
*/
|
|
|
if (gameTime.IsRunningSlowly)
|
|
|
{
|
|
|
if (updateFrameLag == 0)
|
|
|
{
|
|
|
gameTime.IsRunningSlowly = false;
|
|
|
}
|
|
|
}
|
|
|
else if (updateFrameLag >= 5)
|
|
|
{
|
|
|
/* If we lag more than 5 frames,
|
|
|
* start thinking we are running slowly.
|
|
|
*/
|
|
|
gameTime.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 -= 1;
|
|
|
}
|
|
|
|
|
|
/* Draw needs to know the total elapsed time
|
|
|
* that occured for the fixed length updates.
|
|
|
*/
|
|
|
gameTime.ElapsedGameTime = TimeSpan.FromTicks(TargetElapsedTime.Ticks * stepCount);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Perform a single variable length update.
|
|
|
if (forceElapsedTimeToZero)
|
|
|
{
|
|
|
/* When ResetElapsedTime is called,
|
|
|
* Elapsed is forced to zero and
|
|
|
* Total is ignored entirely.
|
|
|
* -flibit
|
|
|
*/
|
|
|
gameTime.ElapsedGameTime = TimeSpan.Zero;
|
|
|
forceElapsedTimeToZero = false;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
gameTime.ElapsedGameTime = accumulatedElapsedTime;
|
|
|
gameTime.TotalGameTime += gameTime.ElapsedGameTime;
|
|
|
}
|
|
|
|
|
|
accumulatedElapsedTime = TimeSpan.Zero;
|
|
|
AssertNotDisposed();
|
|
|
Update(gameTime);
|
|
|
}
|
|
|
|
|
|
// Draw unless the update suppressed it.
|
|
|
if (suppressDraw)
|
|
|
{
|
|
|
suppressDraw = false;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* Draw/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 (BeginDraw())
|
|
|
{
|
|
|
Draw(gameTime);
|
|
|
EndDraw();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Internal Methods
|
|
|
|
|
|
internal void RedrawWindow()
|
|
|
{
|
|
|
/* Draw/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
|
|
|
*
|
|
|
* Additionally, if we haven't even started yet, be quiet until we have!
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (gameTime.TotalGameTime != TimeSpan.Zero && BeginDraw())
|
|
|
{
|
|
|
Draw(new GameTime(gameTime.TotalGameTime, TimeSpan.Zero));
|
|
|
EndDraw();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Protected Methods
|
|
|
|
|
|
protected virtual bool BeginDraw()
|
|
|
{
|
|
|
if (graphicsDeviceManager != null)
|
|
|
{
|
|
|
return graphicsDeviceManager.BeginDraw();
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
protected virtual void EndDraw()
|
|
|
{
|
|
|
if (graphicsDeviceManager != null)
|
|
|
{
|
|
|
graphicsDeviceManager.EndDraw();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected virtual void BeginRun()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
protected virtual void EndRun()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
protected virtual void LoadContent()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
protected virtual void UnloadContent()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
protected virtual void Initialize()
|
|
|
{
|
|
|
/* According to the information given on MSDN, all GameComponents
|
|
|
* in Components at the time Initialize() is called are initialized:
|
|
|
*
|
|
|
* http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.game.initialize.aspx
|
|
|
*
|
|
|
* Note, however, that we are NOT using a foreach. It's actually
|
|
|
* possible to add something during initialization, and those must
|
|
|
* also be initialized. There may be a safer way to account for it,
|
|
|
* considering it may be possible to _remove_ components as well,
|
|
|
* but for now, let's worry about initializing everything we get.
|
|
|
* -flibit
|
|
|
*/
|
|
|
for (int i = 0; i < Components.Count; i += 1)
|
|
|
{
|
|
|
Components[i].Initialize();
|
|
|
}
|
|
|
|
|
|
/* This seems like a condition that warrants a major
|
|
|
* exception more than a silent failure, but for some
|
|
|
* reason it's okay... but only sort of. You can get
|
|
|
* away with initializing just before base.Initialize(),
|
|
|
* but everything gets super broken on the IManager side
|
|
|
* (IService doesn't seem to matter anywhere else).
|
|
|
*/
|
|
|
graphicsDeviceService = (IGraphicsDeviceService)
|
|
|
Services.GetService(typeof(IGraphicsDeviceService));
|
|
|
if (graphicsDeviceService != null &&
|
|
|
graphicsDeviceService.GraphicsDevice != null)
|
|
|
{
|
|
|
graphicsDeviceService.DeviceDisposing += (o, e) => UnloadContent();
|
|
|
LoadContent();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected virtual void Draw(GameTime gameTime)
|
|
|
{
|
|
|
lock (drawableComponents)
|
|
|
{
|
|
|
for (int i = 0; i < drawableComponents.Count; i += 1)
|
|
|
{
|
|
|
currentlyDrawingComponents.Add(drawableComponents[i]);
|
|
|
}
|
|
|
}
|
|
|
foreach (IDrawable drawable in currentlyDrawingComponents)
|
|
|
{
|
|
|
if (drawable.Visible)
|
|
|
{
|
|
|
drawable.Draw(gameTime);
|
|
|
}
|
|
|
}
|
|
|
currentlyDrawingComponents.Clear();
|
|
|
}
|
|
|
|
|
|
protected virtual void Update(GameTime gameTime)
|
|
|
{
|
|
|
lock (updateableComponents)
|
|
|
{
|
|
|
for (int i = 0; i < updateableComponents.Count; i += 1)
|
|
|
{
|
|
|
currentlyUpdatingComponents.Add(updateableComponents[i]);
|
|
|
}
|
|
|
}
|
|
|
foreach (IUpdateable updateable in currentlyUpdatingComponents)
|
|
|
{
|
|
|
if (updateable.Enabled)
|
|
|
{
|
|
|
updateable.Update(gameTime);
|
|
|
}
|
|
|
}
|
|
|
currentlyUpdatingComponents.Clear();
|
|
|
|
|
|
FrameworkDispatcher.Update();
|
|
|
}
|
|
|
|
|
|
protected virtual void OnExiting(object sender, EventArgs args)
|
|
|
{
|
|
|
if (Exiting != null)
|
|
|
{
|
|
|
Exiting(this, args);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected virtual void OnActivated(object sender, EventArgs args)
|
|
|
{
|
|
|
AssertNotDisposed();
|
|
|
if (Activated != null)
|
|
|
{
|
|
|
Activated(this, args);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected virtual void OnDeactivated(object sender, EventArgs args)
|
|
|
{
|
|
|
AssertNotDisposed();
|
|
|
if (Deactivated != null)
|
|
|
{
|
|
|
Deactivated(this, args);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
protected virtual bool ShowMissingRequirementMessage(Exception exception)
|
|
|
{
|
|
|
if (exception is NoAudioHardwareException)
|
|
|
{
|
|
|
FNAPlatform.ShowRuntimeError(
|
|
|
Window.Title,
|
|
|
"Could not find a suitable audio device. " +
|
|
|
" Verify that a sound card is\ninstalled," +
|
|
|
" and check the driver properties to make" +
|
|
|
" sure it is not disabled."
|
|
|
);
|
|
|
return true;
|
|
|
}
|
|
|
if (exception is NoSuitableGraphicsDeviceException)
|
|
|
{
|
|
|
FNAPlatform.ShowRuntimeError(
|
|
|
Window.Title,
|
|
|
"Could not find a suitable graphics device." +
|
|
|
" More information:\n\n" + exception.Message
|
|
|
);
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Methods
|
|
|
|
|
|
private void DoInitialize()
|
|
|
{
|
|
|
AssertNotDisposed();
|
|
|
|
|
|
/* If this is late, you can still create it yourself.
|
|
|
* In fact, you can even go as far as creating the
|
|
|
* _manager_ before base.Initialize(), but Begin/EndDraw
|
|
|
* will not get called. Just... please, make the service
|
|
|
* before calling Run().
|
|
|
*/
|
|
|
graphicsDeviceManager = (IGraphicsDeviceManager)
|
|
|
Services.GetService(typeof(IGraphicsDeviceManager));
|
|
|
if (graphicsDeviceManager != null)
|
|
|
{
|
|
|
graphicsDeviceManager.CreateDevice();
|
|
|
}
|
|
|
|
|
|
Initialize();
|
|
|
|
|
|
/* We need to do this after virtual Initialize(...) is called.
|
|
|
* 1. Categorize components into IUpdateable and IDrawable lists.
|
|
|
* 2. Subscribe to Added/Removed events to keep the categorized
|
|
|
* lists synced and to Initialize future components as they are
|
|
|
* added.
|
|
|
*/
|
|
|
updateableComponents.Clear();
|
|
|
drawableComponents.Clear();
|
|
|
for (int i = 0; i < Components.Count; i += 1)
|
|
|
{
|
|
|
CategorizeComponent(Components[i]);
|
|
|
}
|
|
|
Components.ComponentAdded += OnComponentAdded;
|
|
|
Components.ComponentRemoved += OnComponentRemoved;
|
|
|
}
|
|
|
|
|
|
private void CategorizeComponent(IGameComponent component)
|
|
|
{
|
|
|
IUpdateable updateable = component as IUpdateable;
|
|
|
if (updateable != null)
|
|
|
{
|
|
|
lock (updateableComponents)
|
|
|
{
|
|
|
SortUpdateable(updateable);
|
|
|
}
|
|
|
updateable.UpdateOrderChanged += OnUpdateOrderChanged;
|
|
|
}
|
|
|
|
|
|
IDrawable drawable = component as IDrawable;
|
|
|
if (drawable != null)
|
|
|
{
|
|
|
lock (drawableComponents)
|
|
|
{
|
|
|
SortDrawable(drawable);
|
|
|
}
|
|
|
drawable.DrawOrderChanged += OnDrawOrderChanged;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void SortUpdateable(IUpdateable updateable)
|
|
|
{
|
|
|
for (int i = 0; i < updateableComponents.Count; i += 1)
|
|
|
{
|
|
|
if (updateable.UpdateOrder < updateableComponents[i].UpdateOrder)
|
|
|
{
|
|
|
updateableComponents.Insert(i, updateable);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
updateableComponents.Add(updateable);
|
|
|
}
|
|
|
|
|
|
private void SortDrawable(IDrawable drawable)
|
|
|
{
|
|
|
for (int i = 0; i < drawableComponents.Count; i += 1)
|
|
|
{
|
|
|
if (drawable.DrawOrder < drawableComponents[i].DrawOrder)
|
|
|
{
|
|
|
drawableComponents.Insert(i, drawable);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
drawableComponents.Add(drawable);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Event Handlers
|
|
|
|
|
|
private void OnComponentAdded(
|
|
|
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.
|
|
|
*/
|
|
|
e.GameComponent.Initialize();
|
|
|
CategorizeComponent(e.GameComponent);
|
|
|
}
|
|
|
|
|
|
private void OnComponentRemoved(
|
|
|
object sender,
|
|
|
GameComponentCollectionEventArgs e
|
|
|
)
|
|
|
{
|
|
|
IUpdateable updateable = e.GameComponent as IUpdateable;
|
|
|
if (updateable != null)
|
|
|
{
|
|
|
lock (updateableComponents)
|
|
|
{
|
|
|
updateableComponents.Remove(updateable);
|
|
|
}
|
|
|
updateable.UpdateOrderChanged -= OnUpdateOrderChanged;
|
|
|
}
|
|
|
|
|
|
IDrawable drawable = e.GameComponent as IDrawable;
|
|
|
if (drawable != null)
|
|
|
{
|
|
|
lock (drawableComponents)
|
|
|
{
|
|
|
drawableComponents.Remove(drawable);
|
|
|
}
|
|
|
drawable.DrawOrderChanged -= OnDrawOrderChanged;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void OnUpdateOrderChanged(object sender, EventArgs e)
|
|
|
{
|
|
|
// FIXME: Is there a better way to re-sort one item? -flibit
|
|
|
IUpdateable updateable = sender as IUpdateable;
|
|
|
lock (updateableComponents)
|
|
|
{
|
|
|
updateableComponents.Remove(updateable);
|
|
|
SortUpdateable(updateable);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void OnDrawOrderChanged(object sender, EventArgs e)
|
|
|
{
|
|
|
// FIXME: Is there a better way to re-sort one item? -flibit
|
|
|
IDrawable drawable = sender as IDrawable;
|
|
|
lock (drawableComponents)
|
|
|
{
|
|
|
drawableComponents.Remove(drawable);
|
|
|
SortDrawable(drawable);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private void OnUnhandledException(
|
|
|
object sender,
|
|
|
UnhandledExceptionEventArgs args
|
|
|
)
|
|
|
{
|
|
|
ShowMissingRequirementMessage(args.ExceptionObject as Exception);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
}
|
|
|
|