Fix start/stop buttons, improve DS init order, add console tab

This commit is contained in:
John Gross
2018-01-21 16:44:10 -08:00
parent 2cb921087f
commit 714824df97
10 changed files with 320 additions and 138 deletions

View File

@@ -9,6 +9,7 @@ using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using NLog;
using Torch.Utils;
@@ -84,27 +85,21 @@ quit";
public void Run()
{
_server = new TorchServer(_config);
try
{
var initTask = Task.Run(() => { _server.Init(); });
_server.Init();
if (!_config.NoGui)
{
_server.Init();
if (!_config.NoGui)
{
var ui = new TorchUI(_server);
if (_config.Autostart)
_server.Start();
ui.ShowDialog();
}
else
_server.Start();
if (_config.Autostart)
Task.Run(() => _server.Start());
new TorchUI(_server).ShowDialog();
}
else
{
initTask.Wait();
_server.Start();
}
}
@@ -116,101 +111,105 @@ quit";
}
}
private TorchConfig InitConfig()
private TorchConfig InitConfig()
{
var configName = "Torch.cfg";
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
if (File.Exists(configName))
{
var configName = "Torch.cfg";
var configPath = Path.Combine(Directory.GetCurrentDirectory(), configName);
if (File.Exists(configName))
{
Log.Info($"Loading config {configPath}");
return TorchConfig.LoadFrom(configPath);
}
else
{
Log.Info($"Generating default config at {configPath}");
var config = new TorchConfig {InstancePath = Path.GetFullPath("Instance")};
config.Save(configPath);
return config;
}
Log.Info($"Loading config {configPath}");
return TorchConfig.LoadFrom(configPath);
}
private static void RunSteamCmd()
else
{
var log = LogManager.GetLogger("SteamCMD");
if (!Directory.Exists(STEAMCMD_DIR))
{
Directory.CreateDirectory(STEAMCMD_DIR);
}
if (!File.Exists(RUNSCRIPT_PATH))
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
if (!File.Exists(STEAMCMD_PATH))
{
try
{
log.Info("Downloading SteamCMD.");
using (var client = new WebClient())
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
File.Delete(STEAMCMD_ZIP);
log.Info("SteamCMD downloaded successfully!");
}
catch
{
log.Error("Failed to download SteamCMD, unable to update the DS.");
return;
}
}
log.Info("Checking for DS updates.");
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
{
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
UseShellExecute = false,
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.ASCII
};
var cmd = Process.Start(steamCmdProc);
// ReSharper disable once PossibleNullReferenceException
while (!cmd.HasExited)
{
log.Info(cmd.StandardOutput.ReadLine());
Thread.Sleep(100);
}
}
private void LogException(Exception ex)
{
if (ex.InnerException != null)
{
LogException(ex.InnerException);
}
Log.Fatal(ex);
if (ex is ReflectionTypeLoadException exti)
foreach (Exception exl in exti.LoaderExceptions)
LogException(exl);
}
private void HandleException(object sender, UnhandledExceptionEventArgs e)
{
var ex = (Exception)e.ExceptionObject;
LogException(ex);
Console.WriteLine("Exiting in 5 seconds.");
Thread.Sleep(5000);
LogManager.Flush();
if (_config.RestartOnCrash)
{
var exe = typeof(Program).Assembly.Location;
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
Process.Start(exe, _config.ToString());
}
Process.GetCurrentProcess().Kill();
Log.Info($"Generating default config at {configPath}");
var config = new TorchConfig {InstancePath = Path.GetFullPath("Instance")};
config.Save(configPath);
return config;
}
}
private static void RunSteamCmd()
{
var log = LogManager.GetLogger("SteamCMD");
if (!Directory.Exists(STEAMCMD_DIR))
{
Directory.CreateDirectory(STEAMCMD_DIR);
}
if (!File.Exists(RUNSCRIPT_PATH))
File.WriteAllText(RUNSCRIPT_PATH, RUNSCRIPT);
if (!File.Exists(STEAMCMD_PATH))
{
try
{
log.Info("Downloading SteamCMD.");
using (var client = new WebClient())
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
File.Delete(STEAMCMD_ZIP);
log.Info("SteamCMD downloaded successfully!");
}
catch
{
log.Error("Failed to download SteamCMD, unable to update the DS.");
return;
}
}
log.Info("Checking for DS updates.");
var steamCmdProc = new ProcessStartInfo(STEAMCMD_PATH, "+runscript runscript.txt")
{
WorkingDirectory = Path.Combine(Directory.GetCurrentDirectory(), STEAMCMD_DIR),
UseShellExecute = false,
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.ASCII
};
var cmd = Process.Start(steamCmdProc);
// ReSharper disable once PossibleNullReferenceException
while (!cmd.HasExited)
{
log.Info(cmd.StandardOutput.ReadLine());
Thread.Sleep(100);
}
}
private void LogException(Exception ex)
{
if (ex.InnerException != null)
{
LogException(ex.InnerException);
}
Log.Fatal(ex);
if (ex is ReflectionTypeLoadException exti)
foreach (Exception exl in exti.LoaderExceptions)
LogException(exl);
}
private void HandleException(object sender, UnhandledExceptionEventArgs e)
{
var ex = (Exception)e.ExceptionObject;
LogException(ex);
LogManager.Flush();
if (_config.RestartOnCrash)
{
Console.WriteLine("Restarting in 5 seconds.");
Thread.Sleep(5000);
var exe = typeof(Program).Assembly.Location;
_config.WaitForPID = Process.GetCurrentProcess().Id.ToString();
Process.Start(exe, _config.ToString());
}
else
{
MessageBox.Show("Torch encountered a fatal error and needs to close. Please check the logs for details.");
}
Process.GetCurrentProcess().Kill();
}
}
}

