Split game initialization and starting into a separate thread and file.

This commit is contained in:
Westin Miller
2017-11-25 17:25:06 -08:00
parent e709b6c321
commit f0adeddb66
7 changed files with 296 additions and 122 deletions

View File

@@ -80,12 +80,12 @@ namespace Torch.API
Task InvokeAsync(Action action, [CallerMemberName] string caller = ""); Task InvokeAsync(Action action, [CallerMemberName] string caller = "");
/// <summary> /// <summary>
/// Start the Torch instance. /// Signals the torch instance to start, then blocks until it's started.
/// </summary> /// </summary>
void Start(); void Start();
/// <summary> /// <summary>
/// Stop the Torch instance. /// Signals the torch instance to stop, then blocks until it's stopped.
/// </summary> /// </summary>
void Stop(); void Stop();

View File

@@ -90,7 +90,7 @@ quit";
{ {
var ui = new TorchUI(_server); var ui = new TorchUI(_server);
if (_config.Autostart) if (_config.Autostart)
new Thread(_server.Start).Start(); _server.Start();
ui.ShowDialog(); ui.ShowDialog();
} }
else else

View File

@@ -146,6 +146,7 @@ namespace Torch.Server
} }
} }
/// <inheritdoc />
public override void Start() public override void Start()
{ {
if (State != ServerState.Stopped) if (State != ServerState.Stopped)
@@ -176,17 +177,19 @@ namespace Torch.Server
} }
/// <summary> /// <summary>
/// Restart the program. DOES NOT SAVE! /// Restart the program.
/// </summary> /// </summary>
public override void Restart() public override void Restart()
{ {
var exe = Assembly.GetExecutingAssembly().Location;
((TorchConfig) Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
Config.Autostart = true;
Process.Start(exe, Config.ToString());
Save(0).Wait(); Save(0).Wait();
Stop(); Stop();
LogManager.Flush(); LogManager.Flush();
var exe = Assembly.GetExecutingAssembly().Location;
((TorchConfig)Config).WaitForPID = Process.GetCurrentProcess().Id.ToString();
Config.Autostart = true;
Process.Start(exe, Config.ToString());
Process.GetCurrentProcess().Kill(); Process.GetCurrentProcess().Kill();
} }

View File

@@ -66,7 +66,7 @@ namespace Torch.Server
private void BtnStart_Click(object sender, RoutedEventArgs e) private void BtnStart_Click(object sender, RoutedEventArgs e)
{ {
_server.GetManager<InstanceManager>().SaveConfig(); _server.GetManager<InstanceManager>().SaveConfig();
new Thread(_server.Start).Start(); _server.Start();
} }
private void BtnStop_Click(object sender, RoutedEventArgs e) private void BtnStop_Click(object sender, RoutedEventArgs e)

View File

@@ -255,6 +255,7 @@
<Compile Include="Views\CollectionEditor.xaml.cs"> <Compile Include="Views\CollectionEditor.xaml.cs">
<DependentUpon>CollectionEditor.xaml</DependentUpon> <DependentUpon>CollectionEditor.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="VRageGame.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Torch.API\Torch.API.csproj"> <ProjectReference Include="..\Torch.API\Torch.API.csproj">

View File

@@ -343,7 +343,9 @@ namespace Torch
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}"); Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}");
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}"); Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
InitVRageInstance(); _game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, Config.InstancePath, RunArgs);
if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromMinutes(5)))
Log.Warn("Failed to wait for game to be initialized");
Managers.GetManager<PluginManager>().LoadPlugins(); Managers.GetManager<PluginManager>().LoadPlugins();
Managers.Attach(); Managers.Attach();
_init = true; _init = true;
@@ -357,95 +359,15 @@ namespace Torch
public virtual void Dispose() public virtual void Dispose()
{ {
Managers.Detach(); Managers.Detach();
DisposeVRageInstance(); _game.SignalDestroy();
if (!_game.WaitFor(VRageGame.GameState.Destroyed, TimeSpan.FromSeconds(15)))
Log.Warn("Failed to wait for the game to be destroyed");
_game = null;
} }
#endregion #endregion
#region VRage Instance Init/Destroy private VRageGame _game;
#pragma warning disable 649
[ReflectedGetter(Name = "m_plugins", Type = typeof(MyPlugins))]
private static readonly Func<List<IPlugin>> _getVRagePluginList;
#pragma warning restore 649
protected SpaceEngineersGame GameInstance { get; private set; }
/// <summary>
/// Sets up the VRage instance.
/// Any flags (ie <see cref="Sandbox.Engine.Platform.Game.IsDedicated"/>) must be set before this is called.
/// </summary>
protected virtual void InitVRageInstance()
{
bool dedicated = Sandbox.Engine.Platform.Game.IsDedicated;
Environment.SetEnvironmentVariable("SteamAppId", SteamAppId.ToString());
MyServiceManager.Instance.AddService<IMyGameService>(new MySteamService(dedicated, SteamAppId));
if (dedicated && !MyGameService.HasGameServer)
{
Log.Warn("Steam service is not running! Please reinstall dedicated server.");
return;
}
SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
MySessionComponentExtDebug.ForceDisable = true;
MyPerGameSettings.SendLogToKeen = false;
// SpaceEngineersGame.SetupAnalytics();
MyFileSystem.ExePath = Path.GetDirectoryName(typeof(SpaceEngineersGame).Assembly.Location);
TweakGameSettings();
MyInitializer.InvokeBeforeRun(SteamAppId, SteamAppName, Config.InstancePath);
// MyInitializer.InitCheckSum();
// Hook into the VRage plugin system for updates.
_getVRagePluginList().Add(this);
if (!MySandboxGame.IsReloading)
MyFileSystem.InitUserSpecific(dedicated ? null : MyGameService.UserId.ToString());
MySandboxGame.IsReloading = dedicated;
// render init
{
IMyRender renderer = null;
if (dedicated)
{
renderer = new MyNullRender();
}
else
{
MyPerformanceSettings preset = MyGuiScreenOptionsGraphics.GetPreset(MyRenderQualityEnum.NORMAL);
MyRenderProxy.Settings.User = MyVideoSettingsManager.GetGraphicsSettingsFromConfig(ref preset)
.PerformanceSettings.RenderSettings;
MyStringId graphicsRenderer = MySandboxGame.Config.GraphicsRenderer;
if (graphicsRenderer == MySandboxGame.DirectX11RendererKey)
{
renderer = new MyDX11Render(new MyRenderSettings?(MyRenderProxy.Settings));
if (!renderer.IsSupported)
{
MySandboxGame.Log.WriteLine(
"DirectX 11 renderer not supported. No renderer to revert back to.");
renderer = null;
}
}
if (renderer == null)
{
throw new MyRenderException(
"The current version of the game requires a Dx11 card. \\n For more information please see : http://blog.marekrosa.org/2016/02/space-engineers-news-full-source-code_26.html",
MyRenderExceptionEnum.GpuNotSupported);
}
MySandboxGame.Config.GraphicsRenderer = graphicsRenderer;
}
MyRenderProxy.Initialize(renderer);
MyRenderProxy.GetRenderProfiler().SetAutocommit(false);
MyRenderProxy.GetRenderProfiler().InitMemoryHack("MainEntryPoint");
}
GameInstance = new SpaceEngineersGame(RunArgs);
}
/// <summary> /// <summary>
/// Called after the basic game information is filled, but before the game is created. /// Called after the basic game information is filled, but before the game is created.
@@ -454,22 +376,6 @@ namespace Torch
{ {
} }
/// <summary>
/// Tears down the VRage instance
/// </summary>
protected virtual void DisposeVRageInstance()
{
GameInstance.Dispose();
GameInstance = null;
MyGameService.ShutDown();
_getVRagePluginList().Remove(this);
MyInitializer.InvokeAfterRun();
}
#endregion
/// <inheritdoc/> /// <inheritdoc/>
public virtual Task Save(long callerId) public virtual Task Save(long callerId)
@@ -480,21 +386,17 @@ namespace Torch
/// <inheritdoc/> /// <inheritdoc/>
public virtual void Start() public virtual void Start()
{ {
if (MySandboxGame.FatalErrorDuringInit) _game.SignalStart();
{ if (!_game.WaitFor(VRageGame.GameState.Running, TimeSpan.FromSeconds(15)))
Log.Warn($"Failed to start sandbox game: fatal error during init"); Log.Warn("Failed to wait for the game to be started");
return;
}
GameInstance.Run();
} }
/// <inheritdoc /> /// <inheritdoc />
public virtual void Stop() public virtual void Stop()
{ {
if (IsOnGameThread()) _game.SignalStop();
MySandboxGame.Static.Exit(); if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromSeconds(15)))
else Log.Warn("Failed to wait for the game to be stopped");
InvokeBlocking(MySandboxGame.Static.Exit);
} }
/// <inheritdoc /> /// <inheritdoc />

