From f0adeddb665ff591aa3bfee639d92b047a54701b Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Sat, 25 Nov 2017 17:25:06 -0800 Subject: [PATCH] Split game initialization and starting into a separate thread and file. --- Torch.API/ITorchBase.cs | 4 +- Torch.Server/Initializer.cs | 2 +- Torch.Server/TorchServer.cs | 13 +- Torch.Server/Views/TorchUI.xaml.cs | 2 +- Torch/Torch.csproj | 1 + Torch/TorchBase.cs | 128 ++------------ Torch/VRageGame.cs | 268 +++++++++++++++++++++++++++++ 7 files changed, 296 insertions(+), 122 deletions(-) create mode 100644 Torch/VRageGame.cs diff --git a/Torch.API/ITorchBase.cs b/Torch.API/ITorchBase.cs index edf21bb..a89534c 100644 --- a/Torch.API/ITorchBase.cs +++ b/Torch.API/ITorchBase.cs @@ -80,12 +80,12 @@ namespace Torch.API Task InvokeAsync(Action action, [CallerMemberName] string caller = ""); /// - /// Start the Torch instance. + /// Signals the torch instance to start, then blocks until it's started. /// void Start(); /// - /// Stop the Torch instance. + /// Signals the torch instance to stop, then blocks until it's stopped. /// void Stop(); diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs index b025727..020171f 100644 --- a/Torch.Server/Initializer.cs +++ b/Torch.Server/Initializer.cs @@ -90,7 +90,7 @@ quit"; { var ui = new TorchUI(_server); if (_config.Autostart) - new Thread(_server.Start).Start(); + _server.Start(); ui.ShowDialog(); } else diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index d97d6fe..e40693e 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -146,6 +146,7 @@ namespace Torch.Server } } + /// public override void Start() { if (State != ServerState.Stopped) @@ -176,17 +177,19 @@ namespace Torch.Server } /// - /// Restart the program. DOES NOT SAVE! + /// Restart the program. /// 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(); Stop(); 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(); } diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs index 92b1083..1bee4c3 100644 --- a/Torch.Server/Views/TorchUI.xaml.cs +++ b/Torch.Server/Views/TorchUI.xaml.cs @@ -66,7 +66,7 @@ namespace Torch.Server private void BtnStart_Click(object sender, RoutedEventArgs e) { _server.GetManager().SaveConfig(); - new Thread(_server.Start).Start(); + _server.Start(); } private void BtnStop_Click(object sender, RoutedEventArgs e) diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index a77ba4a..d999355 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -255,6 +255,7 @@ CollectionEditor.xaml + diff --git a/Torch/TorchBase.cs b/Torch/TorchBase.cs index a187169..9160108 100644 --- a/Torch/TorchBase.cs +++ b/Torch/TorchBase.cs @@ -343,7 +343,9 @@ namespace Torch Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}"); 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().LoadPlugins(); Managers.Attach(); _init = true; @@ -357,95 +359,15 @@ namespace Torch public virtual void Dispose() { 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 - #region VRage Instance Init/Destroy - -#pragma warning disable 649 - [ReflectedGetter(Name = "m_plugins", Type = typeof(MyPlugins))] - private static readonly Func> _getVRagePluginList; -#pragma warning restore 649 - - protected SpaceEngineersGame GameInstance { get; private set; } - - /// - /// Sets up the VRage instance. - /// Any flags (ie ) must be set before this is called. - /// - protected virtual void InitVRageInstance() - { - bool dedicated = Sandbox.Engine.Platform.Game.IsDedicated; - Environment.SetEnvironmentVariable("SteamAppId", SteamAppId.ToString()); - MyServiceManager.Instance.AddService(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); - } + private VRageGame _game; /// /// Called after the basic game information is filled, but before the game is created. @@ -453,23 +375,7 @@ namespace Torch protected virtual void TweakGameSettings() { } - - /// - /// Tears down the VRage instance - /// - protected virtual void DisposeVRageInstance() - { - GameInstance.Dispose(); - GameInstance = null; - - MyGameService.ShutDown(); - - _getVRagePluginList().Remove(this); - - MyInitializer.InvokeAfterRun(); - } - - #endregion + /// public virtual Task Save(long callerId) @@ -480,21 +386,17 @@ namespace Torch /// public virtual void Start() { - if (MySandboxGame.FatalErrorDuringInit) - { - Log.Warn($"Failed to start sandbox game: fatal error during init"); - return; - } - GameInstance.Run(); + _game.SignalStart(); + if (!_game.WaitFor(VRageGame.GameState.Running, TimeSpan.FromSeconds(15))) + Log.Warn("Failed to wait for the game to be started"); } /// public virtual void Stop() { - if (IsOnGameThread()) - MySandboxGame.Static.Exit(); - else - InvokeBlocking(MySandboxGame.Static.Exit); + _game.SignalStop(); + if (!_game.WaitFor(VRageGame.GameState.Stopped, TimeSpan.FromSeconds(15))) + Log.Warn("Failed to wait for the game to be stopped"); } /// diff --git a/Torch/VRageGame.cs b/Torch/VRageGame.cs new file mode 100644 index 0000000..e43b8ee --- /dev/null +++ b/Torch/VRageGame.cs @@ -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> _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(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(); + } + + /// + /// Signals the game to stop itself. + /// + public void SignalStop() + { + _startGame = false; + _game.Invoke(DoStop, $"{nameof(VRageGame)}::{nameof(SignalStop)}"); + } + + /// + /// Signals the game to start itself + /// + public void SignalStart() + { + _startGame = true; + _commandChanged.Set(); + } + + /// + /// Signals the game to destroy itself + /// + public void SignalDestroy() + { + _destroyGame = true; + SignalStop(); + _commandChanged.Set(); + } + + /// + /// Waits for the game to transition to the given state + /// + /// State to transition to + /// Timeout + /// + 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; + } + } +} \ No newline at end of file