* Torch 1.0.182.329

- Improved logging, logs now to go the Logs folder and aren't deleted on start
    - Fixed chat tab not enabling with -autostart
    - Fixed player list
    - Watchdog time-out is now configurable in TorchConfig.xml
    - Fixed infinario log spam
    - Fixed crash when sending empty message from chat tab
    - Fixed permissions on Torch commands
    - Changed plugin StoragePath to the current instance path (per-instance configs)
This commit is contained in:
John Gross
2017-07-01 11:16:14 -07:00
parent 5e0f69e0e6
commit 79fe6a08ab
21 changed files with 154 additions and 52 deletions

View File

@@ -3,13 +3,13 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="logfile" layout="${longdate} [${level:uppercase=true}] ${logger}: ${message}" xsi:type="File" fileName="Torch.log" deleteOldFileOnStartup="true"/>
<target name="console" layout="${longdate} [${level:uppercase=true}] ${logger}: ${message}" xsi:type="ColoredConsole" />
<target xsi:type="File" name="main" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" fileName="Logs\Torch-${shortdate}.log" />
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
<target xsi:type="ColoredConsole" name="console" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logfile" />
<logger name="*" minlevel="Info" writeTo="console" />
<logger name="*" minlevel="Info" writeTo="main, console" />
<logger name="Chat" minlevel="Info" writeTo="chat" />
</rules>
</nlog>

View File

@@ -7,6 +7,7 @@
bool RedownloadPlugins { get; set; }
bool AutomaticUpdates { get; set; }
bool RestartOnCrash { get; set; }
int TickTimeout { get; set; }
bool Save(string path = null);
}

View File

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

View File