View File

@@ -10,13 +10,18 @@ using Havok;
using NLog;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils;
using Sandbox.Game;
using Sandbox.Game.Gui;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Server.ViewModels;
using VRage;
using VRage.FileSystem;
using VRage.Game;
using VRage.Game.ObjectBuilder;
using VRage.ObjectBuilders;
using VRage.Plugins;
namespace Torch.Server.Managers
{
@@ -37,6 +42,8 @@ namespace Torch.Server.Managers
public void LoadInstance(string path, bool validate = true)
{
Log.Info($"Loading instance {path}");
if (validate)
ValidateInstance(path);
@@ -56,6 +63,7 @@ namespace Torch.Server.Managers
config.Load(configPath);
DedicatedConfig = new ConfigDedicatedViewModel(config);
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves"));
foreach (var f in worldFolders)

View File

@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Torch.Server
{
public class MultiTextWriter : TextWriter
{
private IEnumerable<TextWriter> writers;
public MultiTextWriter(IEnumerable<TextWriter> writers)
{
this.writers = writers.ToList();
}
public MultiTextWriter(params TextWriter[] writers)
{
this.writers = writers;
}
public override void Write(char value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Write(string value)
{
foreach (var writer in writers)
writer.Write(value);
}
public override void Flush()
{
foreach (var writer in writers)
writer.Flush();
}
public override void Close()
{
foreach (var writer in writers)
writer.Close();
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace Torch.Server
{
public class RichTextBoxWriter : TextWriter
{
private RichTextBox textbox;
private StringBuilder _cache = new StringBuilder();
public RichTextBoxWriter(RichTextBox textbox)
{
this.textbox = textbox;
textbox.Document.Background = new SolidColorBrush(UnpackColor(Console.BackgroundColor));
textbox.Document.Blocks.Clear();
textbox.Document.Blocks.Add(new Paragraph {LineHeight = 12});
}
public override void Write(char value)
{
if (value == '\r')
return;
_cache.Append(value);
if (value == '\n')
{
var str = _cache.ToString();
_cache.Clear();
var brush = _brushes[Console.ForegroundColor];
textbox.Dispatcher.BeginInvoke(() =>
{
var p = (Paragraph)textbox.Document.Blocks.FirstBlock;
p.Inlines.Add(new Run(str) { Foreground = brush });
textbox.ScrollToEnd();
});
}
}
public override void Write(string value)
{
var brush = _brushes[Console.ForegroundColor];
textbox.Dispatcher.BeginInvoke(() =>
{
var p = (Paragraph)textbox.Document.Blocks.FirstBlock;
p.Inlines.Add(new Run(value) { Foreground = brush });
textbox.ScrollToEnd();
});
}
public override Encoding Encoding
{
get { return Encoding.ASCII; }
}
static RichTextBoxWriter()
{
foreach (var value in (ConsoleColor[])Enum.GetValues(typeof(ConsoleColor)))
{
_brushes.Add(value, new SolidColorBrush(UnpackColor(value)));
}
}
private static Dictionary<ConsoleColor, SolidColorBrush> _brushes = new Dictionary<ConsoleColor, SolidColorBrush>();
private static Color UnpackColor(ConsoleColor color)
{
var colorByte = (byte)color;
var isBright = (colorByte & 0b1000) >> 3 > 0;
var brightness = isBright ? (byte)255 : (byte)128;
var red = (colorByte & 0b0100) >> 2;
var green = (colorByte & 0b0010) >> 1;
var blue = (colorByte & 0b0001);
return new Color
{
R = (byte)(brightness * red),
G = (byte)(brightness * green),
B = (byte)(brightness * blue),
A = 255
};
}
}
}

View File

@@ -227,6 +227,8 @@
</Compile>
<Compile Include="Views\Converters\BooleanAndConverter.cs" />
<Compile Include="Views\Converters\ListConverter.cs" />
<Compile Include="MultiTextWriter.cs" />
<Compile Include="RichTextBoxWriter.cs" />
<Compile Include="Views\ValidationRules\ListConverterValidationRule.cs" />
<Compile Include="Views\Entities\EntityControlHost.xaml.cs">
<DependentUpon>EntityControlHost.xaml</DependentUpon>

View File

@@ -70,6 +70,11 @@ namespace Torch.Server
}
}
private bool _canRun;
public bool CanRun { get => _canRun; set => SetValue(ref _canRun, value); }
private bool _hasRun;
/// <inheritdoc />
public InstanceManager DedicatedInstance { get; }
@@ -107,12 +112,13 @@ namespace Torch.Server
/// <inheritdoc />
public override void Init()
{
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
Sandbox.Engine.Platform.Game.IsDedicated = true;
base.Init();
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
Managers.GetManager<ITorchSessionManager>().SessionStateChanged += OnSessionStateChanged;
GetManager<InstanceManager>().LoadInstance(Config.InstancePath);
CanRun = true;
}
private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState)
@@ -129,8 +135,17 @@ namespace Torch.Server
{
if (State != ServerState.Stopped)
return;
if (_hasRun)
{
Restart();
return;
}
State = ServerState.Starting;
IsRunning = true;
CanRun = false;
_hasRun = true;
Log.Info("Starting server.");
MySandboxGame.ConfigDedicated = DedicatedInstance.DedicatedConfig.Model;
@@ -150,6 +165,7 @@ namespace Torch.Server
State = ServerState.Stopped;
IsRunning = false;
CanRun = true;
}
/// <summary>
@@ -157,12 +173,17 @@ namespace Torch.Server
/// </summary>
public override void Restart()
{
Save().ContinueWith((task, torchO) =>
if (IsRunning)
Save().ContinueWith(DoRestart, this, TaskContinuationOptions.RunContinuationsAsynchronously);
else
DoRestart(null, this);
void DoRestart(Task<GameSaveResult> task, object torch0)
{
var torch = (TorchServer) torchO;
var torch = (TorchServer)torch0;
torch.Stop();
// TODO clone this
var config = (TorchConfig) torch.Config;
var config = (TorchConfig)torch.Config;
LogManager.Flush();
string exe = Assembly.GetExecutingAssembly().Location;
@@ -172,7 +193,7 @@ namespace Torch.Server
Process.Start(exe, config.ToString());
Process.GetCurrentProcess().Kill();
}, this, TaskContinuationOptions.RunContinuationsAsynchronously);
}
}
/// <inheritdoc />

View File

@@ -28,22 +28,18 @@
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File"/>
<MenuItem Header="Tools"/>
</Menu>
<StackPanel Grid.Row="1" Margin="5,5,5,5" Orientation="Horizontal">
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0"
HorizontalAlignment="Left" Click="BtnStart_Click">
HorizontalAlignment="Left" Click="BtnStart_Click" Background="LightGreen">
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource BooleanAndConverter}">
<Binding ElementName="MainWindow" Path="DataContext.IsRunning" Converter="{StaticResource InverseBool}"/>
<Binding ElementName="MainWindow" Path="DataContext.CanRun"/>
<Binding ElementName="ConfigControl" Path="ConfigValid"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
<Button x:Name="BtnStop" Content="Stop" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left"
Click="BtnStop_Click" IsEnabled="{Binding IsRunning}" />
Click="BtnStop_Click" IsEnabled="{Binding IsRunning}" Background="IndianRed"/>
<Label>
<Label.Content>
<TextBlock Text="{Binding State, StringFormat=Status: {0}}"></TextBlock>
@@ -60,7 +56,10 @@
</Label.Content>
</Label>
</StackPanel>
<TabControl Grid.Row="2" Height="Auto" x:Name="TabControl" Margin="5,0,5,5">
<TabControl Grid.Row="2" Height="Auto" x:Name="TabControl" Margin="5,10,5,5">
<TabItem Header="Console">
<RichTextBox x:Name="ConsoleText" VerticalScrollBarVisibility="Visible" FontFamily="Consolas"/>
</TabItem>
<TabItem Header="Configuration">
<Grid IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}">
<Grid.RowDefinitions>

