This repository has been archived on 2023-07-11. You can view files and clone it, but cannot push or open issues or pull requests.
Blarg.GameFramework/Blarg.GameFramework.SDL2/SDLApplication.cs

663 lines
19 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using SDL2;
using PortableGL;
using PortableGL.SDL;
using Blarg.GameFramework.Graphics;
using Blarg.GameFramework.Input;
using Blarg.GameFramework.IO;
namespace Blarg.GameFramework
{
public class SDLApplication : BaseApplication
{
const string LOG_TAG = "SDL_APP";
#region Fields
bool _isSDLinited;
IntPtr _window;
IntPtr _glContext;
SDLLogger _logger;
SDLKeyboard _keyboard;
SDLMouse _mouse;
SDLFileSystem _filesystem;
SDLWindow _windowInfo;
SDLGL20 _gl;
PlatformOS _os;
bool _isWindowActive;
bool _isPaused;
bool _isQuitting;
int _targetUpdatesPerSecond;
int _ticksPerUpdate;
float _fixedUpdateInterval;
int _maxFrameSkip = 10;
#endregion
#region Properties
public override PlatformOS OperatingSystem
{
get { return _os; }
}
public override PlatformType PlatformType
{
get { return PlatformType.Desktop; }
}
public override ILogger Logger
{
get { return _logger; }
}
public override IFileSystem FileSystem
{
get { return _filesystem; }
}
public override IKeyboard Keyboard
{
get { return _keyboard; }
}
public override IMouse Mouse
{
get { return _mouse; }
}
public override ITouchScreen TouchScreen
{
get { return null; }
}
public override IPlatformWindow Window
{
get { return _windowInfo; }
}
public override GL20 GL
{
get { return _gl; }
}
#endregion
public SDLApplication()
{
_logger = new SDLLogger();
_windowInfo = new SDLWindow();
if (CurrentOS.IsWindows)
_os = PlatformOS.Windows;
else if (CurrentOS.IsLinux)
_os = PlatformOS.Linux;
else if (CurrentOS.IsMac)
_os = PlatformOS.MacOS;
else
throw new Exception("Unable to determine OS.");
SetUpdateFrequency(25);
}
public override void Run(IGameApp gameApp, IPlatformConfiguration config)
{
if (gameApp == null)
throw new ArgumentNullException("gameApp");
GameApp = gameApp;
if (config == null)
throw new ArgumentNullException("config");
if (!(config is SDLConfiguration))
throw new ArgumentException("Must pass a SDLConfiguration object.", "config");
Logger.Info(LOG_TAG, "Running...");
SDLConfiguration sdlConfig = (SDLConfiguration)config;
Logger.Info(LOG_TAG, "Received SDL configuration:");
Logger.Info(LOG_TAG, "\tTitle: {0}", sdlConfig.Title);
Logger.Info(LOG_TAG, "\tWidth: {0}", sdlConfig.Width);
Logger.Info(LOG_TAG, "\tHeight: {0}", sdlConfig.Height);
Logger.Info(LOG_TAG, "\tFullscreen: {0}", sdlConfig.Fullscreen);
Logger.Info(LOG_TAG, "\tResizeable: {0}", sdlConfig.Resizeable);
Logger.Info(LOG_TAG, "\tGL Doublebuffer: {0}", sdlConfig.glDoubleBuffer);
Logger.Info(LOG_TAG, "\tGL Depth Buffer Size: {0}", sdlConfig.glDepthBufferSize);
Logger.Info(LOG_TAG, "\tGL Red Size: {0}", sdlConfig.glRedSize);
Logger.Info(LOG_TAG, "\tGL Green Size: {0}", sdlConfig.glGreenSize);
Logger.Info(LOG_TAG, "\tGL Blue Size: {0}", sdlConfig.glBlueSize);
Logger.Info(LOG_TAG, "\tGL Alpha Size: {0}", sdlConfig.glAlphaSize);
Logger.Info(LOG_TAG, "\tTarget Update Frequency: {0}", sdlConfig.TargetUpdateFrequency);
if (!InitSDL())
{
Logger.Error(LOG_TAG, "SDL initialization failed. Aborting.");
return;
}
if (!InitSDLWindow(sdlConfig))
{
Logger.Error(LOG_TAG, "SDL window creation failed. Aborting.");
return;
}
Framework.Set(this);
Logger.Info(LOG_TAG, "Framework initialization complete, beginning game application set up.");
SetUpdateFrequency(sdlConfig.TargetUpdateFrequency);
OnInit();
OnNewContext();
OnResize(ScreenOrientation.Rotation0, _windowInfo.ClientRectangle);
OnLoad();
Logger.Info(LOG_TAG, "Main loop starting.");
MainLoop();
Logger.Info(LOG_TAG, "Main loop finished.");
OnUnload();
OnLostContext();
OnShutdown();
Logger.Info(LOG_TAG, "Cleaning up game application object.");
GameApp.Dispose();
GameApp = null;
Release();
}
public override void Quit()
{
Logger.Info(LOG_TAG, "Quit signaled. Main loop will exit.");
_isQuitting = true;
}
private void MainLoop()
{
_isWindowActive = true;
_isPaused = false;
_isQuitting = false;
int numUpdatesThisFrame = 0;
int numLoops = 0;
int timeElapsed = 0;
int updateTime = 0;
int renderTime = 0;
int numUpdates = 0;
int numRenders = 0;
int nextUpdateAt = Environment.TickCount;
int currentTime = Environment.TickCount;
while (!_isQuitting)
{
if (_isPaused)
{
// we always want to be processing events, even when paused
DoEvents();
}
else
{
int newTime = Environment.TickCount;
int frameTime = newTime - currentTime;
currentTime = newTime;
timeElapsed += frameTime;
// every second recalculate the FPS
if (timeElapsed >= 1000)
{
FPS = numLoops;
FrameTime = 1000.0f / FPS;
RendersPerSecond = numRenders;
UpdatesPerSecond = numUpdates;
RenderTime = renderTime;
UpdateTime = updateTime;
numUpdates = 0;
numRenders = 0;
renderTime = 0;
updateTime = 0;
numLoops = 0;
timeElapsed = 0;
}
// we're "running slowly" if we're more then one update behind
if (currentTime > nextUpdateAt + _ticksPerUpdate)
IsRunningSlowly = true;
else
IsRunningSlowly = false;
numUpdatesThisFrame = 0;
while (Environment.TickCount >= nextUpdateAt && numUpdatesThisFrame < _maxFrameSkip)
{
if (numUpdatesThisFrame > 0)
IsRunningSlowly = true;
int before = Environment.TickCount;
DoEvents();
OnUpdate(_fixedUpdateInterval);
updateTime += Environment.TickCount - before;
++numUpdatesThisFrame;
nextUpdateAt += _ticksPerUpdate;
++numUpdates;
}
if (_isWindowActive)
{
float renderDelta = (float)(Environment.TickCount + _ticksPerUpdate - nextUpdateAt) / (float)_ticksPerUpdate;
int before = Environment.TickCount;
OnRender(renderDelta); // TODO
SDL.SDL_GL_SwapWindow(_window);
renderTime += Environment.TickCount - before;
++numRenders;
}
++numLoops;
}
}
}
private void SetUpdateFrequency(int targetFrequency)
{
_targetUpdatesPerSecond = targetFrequency;
_ticksPerUpdate = 1000 / _targetUpdatesPerSecond;
_fixedUpdateInterval = _ticksPerUpdate / 1000.0f;
}
#region Initialization
private bool InitSDL()
{
Logger.Info(LOG_TAG, "SDL initialization starting.");
SDL.SDL_version sdlVersion;
SDL.SDL_VERSION(out sdlVersion);
Logger.Info(LOG_TAG, "SDL Runtime Version: {0}.{1}.{2}", sdlVersion.major, sdlVersion.minor, sdlVersion.patch);
Logger.Info(LOG_TAG, "SDL Linked Version: {0}.{1}.{2}", SDL.SDL_MAJOR_VERSION, SDL.SDL_MINOR_VERSION, SDL.SDL_PATCHLEVEL);
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER | SDL.SDL_INIT_JOYSTICK | SDL.SDL_INIT_TIMER) == -1)
{
Logger.Error(LOG_TAG, "SDL_Init() failed: {0}", SDL.SDL_GetError());
return false;
}
_isSDLinited = true;
_keyboard = new SDLKeyboard();
Logger.Info(LOG_TAG, "Keyboard input device ready.");
_mouse = new SDLMouse();
Logger.Info(LOG_TAG, "Mouse input device ready.");
int numJoysticks = SDL.SDL_NumJoysticks();
Logger.Info(LOG_TAG, "{0} joystick input devices found.", numJoysticks);
for (int i = 0; i < numJoysticks; ++i)
{
Logger.Info(LOG_TAG, "Joystick #{0}. {1}:", (i + 1), SDL.SDL_JoystickNameForIndex(i));
IntPtr joystick = SDL.SDL_JoystickOpen(i);
if (joystick != IntPtr.Zero)
{
Logger.Info(LOG_TAG, "\tAxes: {0}", SDL.SDL_JoystickNumAxes(joystick));
Logger.Info(LOG_TAG, "\tBalls: {0}", SDL.SDL_JoystickNumBalls(joystick));
Logger.Info(LOG_TAG, "\tHats: {0}", SDL.SDL_JoystickNumHats(joystick));
Logger.Info(LOG_TAG, "\tButtons: {0}", SDL.SDL_JoystickNumButtons(joystick));
SDL.SDL_JoystickClose(joystick);
}
else
Logger.Warn(LOG_TAG, "\tMore information could not be obtained.");
}
_filesystem = new SDLFileSystem(Logger);
Logger.Info(LOG_TAG, "Filesystem access initialized.");
Logger.Info(LOG_TAG, "Using assets path: {0}", _filesystem.AssetsPath);
int numVideoDrivers = SDL.SDL_GetNumVideoDrivers();
Logger.Info(LOG_TAG, "Video drivers present: {0}.", numVideoDrivers);
for (int i = 0; i < numVideoDrivers; ++i)
Logger.Info(LOG_TAG, "\t{0}: {1}", (i + 1), SDL.SDL_GetVideoDriver(i));
Logger.Info(LOG_TAG, "Currently using video driver: {0}", SDL.SDL_GetCurrentVideoDriver());
int numAudioDrivers = SDL.SDL_GetNumAudioDrivers();
Logger.Info(LOG_TAG, "Audio drivers present: {0}", numAudioDrivers);
for (int i = 0; i < numAudioDrivers; ++i)
Logger.Info(LOG_TAG, "\t{0}: {1}", (i + 1), SDL.SDL_GetAudioDriver(i));
Logger.Info(LOG_TAG, "Currently using audio driver: {0}", SDL.SDL_GetCurrentAudioDriver());
Logger.Info(LOG_TAG, "SDL initialization finished.");
return true;
}
#endregion
#region Window Management
private bool InitSDLWindow(SDLConfiguration config)
{
Logger.Info(LOG_TAG, "SDL Window initialization starting.");
int flags = (int)SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL | (int)SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN;
if (config.Fullscreen)
flags |= (int)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
if (config.Resizeable)
flags |= (int)SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
if (!CreateWindow(config.Title, config.Width, config.Height, flags))
return false;
if (!CreateOpenGLContext(config))
return false;
Logger.Info(LOG_TAG, "SDL Window initialization finished.");
return true;
}
private bool CreateWindow(string title, int width, int height, int flags)
{
Logger.Info(LOG_TAG, "Attempting to set up new window with dimensions {0}x{1}.", width, height);
if (_window != IntPtr.Zero)
throw new InvalidOperationException("Cannot create new window before destorying existing one.");
_window = SDL.SDL_CreateWindow(title, SDL.SDL_WINDOWPOS_UNDEFINED, SDL.SDL_WINDOWPOS_UNDEFINED, width, height, (SDL.SDL_WindowFlags)flags);
if (_window == IntPtr.Zero)
{
Logger.Error(LOG_TAG, "Window creation failed: {0}", SDL.SDL_GetError());
return false;
}
SetWindowInfo();
Logger.Info(LOG_TAG, "Window creation succeeded.");
return true;
}
private bool DestroyWindow()
{
Logger.Info(LOG_TAG, "Destroying window.");
if (_window == IntPtr.Zero)
{
Logger.Warn(LOG_TAG, "No window currently exists, not doing anything.");
return true;
}
SDL.SDL_DestroyWindow(_window);
_window = IntPtr.Zero;
Logger.Info(LOG_TAG, "Window destroyed.");
return true;
}
private void SetWindowInfo()
{
if (_window == IntPtr.Zero)
throw new InvalidOperationException("Cannot set window info for a non-existant window.");
int windowX;
int windowY;
SDL.SDL_GetWindowPosition(_window, out windowX, out windowY);
int clientWidth;
int clientHeight;
SDL.SDL_GetWindowSize(_window, out clientWidth, out clientHeight);
_windowInfo.ClientWidth = clientWidth;
_windowInfo.ClientHeight = clientHeight;
_windowInfo.ClientRectangle = new Rect(0, 0, clientWidth, clientHeight);
Logger.Info(LOG_TAG, "Window content area set to {0}", _windowInfo.ClientRectangle);
}
#endregion
#region OpenGL Context Management
private bool CreateOpenGLContext(SDLConfiguration config)
{
Logger.Info(LOG_TAG, "Attempting to create OpenGL context.");
if (_glContext != IntPtr.Zero)
throw new InvalidOperationException("Cannoy create new OpenGL context before destroying existing one.");
if (_window == IntPtr.Zero)
throw new InvalidOperationException("Cannot create an OpenGL context without an existing window.");
// minimum requirements
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, config.glMajorVersion);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, config.glMinorVersion);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, config.glDoubleBuffer ? 1 : 0);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, config.glDepthBufferSize);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, config.glRedSize);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, config.glGreenSize);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, config.glBlueSize);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, config.glAlphaSize);
_glContext = SDL.SDL_GL_CreateContext(_window);
if (_glContext == IntPtr.Zero)
{
Logger.Error(LOG_TAG, "OpenGL context creation failed: {0}", SDL.SDL_GetError());
return false;
}
Logger.Info(LOG_TAG, "OpenGL context creation succeeded.");
Logger.Info(LOG_TAG, "Setting OpenTK's OpenGL context and loading OpenGL extensions.");
_gl = new SDLGL20(_glContext);
int redSize;
int greenSize;
int blueSize;
int alphaSize;
int bufferSize;
int doubleBuffer;
int depthSize;
int stencilSize;
int accumRedSize;
int accumGreenSize;
int accumBlueSize;
int accumAlphaSize;
int stereo;
int multisampleBuffers;
int multisampleSamples;
int acceleratedVisual;
int contextMajorVersion;
int contextMinorVersion;
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, out redSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, out greenSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, out blueSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, out alphaSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_BUFFER_SIZE, out bufferSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, out doubleBuffer);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, out depthSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_STENCIL_SIZE, out stencilSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_RED_SIZE, out accumRedSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_GREEN_SIZE, out accumGreenSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_BLUE_SIZE, out accumBlueSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_ALPHA_SIZE, out accumAlphaSize);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_STEREO, out stereo);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_MULTISAMPLEBUFFERS, out multisampleBuffers);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_MULTISAMPLESAMPLES, out multisampleSamples);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, out acceleratedVisual);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, out contextMajorVersion);
SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, out contextMinorVersion);
Logger.Info(LOG_TAG, "OpenGL context attributes:");
Logger.Info(LOG_TAG, "\tGL_RED_SIZE: {0}", redSize);
Logger.Info(LOG_TAG, "\tGL_GREEN_SIZE: {0}", greenSize);
Logger.Info(LOG_TAG, "\tGL_BLUE_SIZE: {0}", blueSize);
Logger.Info(LOG_TAG, "\tGL_ALPHA_SIZE: {0}", alphaSize);
Logger.Info(LOG_TAG, "\tGL_BUFFER_SIZE: {0}", bufferSize);
Logger.Info(LOG_TAG, "\tGL_DOUBLEBUFFER: {0}", doubleBuffer);
Logger.Info(LOG_TAG, "\tGL_DEPTH_SIZE: {0}", depthSize);
Logger.Info(LOG_TAG, "\tGL_STENCIL_SIZE: {0}", stencilSize);
Logger.Info(LOG_TAG, "\tGL_ACCUM_RED_SIZE: {0}", accumRedSize);
Logger.Info(LOG_TAG, "\tGL_ACCUM_GREEN_SIZE: {0}", accumGreenSize);
Logger.Info(LOG_TAG, "\tGL_ACCUM_BLUE_SIZE: {0}", accumBlueSize);
Logger.Info(LOG_TAG, "\tGL_ACCUM_ALPHA_SIZE: {0}", accumAlphaSize);
Logger.Info(LOG_TAG, "\tGL_STEREO: {0}", stereo);
Logger.Info(LOG_TAG, "\tGL_MULTISAMPLEBUFFERS: {0}", multisampleBuffers);
Logger.Info(LOG_TAG, "\tGL_MULTISAMPLESAMPLES: {0}", multisampleSamples);
Logger.Info(LOG_TAG, "\tGL_ACCELERATED_VISUAL: {0}", acceleratedVisual);
Logger.Info(LOG_TAG, "\tGL_CONTEXT_MAJOR_VERSION: {0}", contextMajorVersion);
Logger.Info(LOG_TAG, "\tGL_CONTEXT_MINOR_VERSION: {0}", contextMinorVersion);
Logger.Info(LOG_TAG, "Attempting to enable V-sync.");
if (SDL.SDL_GL_SetSwapInterval(1) != 0)
Logger.Warn(LOG_TAG, "Could not set swap interval: {0}", SDL.SDL_GetError());
else
Logger.Info(LOG_TAG, "Swap interval set successful.");
return true;
}
private bool DestroyOpenGLContext()
{
Logger.Info(LOG_TAG, "Destroying OpenGL context.");
if (_glContext == IntPtr.Zero)
{
Logger.Warn(LOG_TAG, "No OpenGL context currently exists, not doing anything.");
return true;
}
SDL.SDL_GL_DeleteContext(_glContext);
OpenTK.Graphics.GraphicsContext.CurrentContext = IntPtr.Zero;
_glContext = IntPtr.Zero;
Logger.Info(LOG_TAG, "OpenGL context destroyed.");
return true;
}
#endregion
#region Events
private void DoEvents()
{
SDL.SDL_Event e;
while (SDL.SDL_PollEvent(out e) != 0)
{
if (e.type == SDL.SDL_EventType.SDL_WINDOWEVENT)
{
switch (e.window.windowEvent)
{
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MINIMIZED:
Logger.Info(LOG_TAG, "Window focus lost.");
Logger.Info(LOG_TAG, "Window marked inactive.");
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESTORED:
Logger.Info(LOG_TAG, "Window focus gained.");
Logger.Info(LOG_TAG, "Window marked active.");
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_ENTER:
Logger.Info(LOG_TAG, "Gained mouse focus.");
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE:
Logger.Info(LOG_TAG, "Lost mouse focus.");
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED:
Logger.Info(LOG_TAG, "Gained input device focus.");
OnAppGainFocus();
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST:
Logger.Info(LOG_TAG, "Lost input device focus.");
OnAppLostFocus();
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED:
Logger.Info(LOG_TAG, "Window resized to {0}x{1}.", e.window.data1, e.window.data2);
Rect size = new Rect();
size.Right = e.window.data1;
size.Bottom = e.window.data2;
SetWindowInfo();
OnResize(ScreenOrientation.Rotation0, size);
break;
}
}
else
{
switch (e.type)
{
case SDL.SDL_EventType.SDL_QUIT:
Logger.Info(LOG_TAG, "Event: SQL_QUIT");
_isQuitting = true;
break;
case SDL.SDL_EventType.SDL_KEYDOWN:
case SDL.SDL_EventType.SDL_KEYUP:
_keyboard.OnKeyEvent(e.key);
break;
case SDL.SDL_EventType.SDL_MOUSEMOTION:
_mouse.OnMotionEvent(e.motion);
break;
case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN:
case SDL.SDL_EventType.SDL_MOUSEBUTTONUP:
_mouse.OnButtonEvent(e.button);
break;
}
}
}
}
#endregion
#region Support Functions
public override IPlatformBitmap LoadBitmap(Stream file)
{
return new SDLBitmap(file);
}
#endregion
#region IDisposable
protected override void Release()
{
if (!_isSDLinited)
return;
Logger.Info(LOG_TAG, "Releasing SDL application object.");
base.Release();
Logger.Info(LOG_TAG, "Releasing SDL.");
DestroyOpenGLContext();
DestroyWindow();
SDL.SDL_Quit();
_isSDLinited = false;
Logger.Info(LOG_TAG, "SDL shutdown.");
}
~SDLApplication()
{
Release();
}
public override void Dispose()
{
base.Dispose();
Logger.Info(LOG_TAG, "Disposing.");
Release();
GC.SuppressFinalize(this);
}
#endregion
}
}