@@ -291,6 +291,8 @@ quit";
{
var ex = (Exception)e.ExceptionObject;
_log.Fatal(ex);
Console.WriteLine("Exiting in 5 seconds.");
Thread.Sleep(5000);
if (_restartOnCrash)
{
/* Throws an exception somehow and I'm too lazy to debug it.

View File

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

View File

@@ -15,15 +15,19 @@ namespace Torch.Server
public string InstanceName { get; set; }
#warning World Path not implemented
public string WorldPath { get; set; }
//public int Autosave { get; set; }
//public bool AutoRestart { get; set; }
//public bool LogChat { get; set; }
public bool AutomaticUpdates { get; set; } = true;
public bool RedownloadPlugins { get; set; }
public bool RestartOnCrash { get; set; }
/// <summary>
/// How long in seconds to wait before automatically resetting a frozen server.
/// </summary>
public int TickTimeout { get; set; } = 60;
/// <summary>
/// A list of plugins to install or update. TODO
/// </summary>
public List<string> Plugins { get; set; } = new List<string>();
public Point WindowSize { get; set; } = new Point(800, 600);
public Point WindowPosition { get; set; } = new Point();
internal Point WindowSize { get; set; } = new Point(800, 600);
internal Point WindowPosition { get; set; } = new Point();
[NonSerialized]
private string _path;

View File

@@ -11,6 +11,7 @@ using System.Runtime.CompilerServices;
using System.Security.Principal;
using System.Threading;
using Microsoft.Xml.Serialization.GeneratedAssembly;
using Sandbox.Engine.Analytics;
using Sandbox.Game.Multiplayer;
using Sandbox.ModAPI;
using SteamSDK;
@@ -35,9 +36,11 @@ namespace Torch.Server
public TimeSpan ElapsedPlayTime { get => _elapsedPlayTime; set { _elapsedPlayTime = value; OnPropertyChanged(); } }
public Thread GameThread { get; private set; }
public ServerState State { get => _state; private set { _state = value; OnPropertyChanged(); } }
public bool IsRunning { get => _isRunning; set { _isRunning = value; OnPropertyChanged(); } }
public string InstanceName => Config?.InstanceName;
public string InstancePath => Config?.InstancePath;
private bool _isRunning;
private ServerState _state;
private TimeSpan _elapsedPlayTime;
private float _simRatio;
@@ -47,6 +50,7 @@ namespace Torch.Server
public TorchServer(TorchConfig config = null)
{
Config = config ?? new TorchConfig();
MyFakes.ENABLE_INFINARIO = false;
}
public override void Init()
@@ -55,7 +59,6 @@ namespace Torch.Server
Log.Info($"Init server '{Config.InstanceName}' at '{Config.InstancePath}'");
MyFakes.ENABLE_INFINARIO = false;
MyPerGameSettings.SendLogToKeen = false;
MyPerServerSettings.GameName = MyPerGameSettings.GameName;
MyPerServerSettings.GameNameSafe = MyPerGameSettings.GameNameSafe;
@@ -171,10 +174,10 @@ namespace Torch.Server
SimulationRatio = Sync.ServerSimulationRatio;
ElapsedPlayTime = MySession.Static?.ElapsedPlayTime ?? default(TimeSpan);
if (_watchdog == null)
if (_watchdog == null && Instance.Config.TickTimeout > 0)
{
Log.Info("Starting server watchdog.");
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(30));
_watchdog = new Timer(CheckServerResponding, this, TimeSpan.Zero, TimeSpan.FromSeconds(Instance.Config.TickTimeout));
}
}
@@ -182,12 +185,12 @@ namespace Torch.Server
{
var mre = new ManualResetEvent(false);
((TorchServer)state).Invoke(() => mre.Set());
if (!mre.WaitOne(TimeSpan.FromSeconds(30)))
if (!mre.WaitOne(TimeSpan.FromSeconds(Instance.Config.TickTimeout)))
{
var mainThread = MySandboxGame.Static.UpdateThread;
mainThread.Suspend();
var stackTrace = new StackTrace(mainThread, true);
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least 30 seconds.\n{stackTrace}");
throw new TimeoutException($"Server watchdog detected that the server was frozen for at least {Instance.Config.TickTimeout} seconds.\n{stackTrace}");
}
Log.Debug("Server watchdog responded");

View File

@@ -14,9 +14,9 @@
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Value.Name}" FontWeight="Bold"/>
<TextBlock Text=" ("/>
<TextBlock Text="{Binding State}"/>
<TextBlock Text="{Binding Value.State}"/>
<TextBlock Text=")"/>
</WrapPanel>
</DataTemplate>

View File

@@ -21,6 +21,7 @@ using Sandbox.ModAPI;
using SteamSDK;
using Torch.API;
using Torch.Managers;
using Torch.ViewModels;
using VRage.Game.ModAPI;
namespace Torch.Server
@@ -45,20 +46,14 @@ namespace Torch.Server
private void KickButton_Click(object sender, RoutedEventArgs e)
{
var player = PlayerList.SelectedItem as IMyPlayer;
if (player != null)
{
_server.Multiplayer.KickPlayer(player.SteamUserId);
}
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
_server.Multiplayer.KickPlayer(player.Key);
}
private void BanButton_Click(object sender, RoutedEventArgs e)
{
var player = PlayerList.SelectedItem as IMyPlayer;
if (player != null)
{
_server.Multiplayer.BanPlayer(player.SteamUserId);
}
var player = (KeyValuePair<ulong, PlayerViewModel>) PlayerList.SelectedItem;
_server.Multiplayer.BanPlayer(player.Key);
}
}
}

View File

@@ -42,8 +42,8 @@
</TabItem>
<TabItem Header="Chat/Players">
<DockPanel>
<local:PlayerListControl x:Name="PlayerList" DockPanel.Dock="Right" Width="250" IsEnabled="False" />
<local:ChatControl x:Name="Chat" IsEnabled="False" />
<local:PlayerListControl x:Name="PlayerList" DockPanel.Dock="Right" Width="250" />
<local:ChatControl x:Name="Chat" />
</DockPanel>
</TabItem>
<TabItem Header="Entity Manager">

View File

@@ -65,8 +65,6 @@ namespace Torch.Server
private void BtnStart_Click(object sender, RoutedEventArgs e)
{
_config.Save();
Chat.IsEnabled = true;
PlayerList.IsEnabled = true;
((Button) sender).IsEnabled = false;
BtnStop.IsEnabled = true;
ConfigControl.SaveConfig();
@@ -76,8 +74,6 @@ namespace Torch.Server
private void BtnStop_Click(object sender, RoutedEventArgs e)
{
_config.Save();
Chat.IsEnabled = false;
PlayerList.IsEnabled = false;
((Button) sender).IsEnabled = false;
//HACK: Uncomment when restarting is possible.
//BtnStart.IsEnabled = true;

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace Torch.Collections
{
public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
/// <inheritdoc />
public new void Add(TKey key, TValue value)
{
base.Add(key, value);
var kv = new KeyValuePair<TKey, TValue>(key, value);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, kv));
}
/// <inheritdoc />
public new bool Remove(TKey key)
{
if (!ContainsKey(key))
return false;
var kv = new KeyValuePair<TKey, TValue>(key, this[key]);
base.Remove(key);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, kv));
return true;
}
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in collectionChanged.GetInvocationList())
{
var dispObj = nh.Target as DispatcherObject;
var dispatcher = dispObj?.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
nh.Invoke(this, e);
}
}
/// <inheritdoc />
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@@ -76,6 +76,8 @@ namespace Torch.Commands
{
var cmdText = new string(message.Skip(1).ToArray());
var command = Commands.GetCommand(cmdText, out string argText);
if (command == null)
return null;
var cmdPath = string.Join(".", command.Path);
var splitArgs = Regex.Matches(argText, "(\"[^\"]+\"|\\S+)").Cast<Match>().Select(x => x.ToString().Replace("\"", "")).ToList();

View File

@@ -14,6 +14,7 @@ namespace Torch.Commands
public class TorchCommands : CommandModule
{
[Command("help", "Displays help for a command")]
[Permission(MyPromoteLevel.None)]
public void Help()
{
var commandManager = ((TorchBase)Context.Torch).Commands;
@@ -45,6 +46,7 @@ namespace Torch.Commands
}
[Command("ver", "Shows the running Torch version.")]
[Permission(MyPromoteLevel.None)]
public void Version()
{
var ver = Context.Torch.TorchVersion;
@@ -52,6 +54,7 @@ namespace Torch.Commands
}
[Command("plugins", "Lists the currently loaded plugins.")]
[Permission(MyPromoteLevel.None)]
public void Plugins()
{
var plugins = Context.Torch.Plugins.Select(p => p.Name);
@@ -59,7 +62,6 @@ namespace Torch.Commands
}
[Command("stop", "Stops the server.")]
[Permission(MyPromoteLevel.Admin)]
public void Stop()
{
Context.Respond("Stopping server.");

View File

@@ -19,10 +19,10 @@ using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using SharpDX.Toolkit.Collections;
using SteamSDK;
using Torch.API;
using Torch.API.Managers;
using Torch.Collections;
using Torch.Commands;
using Torch.ViewModels;
using VRage.Game;
@@ -126,6 +126,7 @@ namespace Torch.Managers
private void OnSessionLoaded()
{
_log.Info("Initializing Steam auth");
MyMultiplayer.Static.ClientKicked += OnClientKicked;
MyMultiplayer.Static.ClientLeft += OnClientLeft;
@@ -137,6 +138,7 @@ namespace Torch.Managers
SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
_members = (List<ulong>)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
_log.Info("Steam auth initialized");
}
private void OnClientKicked(ulong steamId)
@@ -146,9 +148,11 @@ namespace Torch.Managers
private void OnClientLeft(ulong steamId, ChatMemberStateChangeEnum stateChange)
{
_log.Info($"{GetSteamUsername(steamId)} disconnected ({(ConnectionState)stateChange}).");
Players.TryGetValue(steamId, out PlayerViewModel vm);
PlayerLeft?.Invoke(vm ?? new PlayerViewModel(steamId));
if (vm == null)
vm = new PlayerViewModel(steamId);
_log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
PlayerLeft?.Invoke(vm);
Players.Remove(steamId);
}
@@ -174,6 +178,7 @@ namespace Torch.Managers
if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse")
{
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -= handle as ValidateAuthTicketResponse;
_log.Debug("Removed GameServer_ValidateAuthTicketResponse");
}
}
}
@@ -186,6 +191,7 @@ namespace Torch.Managers
if (handle.Method.Name == "GameServer_UserGroupStatus")
{
SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus;
_log.Debug("Removed GameServer_UserGroupStatus");
}
}
}
@@ -299,7 +305,8 @@ namespace Torch.Managers
private void UserAccepted(ulong steamId)
{
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
var vm = new PlayerViewModel(steamId);
var vm = new PlayerViewModel(steamId) {State = ConnectionState.Connected};
_log.Info($"Player {vm.Name} joined ({vm.SteamId})");
Players.Add(steamId, vm);
PlayerJoined?.Invoke(vm);
}

View File

@@ -129,7 +129,7 @@ namespace Torch.Managers
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
plugin.StoragePath = new FileInfo(asm.Location).Directory.FullName;
plugin.StoragePath = _torch.Config.InstancePath;
Plugins.Add(plugin);
commands.RegisterPluginCommands(plugin);

View File

@@ -145,6 +145,7 @@
<ItemGroup>
<Compile Include="ChatMessage.cs" />
<Compile Include="Collections\KeyTree.cs" />
<Compile Include="Collections\ObservableDictionary.cs" />
<Compile Include="Collections\RollingAverage.cs" />
<Compile Include="CommandLine.cs" />
<Compile Include="Commands\CategoryAttribute.cs" />

View File

@@ -231,15 +231,41 @@ namespace Torch
Log.Info($"Executing assembly: {Assembly.GetEntryAssembly().FullName}");
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
MySession.OnLoading += () => SessionLoading?.Invoke();
MySession.AfterLoading += () => SessionLoaded?.Invoke();
MySession.OnUnloading += () => SessionUnloading?.Invoke();
MySession.OnUnloaded += () => SessionUnloaded?.Invoke();
MySession.OnLoading += OnSessionLoading;
MySession.AfterLoading += OnSessionLoaded;
MySession.OnUnloading += OnSessionUnloading;
MySession.OnUnloaded += OnSessionUnloaded;
RegisterVRagePlugin();
_init = true;
}
private void OnSessionLoading()
{
Log.Debug("Session loading");
foreach (var manager in _managers)
manager.Init();
SessionLoading?.Invoke();
}
private void OnSessionLoaded()
{
Log.Debug("Session loaded");
SessionLoaded?.Invoke();
}
private void OnSessionUnloading()
{
Log.Debug("Session unloading");
SessionUnloading?.Invoke();
}
private void OnSessionUnloaded()
{
Log.Debug("Session unloaded");
SessionUnloaded?.Invoke();
}
/// <summary>
/// Hook into the VRage plugin system for updates.
/// </summary>
@@ -269,8 +295,7 @@ namespace Torch
/// <inheritdoc />
public virtual void Init(object gameInstance)
{
foreach (var manager in _managers)
manager.Init();
}
/// <inheritdoc />

View File

@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Engine.Multiplayer;
using SteamSDK;
using Torch.API;
using VRage.Replication;
namespace Torch.ViewModels
{
@@ -18,7 +20,7 @@ namespace Torch.ViewModels
public PlayerViewModel(ulong steamId, string name = null)
{
SteamId = steamId;
Name = name ?? SteamAPI.Instance?.Friends?.GetPersonaName(steamId) ?? "???";
Name = name ?? ((MyDedicatedServerBase)MyMultiplayerMinimalBase.Instance).GetMemberName(steamId);
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Torch
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propName = "")
protected virtual void OnPropertyChanged([CallerMemberName] string propName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}