Refactor instance management, assorted bugfixes/tweaks

This commit is contained in:
John Gross
2017-07-22 23:11:16 -07:00
parent 3ece4baba6
commit 1fcfe6fb5f
18 changed files with 238 additions and 257 deletions

View File

@@ -4,44 +4,21 @@ namespace Torch
{ {
public interface ITorchConfig public interface ITorchConfig
{ {
/// <summary> bool Autostart { get; set; }
/// (server) Name of the instance. bool ForceUpdate { get; set; }
/// </summary>
string InstanceName { get; set; }
/// <summary>
/// (server) Dedicated instance path.
/// </summary>
string InstancePath { get; set; }
/// <summary>
/// Enable automatic Torch updates.
/// </summary>
bool GetTorchUpdates { get; set; }
/// <summary>
/// Enable automatic Torch updates.
/// </summary>
bool GetPluginUpdates { get; set; } bool GetPluginUpdates { get; set; }
bool GetTorchUpdates { get; set; }
/// <summary> string InstanceName { get; set; }
/// Restart Torch automatically if it crashes. string InstancePath { get; set; }
/// </summary> bool NoGui { get; set; }
bool NoUpdate { get; set; }
List<string> Plugins { get; set; }
bool RestartOnCrash { get; set; } bool RestartOnCrash { get; set; }
bool ShouldUpdatePlugins { get; }
/// <summary> bool ShouldUpdateTorch { get; }
/// Time-out in seconds for the Torch watchdog (to detect a hung session).
/// </summary>
int TickTimeout { get; set; } int TickTimeout { get; set; }
string WaitForPID { get; set; }
/// <summary>
/// A list of plugins that should be installed.
/// </summary>
List<string> Plugins { get; }
/// <summary>
/// Saves the config.
/// </summary>
bool Save(string path = null); bool Save(string path = null);
} }
} }

View File

@@ -158,12 +158,12 @@
<ItemGroup> <ItemGroup>
<Compile Include="ConnectionState.cs" /> <Compile Include="ConnectionState.cs" />
<Compile Include="IChatMessage.cs" /> <Compile Include="IChatMessage.cs" />
<Compile Include="ITorchConfig.cs" />
<Compile Include="Managers\IManager.cs" /> <Compile Include="Managers\IManager.cs" />
<Compile Include="Managers\IMultiplayerManager.cs" /> <Compile Include="Managers\IMultiplayerManager.cs" />
<Compile Include="IPlayer.cs" /> <Compile Include="IPlayer.cs" />
<Compile Include="Managers\INetworkManager.cs" /> <Compile Include="Managers\INetworkManager.cs" />
<Compile Include="Managers\IPluginManager.cs" /> <Compile Include="Managers\IPluginManager.cs" />
<Compile Include="ITorchConfig.cs" />
<Compile Include="Plugins\ITorchPlugin.cs" /> <Compile Include="Plugins\ITorchPlugin.cs" />
<Compile Include="IServerControls.cs" /> <Compile Include="IServerControls.cs" />
<Compile Include="ITorchBase.cs" /> <Compile Include="ITorchBase.cs" />

View File

@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("1.0.198.562")] [assembly: AssemblyVersion("1.0.203.595")]
[assembly: AssemblyFileVersion("1.0.198.562")] [assembly: AssemblyFileVersion("1.0.203.595")]

View File

@@ -1,64 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Utils;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Server.ViewModels;
using VRage.Game;
namespace Torch.Server.Managers
{
//TODO
public class ConfigManager : Manager
{
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
public TorchConfig TorchConfig { get; set; }
public ConfigManager(ITorchBase torchInstance) : base(torchInstance)
{
}
/// <inheritdoc />
public override void Init()
{
LoadInstance(Torch.Config.InstancePath);
}
public void LoadInstance(string path)
{
if (!Directory.Exists(path))
throw new FileNotFoundException($"Instance directory not found at '{path}'");
var configPath = Path.Combine(path, CONFIG_NAME);
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
config.Load();
DedicatedConfig = new ConfigDedicatedViewModel(config);
}
/// <summary>
/// Creates a skeleton of a DS instance folder at the given directory.
/// </summary>
/// <param name="path"></param>
public void CreateInstance(string path)
{
if (Directory.Exists(path))
return;
Directory.CreateDirectory(path);
var savesPath = Path.Combine(path, "Saves");
Directory.CreateDirectory(savesPath);
var modsPath = Path.Combine(path, "Mods");
Directory.CreateDirectory(modsPath);
var configPath = Path.Combine(path, "SpaceEngineers-Dedicated.cfg");
new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath).Save();
LoadInstance(path);
}
}
}

