Show More
Commit Description:
Add timers for Simulation and various engines...
Commit Description:
Add timers for Simulation and various engines
Starting to add additional timers for different stages of the process of
updating in order to get more insight into what is slowing it down.
The update takes 9ms, which is much longer than it used to.
Engine-specific timers are coming later.
References:
File last commit:
Show/Diff file:
Action:
FNA/src/Game.cs
889 lines | 26.0 KiB | text/x-csharp | CSharpLexer
889 lines | 26.0 KiB | text/x-csharp | CSharpLexer
r0 | #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 | ||||
} | ||||
} | ||||