2020-06-23 23:54:42 -05:00

255 lines
13 KiB
C#

using System;
using System.Runtime.InteropServices;
using System.Threading;
using SDL2;
using Serilog;
using Serilog.Core;
using SlatedGameToolkit.Framework.Exceptions;
using SlatedGameToolkit.Framework.Graphics.Textures;
using SlatedGameToolkit.Framework.Graphics.Window;
using SlatedGameToolkit.Framework.Input.Devices;
using SlatedGameToolkit.Framework.StateSystem;
using SlatedGameToolkit.Framework.StateSystem.States;
using SlatedGameToolkit.Framework.Utilities;
namespace SlatedGameToolkit.Framework {
/// <summary>
/// The main engine that will host the game loop.
/// </summary>
public static class GameEngine {
private const int GL_MAJOR_VER = 3, GL_MINOR_VER = 3;
public static bool Debugging { get; set; }
public static Logger Logger { get; private set; }
private static readonly object ignitionLock = new object();
private static readonly object deltaUpdateLock = new object();
private static Thread thread;
private static volatile bool exit = false, stopped = true;
private static volatile bool deltaChanged;
private static TimeSpan updateDeltaTime = TimeSpan.FromSeconds(1d/40d), frameDeltaTime = TimeSpan.FromSeconds(1d/60d);
/// <summary>
/// The amount of updates per second.
///
/// If greater than 0, game loop updates will be fixed (time steps).
/// More specifically, the amount of updates performed per frame to allow for physics calculations
/// to be calculated based on a constant time step will be calculated automatically.
///
/// If set to 0 or less, updates will be as fast as possible.
/// </summary>
/// <value>The updates per second.</value>
public static double UpdatesPerSecond {
get {
if (updateDeltaTime.TotalSeconds <= 0) return 0;
return 1d / updateDeltaTime.TotalSeconds;
}
set {
lock (deltaUpdateLock) {
if (value <= 0) {
updateDeltaTime = TimeSpan.FromSeconds(0);
} else {
updateDeltaTime = TimeSpan.FromSeconds(1d/value);
}
}
deltaChanged = true;
}
}
/// <summary>
/// The target frames per second. Will not go above this number, but may dip below this number.
/// Setting this to 0 renders as many frames as the system can handle.
/// Default is 60.
/// </summary>
/// <value>The target frames per second.</value>
public static double targetFPS {
get {
if (frameDeltaTime.TotalSeconds <= 0) return 0;
return 1 / frameDeltaTime.TotalSeconds;
}
set {
lock (deltaUpdateLock) {
if (value <= 0) {
frameDeltaTime = TimeSpan.FromSeconds(0);
} else {
frameDeltaTime = TimeSpan.FromSeconds(1d/value);
}
}
deltaChanged = true;
}
}
private static void Loop(Object o) {
if (!(o is IState)) throw new InternalFrameworkException(String.Format("Expected initial state object for asynchronous loop. Got {0}", o));
StateManager manager = new StateManager();
try {
IState initialState = (IState) o;
manager.Initialize(initialState);
DateTime currentTime = DateTime.Now;
TimeSpan timePassedFromLastUpdate = TimeSpan.Zero;
TimeSpan timePassedFromLastRender = TimeSpan.Zero;
TimeSpan updateDeltaTime = GameEngine.updateDeltaTime;
TimeSpan frameDeltaTime = GameEngine.frameDeltaTime;
deltaChanged = true;
stopped = false;
Logger.Information("Game engine initiated.");
while (!exit) {
//Pull latest deltas.
if (deltaChanged) {
lock (deltaUpdateLock) {
updateDeltaTime = GameEngine.updateDeltaTime;
frameDeltaTime = GameEngine.frameDeltaTime;
Logger.Information(String.Format("Deltas were set. Update Delta: {0}, Render target Delta: {1}", updateDeltaTime.TotalSeconds, frameDeltaTime.TotalSeconds));
}
deltaChanged = false;
}
//Events
SDL.SDL_Event SDL_Event;
while (SDL.SDL_PollEvent(out SDL_Event) != 0) {
switch (SDL_Event.type) {
case SDL.SDL_EventType.SDL_MOUSEMOTION:
Mouse.OnMouseMoved(SDL_Event.motion.x, SDL_Event.motion.y);
break;
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
Mouse.OnScroll(SDL_Event.wheel.x, SDL_Event.wheel.y);
break;
case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN:
if (SDL.SDL_BUTTON_LEFT == SDL_Event.button.button) {
Mouse.OnLeftChange(true);
} else if (SDL.SDL_BUTTON_RIGHT == SDL_Event.button.button) {
Mouse.OnRightChange(true);
} else if (SDL.SDL_BUTTON_MIDDLE == SDL_Event.button.button) {
Mouse.OnMiddleChange(true);
}
break;
case SDL.SDL_EventType.SDL_MOUSEBUTTONUP:
if (SDL.SDL_BUTTON_LEFT == SDL_Event.button.button) {
Mouse.OnLeftChange(false);
} else if (SDL.SDL_BUTTON_RIGHT == SDL_Event.button.button) {
Mouse.OnRightChange(false);
} else if (SDL.SDL_BUTTON_MIDDLE == SDL_Event.button.button) {
Mouse.OnMiddleChange(false);
}
break;
case SDL.SDL_EventType.SDL_KEYDOWN:
Keyboard.OnKeyPressed(SDL_Event.key.keysym.sym);
break;
case SDL.SDL_EventType.SDL_KEYUP:
Keyboard.OnKeyReleased(SDL_Event.key.keysym.sym);
break;
case SDL.SDL_EventType.SDL_WINDOWEVENT:
WindowContext handle = WindowContextsManager.ContextFromWindowID(SDL_Event.window.windowID);
switch (SDL_Event.window.windowEvent)
{
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
handle.OnResize(SDL_Event.window.data1, SDL_Event.window.data2);
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST:
handle.OnFocusLost();
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED:
handle.OnFocusGained();
break;
}
break;
case SDL.SDL_EventType.SDL_QUIT:
Stop();
break;
}
}
DateTime frameStart = DateTime.Now;
TimeSpan difference = frameStart - currentTime;
currentTime = frameStart;
timePassedFromLastUpdate += difference;
while (timePassedFromLastUpdate > updateDeltaTime) {
//Updates.
manager.update(updateDeltaTime.TotalSeconds <= 0 ? timePassedFromLastUpdate.TotalSeconds : updateDeltaTime.TotalSeconds);
timePassedFromLastUpdate -= updateDeltaTime;
if (updateDeltaTime.TotalSeconds <= 0) break;
}
timePassedFromLastRender += difference;
if (timePassedFromLastRender > frameDeltaTime) {
//Draw calls.
manager.render(updateDeltaTime.TotalSeconds <= 0 ? updateDeltaTime.TotalSeconds : (timePassedFromLastUpdate / updateDeltaTime));
timePassedFromLastRender = TimeSpan.Zero;
}
}
} catch (Exception e) {
Logger.Fatal(e.ToString());
} finally {
stopped = true;
manager.Dispose();
SDL.SDL_Quit();
Logger.Information("Game engine has stopped.");
Logger.Dispose();
WindowContextsManager.DisposeAllWindowContexts();
Logger = null;
}
}
/// <summary>
/// Requests to stop the game engine.
/// Game engine will finish the frame its currently in.
/// This needs to be called to perform proper clean up.
/// </summary>
/// <returns>False if the stop request has already been made.</returns>
public static bool Stop() {
lock (ignitionLock) {
if (exit) return false;
exit = true;
return true;
}
}
/// <summary>
/// Requests to start the game engine.
/// </summary>
/// <returns>True iff the engine is not already running.</returns>
public static bool Ignite(IState initialState) {
lock (ignitionLock) {
if (initialState == null) throw new ArgumentNullException("initialState");
if (!stopped) {
Logger.Warning("Engine is already running.");
return false;
}
GameEngine.Logger = new LoggerConfiguration().MinimumLevel.Debug().
WriteTo.File("SlatedGameToolKit.log", fileSizeLimitBytes: 1048576, rollOnFileSizeLimit: true).
CreateLogger();
SDL.SDL_version SDLVersion;
SDL.SDL_version SDLBuiltVersion;
SDL.SDL_GetVersion(out SDLVersion);
SDL.SDL_VERSION(out SDLBuiltVersion);
Logger.Information(String.Format("Attempting to initiate game engine with SDL version: {0}.{1}.{2}", SDLVersion.major, SDLVersion.minor, SDLVersion.patch));
if (!SDL.SDL_VERSION_ATLEAST(SDLBuiltVersion.major, SDLBuiltVersion.minor, SDLBuiltVersion.patch)) {
Logger.Warning(String.Format("Engine was designed with SDL version {0}.{1}.{2}, currently running on {3}.{4}.{5}", SDLBuiltVersion.major, SDLBuiltVersion.minor, SDLBuiltVersion.patch, SDLVersion.major, SDLVersion.minor, SDLVersion.patch));
if (SDLVersion.major < 2) {
Logger.Error("This engine was designed to work with SDL2. The version you're currently running is severely outdated.");
throw new FrameworkUsageException("Outdated SDL binaries.");
}
}
exit = false;
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO) != 0) {
throw new FrameworkSDLException();
}
if (SDL.SDL_Init(SDL.SDL_INIT_AUDIO) != 0) {
throw new FrameworkSDLException();
}
if (SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, GL_MAJOR_VER) < 0 ||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, GL_MINOR_VER) < 0 ||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE) < 0)
throw new FrameworkSDLException(string.Format("Unable to load correct OpenGL version {0}.{1}.0 Core.", GL_MINOR_VER, GL_MAJOR_VER));
thread = new Thread(Loop);
thread.Name = "SGTK-Engine";
thread.Priority = ThreadPriority.AboveNormal;
thread.Start(initialState);
return true;
}
}
public static bool IsRunning() {
return !stopped;
}
}
}