View File

@@ -0,0 +1,158 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Havok;
using NLog;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils;
using Torch.API;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Server.ViewModels;
using VRage.FileSystem;
using VRage.Game;
using VRage.ObjectBuilders;
namespace Torch.Server.Managers
{
public class InstanceManager : Manager
{
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
public ConfigDedicatedViewModel DedicatedConfig { get; set; }
private static readonly Logger Log = LogManager.GetLogger(nameof(InstanceManager));
public InstanceManager(ITorchBase torchInstance) : base(torchInstance)
{
}
/// <inheritdoc />
public override void Init()
{
LoadInstance(Torch.Config.InstancePath);
}
public void LoadInstance(string path, bool validate = true)
{
if (validate)
ValidateInstance(path);
MyFileSystem.Reset();
MyFileSystem.ExePath = Path.Combine(Torch.GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
MyFileSystem.Init("Content", path);
var configPath = Path.Combine(path, CONFIG_NAME);
if (!File.Exists(configPath))
{
Log.Error($"Failed to load dedicated config at {path}");
return;
}
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
config.Load(configPath);
DedicatedConfig = new ConfigDedicatedViewModel(config);
var worldFolders = Directory.EnumerateDirectories(Path.Combine(Torch.Config.InstancePath, "Saves"));
foreach (var f in worldFolders)
DedicatedConfig.WorldPaths.Add(f);
if (DedicatedConfig.WorldPaths.Count == 0)
{
Log.Warn($"No worlds found in the current instance {path}.");
return;
}
/*
if (string.IsNullOrEmpty(DedicatedConfig.LoadWorld))
{
Log.Warn("No world specified, importing first available world.");
SelectWorld(DedicatedConfig.WorldPaths[0], false);
}*/
}
public void SelectWorld(string worldPath, bool modsOnly = true)
{
DedicatedConfig.LoadWorld = worldPath;
LoadWorldMods(modsOnly);
}
private void LoadWorldMods(bool modsOnly = true)
{
if (DedicatedConfig.LoadWorld == null)
return;
var sandboxPath = Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc");
if (!File.Exists(sandboxPath))
return;
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
if (checkpoint == null)
{
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
return;
}
var sb = new StringBuilder();
foreach (var mod in checkpoint.Mods)
sb.AppendLine(mod.PublishedFileId.ToString());
DedicatedConfig.Mods = sb.ToString();
Log.Info("Loaded mod list from world");
if (!modsOnly)
DedicatedConfig.SessionSettings = new SessionSettingsViewModel(checkpoint.Settings);
}
public void SaveConfig()
{
DedicatedConfig.Model.Save();
Log.Info("Saved dedicated config.");
try
{
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(DedicatedConfig.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
if (checkpoint == null)
{
Log.Error($"Failed to load {DedicatedConfig.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
return;
}
checkpoint.Settings = DedicatedConfig.SessionSettings;
checkpoint.Mods.Clear();
foreach (var modId in DedicatedConfig.Model.Mods)
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
MyLocalCache.SaveCheckpoint(checkpoint, DedicatedConfig.LoadWorld);
Log.Info("Saved world config.");
}
catch (Exception e)
{
Log.Error("Failed to write sandbox config, changes will not appear on server");
Log.Error(e);
}
}
/// <summary>
/// Ensures that the given path is a valid server instance.
/// </summary>
private void ValidateInstance(string path)
{
Directory.CreateDirectory(Path.Combine(path, "Saves"));
Directory.CreateDirectory(Path.Combine(path, "Mods"));
var configPath = Path.Combine(path, CONFIG_NAME);
if (File.Exists(configPath))
return;
var config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(configPath);
config.Save(configPath);
}
}
}

View File

@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("1.1.198.562")] [assembly: AssemblyVersion("1.1.203.596")]
[assembly: AssemblyFileVersion("1.1.198.562")] [assembly: AssemblyFileVersion("1.1.203.596")]

View File

@@ -185,7 +185,7 @@
<Reference Include="PresentationFramework" /> <Reference Include="PresentationFramework" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Managers\ConfigManager.cs" /> <Compile Include="Managers\InstanceManager.cs" />
<Compile Include="NativeMethods.cs" /> <Compile Include="NativeMethods.cs" />
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
@@ -366,7 +366,9 @@
<ItemGroup /> <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>copy "$(SolutionDir)NLog.config" "$(TargetDir)"</PostBuildEvent> <PostBuildEvent>cd "$(TargetDir)"
copy "$(SolutionDir)NLog.config" "$(TargetDir)"
"Torch Server Release.bat"</PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -12,17 +12,24 @@ namespace Torch.Server
{ {
private static Logger _log = LogManager.GetLogger("Config"); private static Logger _log = LogManager.GetLogger("Config");
public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate;
public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate;
/// <inheritdoc />
[Arg("instancename", "The name of the Torch instance.")]
public string InstanceName { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Arg("instancepath", "Server data folder where saves and mods are stored.")] [Arg("instancepath", "Server data folder where saves and mods are stored.")]
public string InstancePath { get; set; } public string InstancePath { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")] [JsonIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")]
public bool NoUpdate { get => false; set => GetTorchUpdates = GetPluginUpdates = !value; } public bool NoUpdate { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore, Arg("update", "Manually check for and install updates.")] [JsonIgnore, Arg("forceupdate", "Manually check for and install updates.")]
public bool Update { get; set; } public bool ForceUpdate { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Arg("autostart", "Start the server immediately.")] [Arg("autostart", "Start the server immediately.")]
@@ -40,10 +47,6 @@ namespace Torch.Server
[JsonIgnore, Arg("waitforpid", "Makes Torch wait for another process to exit.")] [JsonIgnore, Arg("waitforpid", "Makes Torch wait for another process to exit.")]
public string WaitForPID { get; set; } public string WaitForPID { get; set; }
/// <inheritdoc />
[Arg("instancename", "The name of the Torch instance.")]
public string InstanceName { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public bool GetTorchUpdates { get; set; } = true; public bool GetTorchUpdates { get; set; } = true;
@@ -54,7 +57,7 @@ namespace Torch.Server
public int TickTimeout { get; set; } = 60; public int TickTimeout { get; set; } = 60;
/// <inheritdoc /> /// <inheritdoc />
public List<string> Plugins { get; set; } = new List<string> {"TorchAPI/Concealment", "TorchAPI/Essentials"}; public List<string> Plugins { get; set; } = new List<string>();
internal Point WindowSize { get; set; } = new Point(800, 600); internal Point WindowSize { get; set; } = new Point(800, 600);
internal Point WindowPosition { get; set; } = new Point(); internal Point WindowPosition { get; set; } = new Point();
@@ -73,10 +76,14 @@ namespace Torch.Server
{ {
try try
{ {
var config = JsonConvert.DeserializeObject<TorchConfig>(File.ReadAllText(path)); var ser = new XmlSerializer(typeof(TorchConfig));
using (var f = File.OpenRead(path))
{
var config = (TorchConfig)ser.Deserialize(f);
config._path = path; config._path = path;
return config; return config;
} }
}
catch (Exception e) catch (Exception e)
{ {
_log.Error(e); _log.Error(e);
@@ -93,8 +100,9 @@ namespace Torch.Server
try try
{ {
var str = JsonConvert.SerializeObject(this); var ser = new XmlSerializer(typeof(TorchConfig));
File.WriteAllText(path, str); using (var f = File.Create(path))
ser.Serialize(f, this);
return true; return true;
} }
catch (Exception e) catch (Exception e)

View File

@@ -41,6 +41,7 @@ namespace Torch.Server
public Thread GameThread { get; private set; } public Thread GameThread { get; private set; }
public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } } public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } } public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } }
public InstanceManager DedicatedInstance { get; }
/// <inheritdoc /> /// <inheritdoc />
public string InstanceName => Config?.InstanceName; public string InstanceName => Config?.InstanceName;
/// <inheritdoc /> /// <inheritdoc />
@@ -56,7 +57,8 @@ namespace Torch.Server
public TorchServer(TorchConfig config = null) public TorchServer(TorchConfig config = null)
{ {
AddManager(new ConfigManager(this)); DedicatedInstance = new InstanceManager(this);
AddManager(DedicatedInstance);
Config = config ?? new TorchConfig(); Config = config ?? new TorchConfig();
MyFakes.ENABLE_INFINARIO = false; MyFakes.ENABLE_INFINARIO = false;
} }
@@ -64,9 +66,8 @@ namespace Torch.Server
/// <inheritdoc /> /// <inheritdoc />
public override void Init() public override void Init()
{ {
base.Init();
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'"); Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
base.Init();
MyPerGameSettings.SendLogToKeen = false; MyPerGameSettings.SendLogToKeen = false;
MyPerServerSettings.GameName = MyPerGameSettings.GameName; MyPerServerSettings.GameName = MyPerGameSettings.GameName;
@@ -78,7 +79,6 @@ namespace Torch.Server
MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion; MyFinalBuildConstants.APP_VERSION = MyPerGameSettings.BasicGameInfo.GameVersion;
MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly); MyObjectBuilderSerializer.RegisterFromAssembly(typeof(MyObjectBuilder_CheckpointSerializer).Assembly);
InvokeBeforeRun(); InvokeBeforeRun();
MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly); MyPlugins.RegisterGameAssemblyFile(MyPerGameSettings.GameModAssembly);
@@ -88,26 +88,11 @@ namespace Torch.Server
MyPlugins.Load(); MyPlugins.Load();
MyGlobalTypeMetadata.Static.Init(); MyGlobalTypeMetadata.Static.Init();
if (!Directory.Exists(Config.InstancePath))
GetManager<ConfigManager>().CreateInstance(Config.InstancePath);
Plugins.LoadPlugins(); Plugins.LoadPlugins();
} }
private void InvokeBeforeRun() private void InvokeBeforeRun()
{ {
var contentPath = "Content";
var privateContentPath = typeof(MyFileSystem).GetField("m_contentPath", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as string;
if (privateContentPath != null)
Log.Debug("MyFileSystem already initialized");
else
{
MyFileSystem.ExePath = Path.Combine(GetManager<FilesystemManager>().TorchDirectory, "DedicatedServer64");
MyFileSystem.Init(contentPath, InstancePath);
}
MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING); MySandboxGame.Log.Init("SpaceEngineers-Dedicated.log", MyFinalBuildConstants.APP_VERSION_STRING);
MySandboxGame.Log.WriteLine("Steam build: Always true"); MySandboxGame.Log.WriteLine("Steam build: Always true");
MySandboxGame.Log.WriteLine("Environment.ProcessorCount: " + MyEnvironment.ProcessorCount); MySandboxGame.Log.WriteLine("Environment.ProcessorCount: " + MyEnvironment.ProcessorCount);
@@ -142,6 +127,7 @@ namespace Torch.Server
if (State != ServerState.Stopped) if (State != ServerState.Stopped)
return; return;
DedicatedInstance.SaveConfig();
_uptime = Stopwatch.StartNew(); _uptime = Stopwatch.StartNew();
IsRunning = true; IsRunning = true;
GameThread = Thread.CurrentThread; GameThread = Thread.CurrentThread;
@@ -179,10 +165,10 @@ namespace Torch.Server
var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds)); var elapsed = TimeSpan.FromSeconds(Math.Floor(_uptime.Elapsed.TotalSeconds));
ElapsedPlayTime = elapsed; ElapsedPlayTime = elapsed;
if (_watchdog == null && Instance.Config.TickTimeout > 0) if (_watchdog == null && Config.TickTimeout > 0)
{ {
Log.Info("Starting server watchdog."); Log.Info("Starting server watchdog.");
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Instance.Config.TickTimeout)); _watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Config.TickTimeout));
} }
} }
@@ -195,7 +181,7 @@ namespace Torch.Server
var mainThread = MySandboxGame.Static.UpdateThread; var mainThread = MySandboxGame.Static.UpdateThread;
mainThread.Suspend(); mainThread.Suspend();
var stackTrace = new StackTrace(mainThread, true); var stackTrace = new StackTrace(mainThread, true);
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {Instance.Config.TickTimeout} seconds.\n{stackTrace}"); throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {((TorchServer)state).Config.TickTimeout} seconds.\n{stackTrace}");
} }
Log.Debug("Server watchdog responded"); Log.Debug("Server watchdog responded");