268
Torch/VRageGame.cs Normal file
View File

@@ -0,0 +1,268 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Havok;
using NLog;
using NLog.Fluent;
using Sandbox;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Platform.VideoMode;
using Sandbox.Game;
using SpaceEngineers.Game;
using SpaceEngineers.Game.GUI;
using Torch.Utils;
using VRage;
using VRage.FileSystem;
using VRage.Game;
using VRage.Game.SessionComponents;
using VRage.GameServices;
using VRage.Plugins;
using VRage.Steam;
using VRage.Utils;
using VRageRender;
namespace Torch
{
public class VRageGame
{
private static readonly ILogger _log = LogManager.GetCurrentClassLogger();
#pragma warning disable 649
[ReflectedGetter(Name = "m_plugins", Type = typeof(MyPlugins))]
private static readonly Func<List<IPlugin>> _getVRagePluginList;
#pragma warning restore 649
private readonly TorchBase _torch;
private readonly Action _tweakGameSettings;
private readonly string _userDataPath;
private readonly string _appName;
private readonly uint _appSteamId;
private readonly string[] _runArgs;
private SpaceEngineersGame _game;
private readonly Thread _updateThread;
private bool _startGame = false;
private readonly AutoResetEvent _commandChanged = new AutoResetEvent(false);
private bool _destroyGame = false;
private readonly AutoResetEvent _stateChangedEvent = new AutoResetEvent(false);
private GameState _state;
public enum GameState
{
Creating,
Stopped,
Running,
Destroyed
}
internal VRageGame(TorchBase torch, Action tweakGameSettings, string appName, uint appSteamId,
string userDataPath, string[] runArgs)
{
_torch = torch;
_tweakGameSettings = tweakGameSettings;
_appName = appName;
_appSteamId = appSteamId;
_userDataPath = userDataPath;
_runArgs = runArgs;
_updateThread = new Thread(Run);
_updateThread.Start();
}
private void StateChange(GameState s)
{
if (_state == s)
return;
_state = s;
_stateChangedEvent.Set();
}
private void Run()
{
StateChange(GameState.Creating);
try
{
Create();
_destroyGame = false;
while (!_destroyGame)
{
StateChange(GameState.Stopped);
_commandChanged.WaitOne();
if (_startGame)
{
_startGame = false;
DoStart();
}
}
}
finally
{
Destroy();
StateChange(GameState.Destroyed);
}
}
private void Create()
{
bool dedicated = Sandbox.Engine.Platform.Game.IsDedicated;
Environment.SetEnvironmentVariable("SteamAppId", _appSteamId.ToString());
MyServiceManager.Instance.AddService<IMyGameService>(new MySteamService(dedicated, _appSteamId));
if (dedicated && !MyGameService.HasGameServer)
{
_log.Warn("Steam service is not running! Please reinstall dedicated server.");
return;
}
SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
MySessionComponentExtDebug.ForceDisable = true;
MyPerGameSettings.SendLogToKeen = false;
// SpaceEngineersGame.SetupAnalytics();
MyFileSystem.ExePath = Path.GetDirectoryName(typeof(SpaceEngineersGame).Assembly.Location);
_tweakGameSettings();
MyFileSystem.Reset();
MyInitializer.InvokeBeforeRun(_appSteamId, _appName, _userDataPath);
// MyInitializer.InitCheckSum();
// Hook into the VRage plugin system for updates.
_getVRagePluginList().Add(_torch);
if (!MySandboxGame.IsReloading)
MyFileSystem.InitUserSpecific(dedicated ? null : MyGameService.UserId.ToString());
MySandboxGame.IsReloading = dedicated;
// render init
{
IMyRender renderer = null;
if (dedicated)
{
renderer = new MyNullRender();
}
else
{
MyPerformanceSettings preset = MyGuiScreenOptionsGraphics.GetPreset(MyRenderQualityEnum.NORMAL);
MyRenderProxy.Settings.User = MyVideoSettingsManager.GetGraphicsSettingsFromConfig(ref preset)
.PerformanceSettings.RenderSettings;
MyStringId graphicsRenderer = MySandboxGame.Config.GraphicsRenderer;
if (graphicsRenderer == MySandboxGame.DirectX11RendererKey)
{
renderer = new MyDX11Render(new MyRenderSettings?(MyRenderProxy.Settings));
if (!renderer.IsSupported)
{
MySandboxGame.Log.WriteLine(
"DirectX 11 renderer not supported. No renderer to revert back to.");
renderer = null;
}
}
if (renderer == null)
{
throw new MyRenderException(
"The current version of the game requires a Dx11 card. \\n For more information please see : http://blog.marekrosa.org/2016/02/space-engineers-news-full-source-code_26.html",
MyRenderExceptionEnum.GpuNotSupported);
}
MySandboxGame.Config.GraphicsRenderer = graphicsRenderer;
}
MyRenderProxy.Initialize(renderer);
MyRenderProxy.GetRenderProfiler().SetAutocommit(false);
MyRenderProxy.GetRenderProfiler().InitMemoryHack("MainEntryPoint");
}
_game = new SpaceEngineersGame(_runArgs);
}
private void Destroy()
{
_game.Dispose();
_game = null;
MyGameService.ShutDown();
_getVRagePluginList().Remove(_torch);
MyInitializer.InvokeAfterRun();
}
private void DoStart()
{
if (MySandboxGame.FatalErrorDuringInit)
{
_log.Warn("Failed to start sandbox game: fatal error during init");
return;
}
try
{
StateChange(GameState.Running);
_game.Run();
}
finally
{
StateChange(GameState.Stopped);
}
}
private void DoStop()
{
ParallelTasks.Parallel.Scheduler.WaitForTasksToFinish(TimeSpan.FromSeconds(10.0));
MySandboxGame.Static.Exit();
}
/// <summary>
/// Signals the game to stop itself.
/// </summary>
public void SignalStop()
{
_startGame = false;
_game.Invoke(DoStop, $"{nameof(VRageGame)}::{nameof(SignalStop)}");
}
/// <summary>
/// Signals the game to start itself
/// </summary>
public void SignalStart()
{
_startGame = true;
_commandChanged.Set();
}
/// <summary>
/// Signals the game to destroy itself
/// </summary>
public void SignalDestroy()
{
_destroyGame = true;
SignalStop();
_commandChanged.Set();
}
/// <summary>
/// Waits for the game to transition to the given state
/// </summary>
/// <param name="state">State to transition to</param>
/// <param name="timeout">Timeout</param>
/// <returns></returns>
public bool WaitFor(GameState state, TimeSpan? timeout)
{
// Kinda icky, but we can't block the update and expect the state to change.
if (Thread.CurrentThread == _updateThread)
return _state == state;
DateTime? end = timeout.HasValue ? (DateTime?) (DateTime.Now + timeout.Value) : null;
while (_state != state && (!end.HasValue || end > DateTime.Now + TimeSpan.FromSeconds(1)))
if (end.HasValue)
_stateChangedEvent.WaitOne(end.Value - DateTime.Now);
else
_stateChangedEvent.WaitOne();
return _state == state;
}
}
}