View File

@@ -1,25 +1,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Sandbox;
using Torch.API;
using Torch.Server.Managers;
using MessageBoxResult = System.Windows.MessageBoxResult;
using Timer = System.Timers.Timer;
namespace Torch.Server
@@ -49,6 +45,13 @@ namespace Torch.Server
PlayerList.BindServer(server);
Plugins.BindServer(server);
LoadConfig((TorchConfig)server.Config);
AttachConsole();
}
private void AttachConsole()
{
Console.SetOut(new MultiTextWriter(new RichTextBoxWriter(ConsoleText), Console.Out));
}
public void LoadConfig(TorchConfig config)
@@ -65,12 +68,15 @@ namespace Torch.Server
private void BtnStart_Click(object sender, RoutedEventArgs e)
{
_server.Start();
Task.Run(() => _server.Start());
}
private void BtnStop_Click(object sender, RoutedEventArgs e)
{
_server.Stop();
var result = MessageBox.Show("Are you sure you want to stop the server?", "Stop Server", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
_server.Invoke(() => _server.Stop());
}
protected override void OnClosing(CancelEventArgs e)
@@ -82,6 +88,8 @@ namespace Torch.Server
if (_server?.State == ServerState.Running)
_server.Stop();
Environment.Exit(0);
}
private void BtnRestart_Click(object sender, RoutedEventArgs e)

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -26,6 +27,7 @@ using VRage;
using VRage.Audio;
using VRage.FileSystem;
using VRage.Game;
using VRage.Game.ObjectBuilder;
using VRage.Game.SessionComponents;
using VRage.GameServices;
using VRage.Network;
@@ -200,10 +202,16 @@ namespace Torch
MyRenderProxy.GetRenderProfiler().InitMemoryHack("MainEntryPoint");
}
// var layers = _layerSettings();
// layers[layers.Length - 1].Radius *= 4;
_game = new SpaceEngineersGame(_runArgs);
// Loads object builder serializers. Intuitive, right?
_log.Info("Setting up serializers");
MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly);
if (MyPerGameSettings.GameModBaseObjBuildersAssembly != null)
MyPlugins.RegisterBaseGameObjectBuildersAssemblyFile(MyPerGameSettings.GameModBaseObjBuildersAssembly);
MyPlugins.RegisterGameObjectBuildersAssemblyFile(MyPerGameSettings.GameModObjBuildersAssembly);
MyPlugins.RegisterSandboxAssemblyFile(MyPerGameSettings.SandboxAssembly);
MyPlugins.RegisterSandboxGameAssemblyFile(MyPerGameSettings.SandboxGameAssembly);
//typeof(MySandboxGame).GetMethod("Preallocate", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);
MyGlobalTypeMetadata.Static.Init(false);
}
private void Destroy()
@@ -220,6 +228,8 @@ namespace Torch
private void DoStart()
{
_game = new SpaceEngineersGame(_runArgs);
if (MySandboxGame.FatalErrorDuringInit)
{
_log.Warn("Failed to start sandbox game: fatal error during init");

View File

@@ -52,13 +52,12 @@ namespace Torch
/// <summary>
/// Assign a value to the given field and raise PropertyChanged for the caller.
/// </summary>
protected virtual void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propName = "")
protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propName = "")
{
if (backingField != null && backingField.Equals(value))
if (EqualityComparer<T>.Default.Equals(field, value))
return;
backingField = value;
// ReSharper disable once ExplicitCallerInfoArgument
field = value;
OnPropertyChanged(propName);
}
@@ -71,7 +70,6 @@ namespace Torch
throw new ArgumentNullException(nameof(setter));
setter.Invoke(value);
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propName);
}
@@ -80,9 +78,8 @@ namespace Torch
/// </summary>
public void RefreshModel()
{
foreach (var propName in GetType().GetProperties().Select(x => x.Name))
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propName);
foreach (var property in GetType().GetProperties())
OnPropertyChanged(property.Name);
}
}
}