View File

@@ -15,6 +15,7 @@ namespace Torch.Server.ViewModels
{ {
private static readonly Logger Log = LogManager.GetLogger("Config"); private static readonly Logger Log = LogManager.GetLogger("Config");
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config; private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Model => _config;
public ConfigDedicatedViewModel() : this(new MyConfigDedicated<MyObjectBuilder_SessionSettings>("")) public ConfigDedicatedViewModel() : this(new MyConfigDedicated<MyObjectBuilder_SessionSettings>(""))
{ {
@@ -54,7 +55,8 @@ namespace Torch.Server.ViewModels
_config.Save(path); _config.Save(path);
} }
public SessionSettingsViewModel SessionSettings { get; } private SessionSettingsViewModel _sessionSettings;
public SessionSettingsViewModel SessionSettings { get => _sessionSettings; set { _sessionSettings = value; OnPropertyChanged(); } }
public ObservableList<string> WorldPaths { get; } = new ObservableList<string>(); public ObservableList<string> WorldPaths { get; } = new ObservableList<string>();
private string _administrators; private string _administrators;

View File

@@ -21,6 +21,7 @@ using NLog;
using Sandbox; using Sandbox;
using Sandbox.Engine.Networking; using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils; using Sandbox.Engine.Utils;
using Torch.Server.Managers;
using Torch.Server.ViewModels; using Torch.Server.ViewModels;
using Torch.Views; using Torch.Views;
using VRage; using VRage;
@@ -36,109 +37,29 @@ namespace Torch.Server.Views
/// </summary> /// </summary>
public partial class ConfigControl : UserControl public partial class ConfigControl : UserControl
{ {
private readonly Logger Log = LogManager.GetLogger("Config"); private InstanceManager _instanceManager;
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Config { get; set; }
private ConfigDedicatedViewModel _viewModel;
private string _configPath;
private TorchConfig _torchConfig;
public ConfigControl() public ConfigControl()
{ {
InitializeComponent(); InitializeComponent();
} _instanceManager = TorchBase.Instance.GetManager<InstanceManager>();
DataContext = _instanceManager.DedicatedConfig;
public void SaveConfig()
{
_viewModel.Save(_configPath);
Log.Info("Saved DS config.");
try
{
MyObjectBuilderSerializer.DeserializeXML(Path.Combine(Config.LoadWorld, "Sandbox.sbc"), out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
if (checkpoint == null)
{
Log.Error($"Failed to load {Config.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
return;
}
checkpoint.Settings = Config.SessionSettings;
checkpoint.Mods.Clear();
foreach (var modId in Config.Mods)
checkpoint.Mods.Add(new MyObjectBuilder_Checkpoint.ModItem(modId));
MyLocalCache.SaveCheckpoint(checkpoint, Config.LoadWorld);
Log.Info("Saved world config.");
}
catch (Exception e)
{
Log.Error("Failed to write sandbox config, changes will not appear on server");
Log.Error(e);
}
}
public void LoadDedicatedConfig(TorchConfig torchConfig)
{
_torchConfig = torchConfig;
DataContext = null;
MySandboxGame.Config = new MyConfig(MyPerServerSettings.GameNameSafe + ".cfg");
var path = Path.Combine(torchConfig.InstancePath, "SpaceEngineers-Dedicated.cfg");
if (!File.Exists(path))
{
Log.Error($"Failed to load dedicated config at {path}");
DataContext = null;
return;
}
Config = new MyConfigDedicated<MyObjectBuilder_SessionSettings>(path);
Config.Load(path);
_configPath = path;
_viewModel = new ConfigDedicatedViewModel(Config);
var worldFolders = Directory.EnumerateDirectories(Path.Combine(torchConfig.InstancePath, "Saves"));
foreach (var f in worldFolders)
_viewModel.WorldPaths.Add(f);
LoadWorldMods();
DataContext = _viewModel;
}
private void LoadWorldMods()
{
var sandboxPath = Path.Combine(Config.LoadWorld, "Sandbox.sbc");
if (!File.Exists(sandboxPath))
return;
MyObjectBuilderSerializer.DeserializeXML(sandboxPath, out MyObjectBuilder_Checkpoint checkpoint, out ulong sizeInBytes);
if (checkpoint == null)
{
Log.Error($"Failed to load {Config.LoadWorld}, checkpoint null ({sizeInBytes} bytes, instance {TorchBase.Instance.Config.InstancePath})");
return;
}
var sb = new StringBuilder();
foreach (var mod in checkpoint.Mods)
sb.AppendLine(mod.PublishedFileId.ToString());
_viewModel.Mods = sb.ToString();
Log.Info("Loaded mod list from world");
} }
private void Save_OnClick(object sender, RoutedEventArgs e) private void Save_OnClick(object sender, RoutedEventArgs e)
{ {
SaveConfig(); _instanceManager.SaveConfig();
} }
private void RemoveLimit_OnClick(object sender, RoutedEventArgs e) private void RemoveLimit_OnClick(object sender, RoutedEventArgs e)
{ {
var vm = (BlockLimitViewModel)((Button)sender).DataContext; var vm = (BlockLimitViewModel)((Button)sender).DataContext;
_viewModel.SessionSettings.BlockLimits.Remove(vm); _instanceManager.DedicatedConfig.SessionSettings.BlockLimits.Remove(vm);
} }
private void AddLimit_OnClick(object sender, RoutedEventArgs e) private void AddLimit_OnClick(object sender, RoutedEventArgs e)
{ {
_viewModel.SessionSettings.BlockLimits.Add(new BlockLimitViewModel(_viewModel.SessionSettings, "", 0)); _instanceManager.DedicatedConfig.SessionSettings.BlockLimits.Add(new BlockLimitViewModel(_instanceManager.DedicatedConfig.SessionSettings, "", 0));
} }
private void NewWorld_OnClick(object sender, RoutedEventArgs e) private void NewWorld_OnClick(object sender, RoutedEventArgs e)
@@ -151,8 +72,7 @@ namespace Torch.Server.Views
//The control doesn't update the binding before firing the event. //The control doesn't update the binding before firing the event.
if (e.AddedItems.Count > 0) if (e.AddedItems.Count > 0)
{ {
Config.LoadWorld = (string)e.AddedItems[0]; _instanceManager.SelectWorld((string)e.AddedItems[0]);
LoadWorldMods();
} }
} }
} }

View File

@@ -18,7 +18,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="5,5,5,5" Orientation="Horizontal"> <StackPanel Grid.Row="0" Margin="5,5,5,5" Orientation="Horizontal">
<Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0" <Button x:Name="BtnStart" Content="Start" Height="24" Width="75" Margin="5,0,5,0"
HorizontalAlignment="Left" Click="BtnStart_Click" IsDefault="True" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/> HorizontalAlignment="Left" Click="BtnStart_Click" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/>
<Button x:Name="BtnStop" Content="Stop" Height="24" Width="75" Margin="5,0,5,0" HorizontalAlignment="Left" <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}" />
<Label> <Label>
@@ -51,7 +51,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Instance Path: " Margin="3" /> <Label Grid.Column="0" Content="Instance Path: " Margin="3" />
<TextBox Grid.Column="1" x:Name="InstancePathBox" Margin="3" Height="20" <TextBox Grid.Column="1" x:Name="InstancePathBox" Margin="3" Height="20"
TextChanged="InstancePathBox_OnTextChanged" IsEnabled="False" /> LostKeyboardFocus="InstancePathBox_OnLostKeyboardFocus" />
</Grid> </Grid>
<views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/> <views:ConfigControl Grid.Row="1" x:Name="ConfigControl" Margin="3" DockPanel.Dock="Bottom" IsEnabled="{Binding IsRunning, Converter={StaticResource InverseBool}}"/>
</Grid> </Grid>

View File

@@ -19,6 +19,7 @@ using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using Sandbox; using Sandbox;
using Torch.API; using Torch.API;
using Torch.Server.Managers;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
namespace Torch.Server namespace Torch.Server
@@ -58,7 +59,6 @@ namespace Torch.Server
_config = config; _config = config;
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
ConfigControl.LoadDedicatedConfig(config);
InstancePathBox.Text = config.InstancePath; InstancePathBox.Text = config.InstancePath;
}); });
} }
@@ -66,7 +66,6 @@ namespace Torch.Server
private void BtnStart_Click(object sender, RoutedEventArgs e) private void BtnStart_Click(object sender, RoutedEventArgs e)
{ {
_config.Save(); _config.Save();
ConfigControl.SaveConfig();
new Thread(_server.Start).Start(); new Thread(_server.Start).Start();
} }
@@ -92,13 +91,15 @@ namespace Torch.Server
//MySandboxGame.Static.Invoke(MySandboxGame.ReloadDedicatedServerSession); use i //MySandboxGame.Static.Invoke(MySandboxGame.ReloadDedicatedServerSession); use i
} }
private void InstancePathBox_OnTextChanged(object sender, TextChangedEventArgs e) private void InstancePathBox_OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{ {
var name = ((TextBox)sender).Text; var name = ((TextBox)sender).Text;
_config.InstancePath = name; if (!Directory.Exists(name))
return;
LoadConfig(_config); _config.InstancePath = name;
_server.GetManager<InstanceManager>().LoadInstance(_config.InstancePath);
} }
} }
} }

