/*
* Copyright (c) 2007-2009 SlimDX Group
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
using SharpDX;
using SharpDX.Direct3D9;
using System.Collections.ObjectModel;
namespace SampleFramework
{
///
/// Presents an easy to use wrapper for making games and samples.
///
public abstract class Game : IDisposable
{
GameClock clock = new GameClock();
GameTime gameTime = new GameTime();
TimeSpan maximumElapsedTime = TimeSpan.FromMilliseconds(500.0);
TimeSpan totalGameTime;
TimeSpan accumulatedElapsedGameTime;
TimeSpan lastFrameElapsedGameTime;
TimeSpan lastFrameElapsedRealTime;
TimeSpan targetElapsedTime = TimeSpan.FromTicks(166667);
TimeSpan inactiveSleepTime = TimeSpan.FromMilliseconds(20.0);
int updatesSinceRunningSlowly1 = int.MaxValue;
int updatesSinceRunningSlowly2 = int.MaxValue;
bool forceElapsedTimeToZero;
bool drawRunningSlowly;
long lastUpdateFrame;
float lastUpdateTime;
///
/// Occurs when the game is disposed.
///
public event EventHandler Disposed;
///
/// Occurs when the game is activated.
///
public event EventHandler Activated;
///
/// Occurs when the game is deactivated.
///
public event EventHandler Deactivated;
///
/// Occurs when the game is exiting.
///
public event EventHandler Exiting;
///
/// Occurs when a drawing frame is about to start.
///
public event CancelEventHandler FrameStart;
///
/// Occurs when a drawing frame ends.
///
public event EventHandler FrameEnd;
///
/// Gets or sets the inactive sleep time.
///
/// The inactive sleep time.
public TimeSpan InactiveSleepTime
{
get { return inactiveSleepTime; }
set
{
// error checking
if (value < TimeSpan.Zero)
throw new ArgumentOutOfRangeException("value", "Inactive sleep time cannot be less than zero.");
inactiveSleepTime = value;
}
}
///
/// Gets or sets the target elapsed time.
///
/// The target elapsed time.
public TimeSpan TargetElapsedTime
{
get { return targetElapsedTime; }
set
{
// error checking
if (value <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException("value", "Target elapsed time must be greater than zero.");
targetElapsedTime = value;
}
}
///
/// Gets or sets a value indicating whether the game is using a fixed time step.
///
///
/// true if the game is using a fixed time step; otherwise, false.
///
public bool IsFixedTimeStep
{
get;
set;
}
///
/// Gets a value indicating whether this is exiting.
///
/// true if exiting; otherwise, false.
public bool IsExiting
{
get;
private set;
}
///
/// Gets or sets a value indicating whether this instance is running.
///
///
/// true if this instance is running; otherwise, false.
///
public bool IsRunning
{
get;
private set;
}
///
/// Gets the game window.
///
/// The game window.
public GameWindow Window
{
get;
private set;
}
///
/// Gets the graphics device manager.
///
/// The graphics device manager.
public GraphicsDeviceManager GraphicsDeviceManager
{
get;
private set;
}
///
/// Gets or sets a value indicating whether this is active.
///
/// true if active; otherwise, false.
public bool IsActive
{
get;
private set;
}
///
/// Initializes the class.
///
static Game()
{
// configure SlimDX
//Configuration.ThrowOnError = true;
//Configuration.AddResultWatch(ResultCode.DeviceLost, ResultWatchFlags.AlwaysIgnore);
//Configuration.AddResultWatch(ResultCode.WasStillDrawing, ResultWatchFlags.AlwaysIgnore);
#if DEBUG
//Configuration.DetectDoubleDispose = true;
Configuration.EnableObjectTracking = true;
#else
//Configuration.DetectDoubleDispose = false;
Configuration.EnableObjectTracking = false;
#endif
// setup the application
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
}
///
/// Initializes a new instance of the class.
///
protected Game()
{
IsFixedTimeStep = true;
Window = new GameWindow();
Window.ApplicationActivated += Window_ApplicationActivated;
Window.ApplicationDeactivated += Window_ApplicationDeactivated;
Window.Suspend += Window_Suspend;
Window.Resume += Window_Resume;
Window.Paint += Window_Paint;
GraphicsDeviceManager = new GraphicsDeviceManager(this);
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
// GraphicsDeviceManager.Dispose will come around and call the Dispose(bool)
// overload, so we don't need to do it here. It's convoluted, but it works well.
if (GraphicsDeviceManager != null)
GraphicsDeviceManager.Dispose();
GraphicsDeviceManager = null;
if (Disposed != null)
Disposed(this, EventArgs.Empty);
GC.SuppressFinalize(this);
}
///
/// Exits the game.
///
public void Exit()
{
// request the game to terminate
IsExiting = true;
}
///
/// Runs the game.
///
public void Run()
{
IsRunning = true;
try
{
gameTime.ElapsedGameTime = 0;
gameTime.ElapsedRealTime = 0;
gameTime.TotalGameTime = (float)totalGameTime.TotalSeconds;
gameTime.TotalRealTime = (float)clock.CurrentTime.TotalSeconds;
gameTime.IsRunningSlowly = false;
Update(gameTime);
Application.Idle += Application_Idle;
Application.Run(Window);
}
finally
{
Application.Idle -= Application_Idle;
IsRunning = false;
OnExiting(EventArgs.Empty);
}
}
///
/// Performs one complete frame for the game.
///
public void Tick()
{
// if we are exiting, do nothing
if (IsExiting)
return;
// if we are inactive, sleep for a bit
//if (!IsActive)
// Thread.Sleep((int)InactiveSleepTime.TotalMilliseconds);
clock.Step();
gameTime.TotalRealTime = (float)clock.CurrentTime.TotalSeconds;
gameTime.ElapsedRealTime = (float)clock.ElapsedTime.TotalSeconds;
lastFrameElapsedRealTime += clock.ElapsedTime;
TimeSpan elapsedAdjustedTime = clock.ElapsedAdjustedTime;
if (elapsedAdjustedTime < TimeSpan.Zero)
elapsedAdjustedTime = TimeSpan.Zero;
if (forceElapsedTimeToZero)
{
gameTime.ElapsedRealTime = 0;
lastFrameElapsedRealTime = elapsedAdjustedTime = TimeSpan.Zero;
forceElapsedTimeToZero = false;
}
// cap the adjusted time
if (elapsedAdjustedTime > maximumElapsedTime)
elapsedAdjustedTime = maximumElapsedTime;
// check if we are using a fixed or variable time step
if (IsFixedTimeStep)
{
accumulatedElapsedGameTime += elapsedAdjustedTime;
long ratio = accumulatedElapsedGameTime.Ticks / TargetElapsedTime.Ticks;
accumulatedElapsedGameTime = TimeSpan.FromTicks(accumulatedElapsedGameTime.Ticks % TargetElapsedTime.Ticks);
lastFrameElapsedGameTime = TimeSpan.Zero;
if (ratio == 0)
return;
TimeSpan targetElapsedTime = TargetElapsedTime;
if (ratio > 1)
{
updatesSinceRunningSlowly2 = updatesSinceRunningSlowly1;
updatesSinceRunningSlowly1 = 0;
}
else
{
if (updatesSinceRunningSlowly1 < int.MaxValue)
updatesSinceRunningSlowly1++;
if (updatesSinceRunningSlowly2 < int.MaxValue)
updatesSinceRunningSlowly2++;
}
drawRunningSlowly = updatesSinceRunningSlowly2 < 20;
// update until it's time to draw the next frame
while (ratio > 0 && !IsExiting)
{
ratio -= 1;
try
{
gameTime.ElapsedGameTime = (float)targetElapsedTime.TotalSeconds;
gameTime.TotalGameTime = (float)totalGameTime.TotalSeconds;
gameTime.IsRunningSlowly = drawRunningSlowly;
Update(gameTime);
}
finally
{
lastFrameElapsedGameTime += targetElapsedTime;
totalGameTime += targetElapsedTime;
}
}
}
else
{
drawRunningSlowly = false;
updatesSinceRunningSlowly1 = int.MaxValue;
updatesSinceRunningSlowly2 = int.MaxValue;
// make sure we shouldn't be exiting
if (!IsExiting)
{
try
{
gameTime.ElapsedGameTime = 0;
lastFrameElapsedGameTime = elapsedAdjustedTime;
gameTime.TotalGameTime = (float)totalGameTime.TotalSeconds;
gameTime.IsRunningSlowly = false;
Update(gameTime);
}
finally
{
totalGameTime += elapsedAdjustedTime;
}
}
}
DrawFrame();
// refresh the FPS counter once per second
lastUpdateFrame++;
if ((float)clock.CurrentTime.TotalSeconds - lastUpdateTime > 1.0f)
{
gameTime.FramesPerSecond = (float)lastUpdateFrame / (float)(clock.CurrentTime.TotalSeconds - lastUpdateTime);
lastUpdateTime = (float)clock.CurrentTime.TotalSeconds;
lastUpdateFrame = 0;
}
}
///
/// Resets the elapsed time.
///
public void ResetElapsedTime()
{
forceElapsedTimeToZero = true;
updatesSinceRunningSlowly1 = int.MaxValue;
updatesSinceRunningSlowly2 = int.MaxValue;
}
///
/// Allows the game to perform logic processing.
///
/// The time passed since the last update.
protected virtual void Update(GameTime gameTime)
{
}
///
/// Called when a frame is ready to be drawn.
///
/// The time passed since the last frame.
protected virtual void Draw(GameTime gameTime)
{
}
///
/// Initializes the game.
///
protected internal virtual void Initialize()
{
}
///
/// Loads graphical resources.
///
protected internal virtual void LoadContent()
{
}
///
/// Unloads graphical resources.
///
protected internal virtual void UnloadContent()
{
}
///
/// Releases unmanaged and - optionally - managed resources
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected internal virtual void Dispose(bool disposing)
{
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnActivated(EventArgs e)
{
if (Activated != null)
Activated(this, e);
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnDeactivated(EventArgs e)
{
if (Deactivated != null)
Deactivated(this, e);
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnExiting(EventArgs e)
{
if (Exiting != null)
Exiting(this, e);
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnFrameStart(CancelEventArgs e)
{
if (FrameStart != null)
FrameStart(this, e);
}
///
/// Raises the event.
///
/// The instance containing the event data.
protected virtual void OnFrameEnd(EventArgs e)
{
if (FrameEnd != null)
FrameEnd(this, e);
}
void DrawFrame()
{
try
{
if ( !IsExiting /* && !Window.IsMinimized */ ) // #28230 2012.5.1 yyagi
{
CancelEventArgs e = new CancelEventArgs(false);
OnFrameStart(e);
if (!e.Cancel)
{
gameTime.TotalRealTime = (float)clock.CurrentTime.TotalSeconds;
gameTime.ElapsedRealTime = (float)lastFrameElapsedRealTime.TotalSeconds;
gameTime.TotalGameTime = (float)totalGameTime.TotalSeconds;
gameTime.ElapsedGameTime = (float)lastFrameElapsedGameTime.TotalSeconds;
gameTime.IsRunningSlowly = drawRunningSlowly;
Draw(gameTime);
OnFrameEnd(EventArgs.Empty);
}
}
}
finally
{
lastFrameElapsedGameTime = TimeSpan.Zero;
lastFrameElapsedRealTime = TimeSpan.Zero;
}
}
void Application_Idle(object sender, EventArgs e)
{
NativeMessage message;
while (!NativeMethods.PeekMessage(out message, IntPtr.Zero, 0, 0, 0))
{
if (IsExiting)
Window.Close();
else
Tick();
}
}
void Window_ApplicationDeactivated(object sender, EventArgs e)
{
if (IsActive)
{
IsActive = false;
OnDeactivated(EventArgs.Empty);
}
}
void Window_ApplicationActivated(object sender, EventArgs e)
{
if (!IsActive)
{
IsActive = true;
OnActivated(EventArgs.Empty);
}
}
void Window_Paint(object sender, PaintEventArgs e)
{
DrawFrame();
}
void Window_Resume(object sender, EventArgs e)
{
clock.Resume();
}
void Window_Suspend(object sender, EventArgs e)
{
clock.Suspend();
}
}
}