View File

@@ -40,8 +40,7 @@ namespace Torch.Commands
} }
else else
{ {
var topNodeNames = commandManager.Commands.Root.Select(x => x.Key); Context.Respond($"Use the {commandManager.Prefix}longhelp command and check your Comms menu for a full list of commands.");
Context.Respond($"Top level commands: {string.Join(", ", topNodeNames)}");
} }
} }

View File

@@ -71,7 +71,7 @@ namespace Torch.Managers
} }
var manifest = PluginManifest.Load(manifestPath); var manifest = PluginManifest.Load(manifestPath);
toDownload.Remove(manifest.Repository); toDownload.RemoveAll(x => string.Compare(manifest.Repository, x, StringComparison.InvariantCultureIgnoreCase) == 0);
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest)); taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
} }
@@ -90,7 +90,7 @@ namespace Torch.Managers
_updateManager = Torch.GetManager<UpdateManager>(); _updateManager = Torch.GetManager<UpdateManager>();
var commands = Torch.GetManager<CommandManager>(); var commands = Torch.GetManager<CommandManager>();
if (Torch.Config.GetPluginUpdates) if (Torch.Config.ShouldUpdatePlugins)
DownloadPlugins(); DownloadPlugins();
else else
_log.Warn("Automatic plugin updates are disabled."); _log.Warn("Automatic plugin updates are disabled.");

View File

@@ -54,9 +54,10 @@ namespace Torch.Managers
var zip = latest.Assets.FirstOrDefault(x => x.Name.Contains(".zip")); var zip = latest.Assets.FirstOrDefault(x => x.Name.Contains(".zip"));
return new Tuple<Version, string>(new Version(latest.TagName ?? "0"), zip?.BrowserDownloadUrl); return new Tuple<Version, string>(new Version(latest.TagName ?? "0"), zip?.BrowserDownloadUrl);
} }
catch (Exception) catch (Exception e)
{ {
_log.Error($"An error occurred getting release information for '{owner}/{name}'"); _log.Error($"An error occurred getting release information for '{owner}/{name}'");
_log.Error(e);
return new Tuple<Version, string>(new Version(), null); return new Tuple<Version, string>(new Version(), null);
} }
} }

View File

@@ -6,12 +6,12 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using System.Xml.Serialization;
namespace Torch namespace Torch
{ {
/// <summary> /// <summary>
/// Simple class that manages saving <see cref="Persistent{T}.Data"/> to disk using JSON serialization. /// Simple class that manages saving <see cref="Persistent{T}.Data"/> to disk using XML serialization.
/// Can automatically save on changes by implementing <see cref="INotifyPropertyChanged"/> in the data class. /// Can automatically save on changes by implementing <see cref="INotifyPropertyChanged"/> in the data class.
/// </summary> /// </summary>
/// <typeparam name="T">Data class type</typeparam> /// <typeparam name="T">Data class type</typeparam>
@@ -19,7 +19,6 @@ namespace Torch
{ {
public string Path { get; set; } public string Path { get; set; }
public T Data { get; private set; } public T Data { get; private set; }
private Timer _saveTimer;
~Persistent() ~Persistent()
{ {
@@ -28,7 +27,6 @@ namespace Torch
public Persistent(string path, T data = default(T)) public Persistent(string path, T data = default(T))
{ {
_saveTimer = new Timer(Callback);
Path = path; Path = path;
Data = data; Data = data;
if (Data is INotifyPropertyChanged npc) if (Data is INotifyPropertyChanged npc)
@@ -36,11 +34,6 @@ namespace Torch
} }
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
_saveTimer.Change(5000, -1);
}
private void Callback(object state)
{ {
Save(); Save();
} }
@@ -50,11 +43,10 @@ namespace Torch
if (path == null) if (path == null)
path = Path; path = Path;
var ser = new XmlSerializer(typeof(T));
using (var f = File.Create(path)) using (var f = File.Create(path))
{ {
var writer = new StreamWriter(f); ser.Serialize(f, Data);
writer.Write(JsonConvert.SerializeObject(Data, Formatting.Indented));
writer.Flush();
} }
} }
@@ -64,10 +56,10 @@ namespace Torch
if (File.Exists(path)) if (File.Exists(path))
{ {
var ser = new XmlSerializer(typeof(T));
using (var f = File.OpenRead(path)) using (var f = File.OpenRead(path))
{ {
var reader = new StreamReader(f); config.Data = (T)ser.Deserialize(f);
config.Data = JsonConvert.DeserializeObject<T>(reader.ReadToEnd());
} }
} }
else if (saveIfNew) else if (saveIfNew)
@@ -84,7 +76,6 @@ namespace Torch
{ {
if (Data is INotifyPropertyChanged npc) if (Data is INotifyPropertyChanged npc)
npc.PropertyChanged -= OnPropertyChanged; npc.PropertyChanged -= OnPropertyChanged;
_saveTimer.Dispose();
Save(); Save();
} }
catch catch

View File

@@ -228,7 +228,7 @@ namespace Torch
TorchVersion = Assembly.GetEntryAssembly().GetName().Version; TorchVersion = Assembly.GetEntryAssembly().GetName().Version;
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", ".")); GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
var verInfo = $"Torch {TorchVersion}, SE {GameVersion}"; var verInfo = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}";
Console.Title = verInfo; Console.Title = verInfo;
#if DEBUG #if DEBUG
Log.Info("DEBUG"); Log.Info("DEBUG");