updated NLog to v5

fixed most of issues with world creating/loading
fixed log window lags
fixed some compiler warnings
fixed empty log files creating
fixed logging performance
added better logging of load process
This commit is contained in:
z__
2022-02-02 14:09:08 +07:00
parent ab61674b47
commit 70833adb44
29 changed files with 304 additions and 298 deletions

View File

@@ -4,14 +4,17 @@
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
<variable name="logContent" value="${message:withException=true}"/>
<targets async="true">
<targets>
<default-wrapper xsi:type="AsyncWrapper" overflowAction="Block" optimizeBufferReuse="true" />
<target xsi:type="Null" name="null" formatMessage="false" />
<target xsi:type="File" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Keen-${shortdate}.log" />
<target xsi:type="File" name="main" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Torch-${shortdate}.log" />
<target xsi:type="File" keepFileOpen="true" concurrentWrites="false" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}"
fileName="Logs\Keen-${shortdate}.log" />
<target xsi:type="File" keepFileOpen="true" concurrentWrites="false" name="main" layout="${var:logStamp} ${logger}: ${var:logContent}"
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="${var:logStamp} ${logger:shortName=true}: ${var:logContent}" />
<target xsi:type="File" name="patch" layout="${var:logContent}" fileName="Logs\patch.log"/>
<target xsi:type="FlowDocument" name="wpf" layout="${var:logStamp} ${logger:shortName=true}: ${var:logContent}" />
<target xsi:type="LogViewerTarget" name="wpf" layout="[${level:uppercase=true}] ${logger:shortName=true}: ${var:logContent}" />
</targets>
<rules>

View File

@@ -29,7 +29,7 @@ namespace Torch
int WindowHeight { get; set; }
int FontSize { get; set; }
UGCServiceType UgcServiceType { get; set; }
bool EntityManagerEnabled { get; set; }
void Save(string path = null);
}
}

View File

@@ -22,6 +22,11 @@ namespace Torch.API.Session
/// The Space Engineers game session this session is bound to.
/// </summary>
MySession KeenSession { get; }
/// <summary>
/// Currently running world
/// </summary>
IWorld World { get; }
/// <inheritdoc cref="IDependencyManager"/>
IDependencyManager Managers { get; }

View File

@@ -17,7 +17,7 @@
</PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup>
<PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="SemanticVersioning" Version="2.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -18,7 +18,7 @@
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Documents;
using System.Windows.Media;
using NLog;
using NLog.Targets;
namespace Torch.Server
{
/// <summary>
/// NLog target that writes to a <see cref="FlowDocument"/>.
/// </summary>
[Target("flowDocument")]
public sealed class FlowDocumentTarget : TargetWithLayout
{
private FlowDocument _document = new FlowDocument { Background = new SolidColorBrush(Colors.Black) };
private readonly Paragraph _paragraph = new Paragraph();
private readonly int _maxLines = 500;
public FlowDocument Document => _document;
public FlowDocumentTarget()
{
_document.Blocks.Add(_paragraph);
}
/// <inheritdoc />
protected override void Write(LogEventInfo logEvent)
{
_document.Dispatcher.BeginInvoke(() =>
{
var message = $"{Layout.Render(logEvent)}\n";
_paragraph.Inlines.Add(new Run(message) {Foreground = LogLevelColors[logEvent.Level]});
// A massive paragraph slows the UI down
if (_paragraph.Inlines.Count > _maxLines)
_paragraph.Inlines.Remove(_paragraph.Inlines.FirstInline);
});
}
private static readonly Dictionary<LogLevel, SolidColorBrush> LogLevelColors = new Dictionary<LogLevel, SolidColorBrush>
{
[LogLevel.Trace] = new SolidColorBrush(Colors.DimGray),
[LogLevel.Debug] = new SolidColorBrush(Colors.DarkGray),
[LogLevel.Info] = new SolidColorBrush(Colors.White),
[LogLevel.Warn] = new SolidColorBrush(Colors.Magenta),
[LogLevel.Error] = new SolidColorBrush(Colors.Yellow),
[LogLevel.Fatal] = new SolidColorBrush(Colors.Red),
};
}
}

View File

@@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
@@ -156,6 +157,10 @@ quit";
gameThread.Start();
var ui = new TorchUI(_server);
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
ui.ShowDialog();
}
}
@@ -192,8 +197,9 @@ quit";
try
{
log.Info("Downloading SteamCMD.");
using (var client = new WebClient())
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
using (var client = new HttpClient())
using (var file = File.Create(STEAMCMD_ZIP))
client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result.CopyTo(file);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
File.Delete(STEAMCMD_ZIP);

View File

@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Threading;
using System.Windows.Media;
using System.Windows.Threading;
using NLog;
using NLog.Targets;
using Torch.Server.ViewModels;
using Torch.Server.Views;
namespace Torch.Server
{
/// <summary>
/// NLog target that writes to a <see cref="LogViewerControl"/>.
/// </summary>
[Target("logViewer")]
public sealed class LogViewerTarget : TargetWithLayout
{
public IList<LogEntry> LogEntries { get; set; }
public SynchronizationContext TargetContext { get; set; }
private readonly int _maxLines = 1000;
/// <inheritdoc />
protected override void Write(LogEventInfo logEvent)
{
TargetContext?.Post(_sendOrPostCallback, logEvent);
}
private void WriteCallback(object state)
{
var logEvent = (LogEventInfo) state;
LogEntries?.Add(new(logEvent.TimeStamp, Layout.Render(logEvent), LogLevelColors[logEvent.Level]));
}
private static readonly Dictionary<LogLevel, SolidColorBrush> LogLevelColors = new()
{
[LogLevel.Trace] = new SolidColorBrush(Colors.DimGray),
[LogLevel.Debug] = new SolidColorBrush(Colors.DarkGray),
[LogLevel.Info] = new SolidColorBrush(Colors.White),
[LogLevel.Warn] = new SolidColorBrush(Colors.Magenta),
[LogLevel.Error] = new SolidColorBrush(Colors.Yellow),
[LogLevel.Fatal] = new SolidColorBrush(Colors.Red),
};
private readonly SendOrPostCallback _sendOrPostCallback;
public LogViewerTarget()
{
_sendOrPostCallback = WriteCallback;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Havok;
@@ -30,7 +31,7 @@ using VRage.Plugins;
namespace Torch.Server.Managers
{
public class InstanceManager : Manager
public class InstanceManager : Manager, IInstanceManager
{
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
@@ -44,7 +45,9 @@ namespace Torch.Server.Managers
{
}
public IWorld SelectedWorld => DedicatedConfig.SelectedWorld;
public void LoadInstance(string path, bool validate = true)
{
Log.Info($"Loading instance {path}");
@@ -221,14 +224,11 @@ namespace Torch.Server.Managers
public void SaveConfig()
{
if (((TorchServer)Torch).HasRun)
if (!((TorchServer)Torch).HasRun)
{
Log.Warn("Checkpoint cache is stale, not saving dedicated config.");
return;
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
Log.Info("Saved dedicated config.");
}
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
Log.Info("Saved dedicated config.");
try
{
@@ -255,7 +255,7 @@ namespace Torch.Server.Managers
}
catch (Exception e)
{
Log.Error("Failed to write sandbox config, changes will not appear on server");
Log.Error("Failed to write sandbox config");
Log.Error(e);
}
}
@@ -276,12 +276,14 @@ namespace Torch.Server.Managers
}
}
public class WorldViewModel : ViewModel
public class WorldViewModel : ViewModel, IWorld
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string FolderName { get; set; }
public string WorldPath { get; }
public MyObjectBuilder_SessionSettings KeenSessionSettings => WorldConfiguration.Settings;
public MyObjectBuilder_Checkpoint KeenCheckpoint => Checkpoint;
public long WorldSizeKB { get; }
private string _checkpointPath;
private string _worldConfigPath;
@@ -329,13 +331,15 @@ namespace Torch.Server.Managers
public void LoadSandbox()
{
MyObjectBuilderSerializer.DeserializeXML(_checkpointPath, out MyObjectBuilder_Checkpoint checkpoint);
if (!MyObjectBuilderSerializer.DeserializeXML(_checkpointPath, out MyObjectBuilder_Checkpoint checkpoint))
throw new SerializationException("Error reading checkpoint, see keen log for details");
Checkpoint = new CheckpointViewModel(checkpoint);
// migrate old saves
if (File.Exists(_worldConfigPath))
{
MyObjectBuilderSerializer.DeserializeXML(_worldConfigPath, out MyObjectBuilder_WorldConfiguration worldConfig);
if (!MyObjectBuilderSerializer.DeserializeXML(_worldConfigPath, out MyObjectBuilder_WorldConfiguration worldConfig))
throw new SerializationException("Error reading settings, see keen log for details");
WorldConfiguration = new WorldConfigurationViewModel(worldConfig);
}
else

View File

@@ -25,7 +25,7 @@ namespace Torch.Server
[STAThread]
public static void Main(string[] args)
{
Target.Register<FlowDocumentTarget>("FlowDocument");
Target.Register<LogViewerTarget>(nameof(LogViewerTarget));
//Ensures that all the files are downloaded in the Torch directory.
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory!.FullName;
var binDir = Path.Combine(workingDir, "DedicatedServer64");

View File

@@ -3,7 +3,7 @@
"profiles": {
"Torch.Server": {
"commandName": "Project",
"commandLineArgs": "-noupdate",
"use64Bit": true,
"hotReloadEnabled": false
}

View File

@@ -39,12 +39,13 @@
</PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup>
<PackageReference Include="AutoCompleteTextBox" Version="1.3.0" />
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="ControlzEx" Version="5.0.1" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="MdXaml" Version="1.12.0" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.0.226801" />
<PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Management" Version="6.0.0" />

View File

@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.Runtime;
using NLog;
using PropertyChanged;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Multiplayer;
@@ -212,6 +213,7 @@ namespace Torch.Server
Environment.Exit(0);
}
[SuppressPropertyChangedWarnings]
private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState)
{
if (newState == TorchSessionState.Unloading || newState == TorchSessionState.Unloaded)

View File

@@ -18,11 +18,6 @@ namespace Torch.Server.ViewModels
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Model => _config;
public ConfigDedicatedViewModel() : this(new MyConfigDedicated<MyObjectBuilder_SessionSettings>(""))
{
}
public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated)
{
_config = configDedicated;
@@ -36,8 +31,7 @@ namespace Torch.Server.ViewModels
Validate();
_config.SessionSettings = SessionSettings;
// Never ever
//_config.IgnoreLastSession = true;
_config.IgnoreLastSession = true;
_config.Save(path);
}
@@ -73,8 +67,9 @@ namespace Torch.Server.ViewModels
}
}
public async Task UpdateAllModInfosAsync()
public Task UpdateAllModInfosAsync()
{
return Task.CompletedTask;
/*if (!Mods.Any())
return;
List<MyWorkshopItem> modInfos;

View File

@@ -85,8 +85,9 @@ namespace Torch.Server.ViewModels
/// via the Steam web API.
/// </summary>
/// <returns></returns>
public async Task<bool> UpdateModInfoAsync()
public Task<bool> UpdateModInfoAsync()
{
return Task.FromResult(true);
/*if (UgcService.ToLower() == "mod.io")
return true;
@@ -104,7 +105,6 @@ namespace Torch.Server.ViewModels
Log.Info("Mod Info successfully retrieved!");
FriendlyName = modInfo.Title;
Description = modInfo.Description;*/
return true;
}
public override string ToString()

View File

@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:editors="http://wpfcontrols.com/"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
@@ -18,7 +19,7 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" Content="Send" DockPanel.Dock="Right" Width="50" Margin="5" Click="SendButton_Click"></Button>
<TextBox Grid.Column="0" x:Name="Message" Margin="5" KeyDown="Message_OnKeyDown"></TextBox>
<editors:AutoCompleteTextBox Grid.Column="0" Margin="5" KeyDown="Message_OnKeyDown" x:Name="MessageBox" />
</Grid>
</Grid>
</UserControl>

View File

@@ -28,7 +28,9 @@ using Torch.API.Managers;
using Torch.API.Session;
using Torch.Managers;
using Torch.Server.Managers;
using Torch.Server.Views;
using VRage.Game;
using Color = VRageMath.Color;
namespace Torch.Server
{
@@ -38,12 +40,17 @@ namespace Torch.Server
public partial class ChatControl : UserControl
{
private static Logger _log = LogManager.GetCurrentClassLogger();
private ITorchServer _server;
#pragma warning disable CS0618
private ITorchServer _server = (ITorchServer) TorchBase.Instance;
#pragma warning restore CS0618
private readonly LinkedList<string> _lastMessages = new();
private LinkedListNode<string> _currentLastMessageNode;
public ChatControl()
{
InitializeComponent();
this.IsVisibleChanged += OnIsVisibleChanged;
MessageBox.Provider = new CommandSuggestionsProvider(_server);
}
private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
@@ -57,8 +64,8 @@ namespace Torch.Server
Dispatcher.Invoke(() =>
{
Message.Focus();
Keyboard.Focus(Message);
MessageBox.Focus();
Keyboard.Focus(MessageBox);
});
});
}
@@ -160,35 +167,50 @@ namespace Torch.Server
private void Message_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
OnMessageEntered();
switch (e.Key)
{
case Key.Enter:
OnMessageEntered();
break;
case Key.Up:
_currentLastMessageNode = _currentLastMessageNode?.Previous ?? _lastMessages.Last;
MessageBox.Text = _currentLastMessageNode?.Value ?? string.Empty;
break;
case Key.Down:
_currentLastMessageNode = _currentLastMessageNode?.Next ?? _lastMessages.First;
MessageBox.Text = _currentLastMessageNode?.Value ?? string.Empty;
break;
}
}
private void OnMessageEntered()
{
//Can't use Message.Text directly because of object ownership in WPF.
var text = Message.Text;
var text = MessageBox.Text;
if (string.IsNullOrEmpty(text))
return;
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
if (commands != null && commands.IsCommand(text))
{
InsertMessage(new TorchChatMessage(TorchBase.Instance.Config.ChatName, text, TorchBase.Instance.Config.ChatColor));
InsertMessage(new(_server.Config.ChatName, text, Color.Red, _server.Config.ChatColor));
_server.Invoke(() =>
{
if (!commands.HandleCommandFromServer(text, InsertMessage))
{
InsertMessage(new TorchChatMessage(TorchBase.Instance.Config.ChatName, "Invalid command.", TorchBase.Instance.Config.ChatColor));
return;
}
if (commands.HandleCommandFromServer(text, InsertMessage)) return;
InsertMessage(new(_server.Config.ChatName, "Invalid command.", Color.Red, _server.Config.ChatColor));
});
}
else
{
_server.CurrentSession?.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf(text);
}
Message.Text = "";
if (_currentLastMessageNode is { } && _currentLastMessageNode.Value == text)
{
_lastMessages.Remove(_currentLastMessageNode);
}
_lastMessages.AddLast(text);
_currentLastMessageNode = null;
MessageBox.Text = "";
}
}
}

View File

@@ -16,9 +16,6 @@
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
<viewModels:ConfigDedicatedViewModel />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -58,7 +55,7 @@
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="DediConfigScrollViewer">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -133,7 +130,7 @@
</Grid>
<TabControl Grid.Column="1" Margin="3">
<TabItem Header="World">
<views:PropertyGrid DataContext="{Binding SessionSettings}" IgnoreDisplay ="True" />
<views:PropertyGrid DataContext="{Binding SessionSettings}" />
</TabItem>
<TabItem Header="Torch">
<views:PropertyGrid x:Name="TorchSettings" />

View File

@@ -8,6 +8,8 @@ using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Threading;
using Sandbox;
using Torch.API;
using Torch.API.Managers;
using Torch.Server.Annotations;
using Torch.Server.Managers;
@@ -32,15 +34,26 @@ namespace Torch.Server.Views
public ConfigControl()
{
InitializeComponent();
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
#pragma warning disable CS0618
var instance = TorchBase.Instance;
#pragma warning restore CS0618
instance.GameStateChanged += InstanceOnGameStateChanged;
_instanceManager = instance.Managers.GetManager<InstanceManager>();
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
DataContext = _instanceManager.DedicatedConfig;
TorchSettings.DataContext = (TorchConfig)TorchBase.Instance.Config;
InitializeComponent();
TorchSettings.DataContext = (TorchConfig)instance.Config;
// Gets called once all children are loaded
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles));
}
private void InstanceOnGameStateChanged(MySandboxGame game, TorchGameState newState)
{
if (newState > TorchGameState.Creating)
Dispatcher.InvokeAsync(() => DediConfigScrollViewer.IsEnabled = false);
}
private void CheckValid()
{
ConfigValid = !_bindingExpressions.Any(x => x.HasError);

View File

@@ -25,9 +25,6 @@
</Style>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
<viewModels:ConfigDedicatedViewModel />
</UserControl.DataContext>
<Grid Style="{StaticResource RootGridStyle}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500px"/>

View File

@@ -17,11 +17,6 @@
<converters:InverseBooleanConverter x:Key="InverseBool"/>
</ResourceDictionary>
</Window.Resources>
<!--
<Window.DataContext>
<local:TorchServer/>
</Window.DataContext>
-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
@@ -63,10 +58,10 @@
</StackPanel>
<TabControl Grid.Row="2" Height="Auto" x:Name="TabControl" Margin="5,10,5,5">
<TabItem Header="Log">
<RichTextBox x:Name="ConsoleText" VerticalScrollBarVisibility="Visible" FontFamily="Consolas" IsReadOnly="True" Background="#0c0c0c"/>
<views:LogViewerControl x:Name="ConsoleText" Margin="3" />
</TabItem>
<TabItem Header="Configuration">
<Grid IsEnabled="{Binding Path=HasRun, Converter={StaticResource InverseBool}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
@@ -93,7 +88,7 @@
<local:PlayerListControl Grid.Column="1" x:Name="PlayerList" DockPanel.Dock="Right"/>
</Grid>
</TabItem>
<TabItem Header="Entity Manager" x:Name="EntityManagerTab">
<TabItem Header="Entity Manager" x:Name="EntityManagerTab" IsEnabled="{Binding Config.EntityManagerEnabled}">
</TabItem>
<TabItem Header="Plugins">
<views:PluginsControl x:Name="Plugins" />

View File

@@ -14,12 +14,14 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using NLog;
using NLog.Targets.Wrappers;
using Sandbox;
using Torch.API;
using Torch.API.Managers;
using Torch.Server.Managers;
using Torch.Server.ViewModels;
using Torch.Server.Views;
using MessageBoxResult = System.Windows.MessageBoxResult;
@@ -30,23 +32,22 @@ namespace Torch.Server
/// </summary>
public partial class TorchUI : Window
{
private TorchServer _server;
private TorchConfig _config;
private bool _autoscrollLog = true;
private readonly TorchServer _server;
private ITorchConfig Config => _server.Config;
public TorchUI(TorchServer server)
{
WindowStartupLocation = WindowStartupLocation.Manual;
_config = (TorchConfig)server.Config;
Width = _config.WindowWidth;
Height = _config.WindowHeight;
_server = server;
//TODO: data binding for whole server
DataContext = server;
WindowStartupLocation = WindowStartupLocation.Manual;
Width = Config.WindowWidth;
Height = Config.WindowHeight;
InitializeComponent();
ConsoleText.FontSize = Config.FontSize;
AttachConsole();
Loaded += OnLoaded;
//Left = _config.WindowPosition.X;
//Top = _config.WindowPosition.Y;
@@ -56,94 +57,35 @@ namespace Torch.Server
Chat.BindServer(server);
PlayerList.BindServer(server);
Plugins.BindServer(server);
LoadConfig((TorchConfig)server.Config);
if (Config.EntityManagerEnabled)
{
EntityManagerTab.Content = new EntitiesControl();
}
Themes.uiSource = this;
Themes.SetConfig(_config);
Title = $"{_config.InstanceName} - Torch {server.TorchVersion}, SE {server.GameVersion}";
Loaded += TorchUI_Loaded;
Themes.SetConfig((TorchConfig) Config);
Title = $"{Config.InstanceName} - Torch {server.TorchVersion}, SE {server.GameVersion}";
}
private void TorchUI_Loaded(object sender, RoutedEventArgs e)
private void OnLoaded(object sender, RoutedEventArgs e)
{
var scrollViewer = FindDescendant<ScrollViewer>(ConsoleText);
scrollViewer.ScrollChanged += ConsoleText_OnScrollChanged;
AttachConsole();
}
private void AttachConsole()
{
const string target = "wpf";
var doc = LogManager.Configuration.FindTargetByName<FlowDocumentTarget>(target)?.Document;
if (doc == null)
const string targetName = "wpf";
var target = LogManager.Configuration.FindTargetByName<LogViewerTarget>(targetName);
if (target == null)
{
var wrapped = LogManager.Configuration.FindTargetByName<WrapperTargetBase>(target);
doc = (wrapped?.WrappedTarget as FlowDocumentTarget)?.Document;
var wrapped = LogManager.Configuration.FindTargetByName<WrapperTargetBase>(targetName);
target = wrapped?.WrappedTarget as LogViewerTarget;
}
ConsoleText.FontSize = _config.FontSize;
ConsoleText.Document = doc ?? new FlowDocument(new Paragraph(new Run("No target!")));
ConsoleText.TextChanged += ConsoleText_OnTextChanged;
}
public static T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
{
if (obj == null) return default(T);
int numberChildren = VisualTreeHelper.GetChildrenCount(obj);
if (numberChildren == 0) return default(T);
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)child;
}
}
for (int i = 0; i < numberChildren; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
var potentialMatch = FindDescendant<T>(child);
if (potentialMatch != default(T))
{
return potentialMatch;
}
}
return default(T);
}
private void ConsoleText_OnTextChanged(object sender, TextChangedEventArgs args)
{
var textBox = (RichTextBox) sender;
if (_autoscrollLog)
ConsoleText.ScrollToEnd();
}
private void ConsoleText_OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
var scrollViewer = (ScrollViewer) sender;
if (e.ExtentHeightChange == 0)
{
// User change.
_autoscrollLog = scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight;
}
}
public void LoadConfig(TorchConfig config)
{
if (!Directory.Exists(config.InstancePath))
return;
_config = config;
Dispatcher.Invoke(() =>
{
EntityManagerTab.IsEnabled = _config.EntityManagerEnabled;
if (_config.EntityManagerEnabled)
{
EntityManagerTab.Content = new EntitiesControl();
}
});
if (target is null) return;
var viewModel = (LogViewerViewModel)ConsoleText.DataContext;
target.LogEntries = viewModel.LogEntries;
target.TargetContext = SynchronizationContext.Current;
}
private void BtnStart_Click(object sender, RoutedEventArgs e)
@@ -176,21 +118,5 @@ namespace Torch.Server
Process.GetCurrentProcess().Kill();
}
private void BtnRestart_Click(object sender, RoutedEventArgs e)
{
//MySandboxGame.Static.Invoke(MySandboxGame.ReloadDedicatedServerSession); use i
}
private void InstancePathBox_OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
var name = ((TextBox)sender).Text;
if (!Directory.Exists(name))
return;
_config.InstancePath = name;
_server.Managers.GetManager<InstanceManager>().LoadInstance(_config.InstancePath);
}
}
}

View File

@@ -19,7 +19,7 @@
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -49,12 +49,13 @@ namespace Torch.Commands
{
return !string.IsNullOrEmpty(command) && command[0] == Prefix;
}
public void RegisterCommandModule(Type moduleType, ITorchPlugin plugin = null)
public int RegisterCommandModule(Type moduleType, ITorchPlugin plugin = null)
{
if (!moduleType.IsSubclassOf(typeof(CommandModule)))
return;
return 0;
var i = 0;
foreach (var method in moduleType.GetMethods())
{
var commandAttrib = method.GetCustomAttribute<CommandAttribute>();
@@ -63,11 +64,14 @@ namespace Torch.Commands
var command = new Command(plugin, method);
var cmdPath = string.Join(".", command.Path);
_log.Info($"Registering command '{cmdPath}'");
_log.Debug($"Registering command '{cmdPath}'");
i++;
if (!Commands.AddCommand(command))
_log.Error($"Command path {cmdPath} is already registered.");
}
return i;
}
public void UnregisterPluginCommands(ITorchPlugin plugin)
@@ -78,10 +82,9 @@ namespace Torch.Commands
public void RegisterPluginCommands(ITorchPlugin plugin)
{
var assembly = plugin.GetType().Assembly;
foreach (var type in assembly.ExportedTypes)
{
RegisterCommandModule(type, plugin);
}
var count = assembly.ExportedTypes.Sum(type => RegisterCommandModule(type, plugin));
if (count > 0)
_log.Info($"Registered {count} commands from {plugin.Name}");
}
private List<TorchChatMessage> HandleCommandFromServerInternal(string message, Action<TorchChatMessage> subscriber = null)

View File

@@ -23,6 +23,12 @@ namespace Torch.Managers.PatchManager
{
private static Action<ILHook, bool> IsAppliedSetter;
[ReflectedMethodInfo(typeof(MethodBase), nameof(MethodBase.GetMethodFromHandle), Parameters = new[] {typeof(RuntimeMethodHandle)})]
private static MethodInfo _getMethodFromHandle = null!;
[ReflectedMethodInfo(typeof(MethodBase), nameof(MethodBase.GetMethodFromHandle), Parameters = new[] {typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle)})]
private static MethodInfo _getMethodFromHandleGeneric = null!;
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
private readonly MethodBase _method;
@@ -103,6 +109,7 @@ namespace Torch.Managers.PatchManager
public const string INSTANCE_PARAMETER = "__instance";
public const string RESULT_PARAMETER = "__result";
public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped";
public const string ORIGINAL_PARAMETER = "__original";
public const string LOCAL_PARAMETER = "__local";
private void SavePatchedMethod(string target)
@@ -320,6 +327,24 @@ namespace Torch.Managers.PatchManager
yield return new MsilInstruction(OpCodes.Ldarg_0);
break;
}
case ORIGINAL_PARAMETER:
{
if (!typeof(MethodBase).IsAssignableFrom(param.ParameterType))
throw new PatchException($"Original parameter should be assignable to {nameof(MethodBase)}",
_method);
yield return new MsilInstruction(OpCodes.Ldtoken).InlineValue(_method);
if (_method.DeclaringType!.ContainsGenericParameters)
{
yield return new MsilInstruction(OpCodes.Ldtoken).InlineValue(_method.DeclaringType);
yield return new MsilInstruction(OpCodes.Call).InlineValue(_getMethodFromHandleGeneric);
}
else
yield return new MsilInstruction(OpCodes.Call).InlineValue(_getMethodFromHandle);
if (param.ParameterType != typeof(MethodBase))
yield return new MsilInstruction(OpCodes.Castclass).InlineValue(param.ParameterType);
break;
}
case PREFIX_SKIPPED_PARAMETER:
{
if (param.ParameterType != typeof(bool))

View File

@@ -9,6 +9,7 @@ using NLog;
using Torch.API;
using Torch.Managers.PatchManager;
using Torch.Utils;
using VRage;
using VRage.Utils;
namespace Torch.Patches
@@ -42,71 +43,81 @@ namespace Torch.Patches
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLineAndConsole), Parameters = new[] { typeof(string) })]
private static MethodInfo _logWriteLineAndConsole;
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Init))]
private static MethodInfo _logInit;
#pragma warning restore 649
public static void Patch(PatchContext context)
{
context.GetPattern(_logStringBuilder).Prefixes.Add(Method(nameof(PrefixLogStringBuilder)));
context.GetPattern(_logFormatted).Prefixes.Add(Method(nameof(PrefixLogFormatted)));
context.GetPattern(_logStringBuilder).AddPrefix(nameof(PrefixLogStringBuilder));
context.GetPattern(_logFormatted).AddPrefix(nameof(PrefixLogFormatted));
context.GetPattern(_logWriteLine).Prefixes.Add(Method(nameof(PrefixWriteLine)));
context.GetPattern(_logAppendToClosedLog).Prefixes.Add(Method(nameof(PrefixAppendToClosedLog)));
context.GetPattern(_logWriteLineAndConsole).Prefixes.Add(Method(nameof(PrefixWriteLineConsole)));
context.GetPattern(_logWriteLine).AddPrefix(nameof(PrefixWriteLine));
context.GetPattern(_logAppendToClosedLog).AddPrefix(nameof(PrefixAppendToClosedLog));
context.GetPattern(_logWriteLineAndConsole).AddPrefix(nameof(PrefixWriteLineConsole));
context.GetPattern(_logWriteLineException).Prefixes.Add(Method(nameof(PrefixWriteLineException)));
context.GetPattern(_logAppendToClosedLogException).Prefixes.Add(Method(nameof(PrefixAppendToClosedLogException)));
context.GetPattern(_logWriteLineException).AddPrefix(nameof(PrefixWriteLineException));
context.GetPattern(_logAppendToClosedLogException).AddPrefix(nameof(PrefixAppendToClosedLogException));
context.GetPattern(_logWriteLineOptions).Prefixes.Add(Method(nameof(PrefixWriteLineOptions)));
context.GetPattern(_logWriteLineOptions).AddPrefix(nameof(PrefixWriteLineOptions));
}
private static MethodInfo Method(string name)
{
return typeof(KeenLogPatch).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
context.GetPattern(_logInit).AddPrefix(nameof(PrefixInit));
}
[ReflectedMethod(Name = "GetIdentByThread")]
private static Func<MyLog, int, int> GetIndentByThread = null!;
private static Func<MyLog, int, int> _getIndentByThread = null!;
[ThreadStatic]
private static StringBuilder _tmpStringBuilder;
private static StringBuilder PrepareLog(MyLog log)
[ReflectedGetter(Name = "m_lock")]
private static Func<MyLog, FastResourceLock> _lockGetter = null!;
[ReflectedSetter(Name = "m_enabled")]
private static Action<MyLog, bool> _enabledSetter = null!;
private static int GetIndentByCurrentThread()
{
_tmpStringBuilder ??= new();
using var l = _lockGetter(MyLog.Default).AcquireExclusiveUsing();
return _getIndentByThread(MyLog.Default, Environment.CurrentManagedThreadId);
}
private static bool PrefixInit(MyLog __instance, StringBuilder appVersionString)
{
__instance.WriteLine("Log Started");
var byThreadField =
typeof(MyLog).GetField("m_indentsByThread", BindingFlags.Instance | BindingFlags.NonPublic)!;
var indentsField = typeof(MyLog).GetField("m_indents", BindingFlags.Instance | BindingFlags.NonPublic)!;
_tmpStringBuilder.Clear();
var t = GetIndentByThread(log, Environment.CurrentManagedThreadId);
_tmpStringBuilder.Append(' ', t * 3);
return _tmpStringBuilder;
byThreadField.SetValue(__instance, Activator.CreateInstance(byThreadField.FieldType));
indentsField.SetValue(__instance, Activator.CreateInstance(indentsField.FieldType));
_enabledSetter(__instance, true);
return false;
}
private static bool PrefixWriteLine(MyLog __instance, string msg)
{
if (__instance.LogEnabled)
_log.Debug(PrepareLog(__instance).Append(msg));
if (__instance.LogEnabled && _log.IsDebugEnabled)
_log.Debug($"{" ".PadRight(3 * GetIndentByCurrentThread())}{msg}");
return false;
}
private static bool PrefixWriteLineConsole(MyLog __instance, string msg)
{
if (__instance.LogEnabled)
_log.Info(PrepareLog(__instance).Append(msg));
if (__instance.LogEnabled && _log.IsInfoEnabled)
_log.Info($"{" ".PadRight(3 * GetIndentByCurrentThread())}{msg}");
return false;
}
private static bool PrefixAppendToClosedLog(MyLog __instance, string text)
{
if (__instance.LogEnabled)
_log.Info(PrepareLog(__instance).Append(text));
if (__instance.LogEnabled && _log.IsDebugEnabled)
_log.Debug($"{" ".PadRight(3 * GetIndentByCurrentThread())}{text}");
return false;
}
private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option)
{
if (__instance.LogEnabled && __instance.LogFlag(option))
_log.Info(PrepareLog(__instance).Append(message));
if (__instance.LogEnabled && __instance.LogFlag(option) && _log.IsDebugEnabled)
_log.Info($"{" ".PadRight(3 * GetIndentByCurrentThread())}{message}");
return false;
}
@@ -126,22 +137,22 @@ namespace Torch.Patches
{
if (__instance.LogEnabled)
return false;
// Sometimes this is called with a pre-formatted string and no args
// and causes a crash when the format string contains braces
var sb = PrepareLog(__instance);
if (args is {Length: > 0})
sb.AppendFormat(format, args);
else
sb.Append(format);
_log.Log(LogLevelFor(severity), sb);
// ReSharper disable once TemplateIsNotCompileTimeConstantProblem
_log.Log(new(LogLevelFor(severity), _log.Name, $"{" ".PadRight(3 * GetIndentByCurrentThread())}{string.Format(format, args)}"));
return false;
}
private static bool PrefixLogStringBuilder(MyLog __instance, MyLogSeverity severity, StringBuilder builder)
{
if (__instance.LogEnabled)
_log.Log(LogLevelFor(severity), PrepareLog(__instance).Append(builder));
if (!__instance.LogEnabled) return false;
var indent = GetIndentByCurrentThread() * 3;
// because append resizes every char
builder.EnsureCapacity(indent);
builder.Append(' ', indent);
_log.Log(LogLevelFor(severity), builder);
return false;
}

View File

@@ -26,13 +26,16 @@ namespace Torch.Session
/// </summary>
public MySession KeenSession { get; }
public IWorld World { get; }
/// <inheritdoc cref="IDependencyManager"/>
public IDependencyManager Managers { get; }
public TorchSession(ITorchBase torch, MySession keenSession)
public TorchSession(ITorchBase torch, MySession keenSession, IWorld world)
{
Torch = torch;
KeenSession = keenSession;
World = world;
Managers = new DependencyManager(torch.Managers);
}

View File

@@ -28,6 +28,9 @@ namespace Torch.Session
private readonly Dictionary<ulong, MyObjectBuilder_Checkpoint.ModItem> _overrideMods;
[Dependency]
private IInstanceManager _instanceManager = null!;
public event Action<CollectionChangeEventArgs> OverrideModsChanged;
/// <summary>
@@ -101,15 +104,18 @@ namespace Torch.Session
{
try
{
if (_instanceManager.SelectedWorld is null)
throw new InvalidOperationException("No valid worlds selected! Please select world first.");
if (_currentSession != null)
{
_log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
_currentSession.Detach();
}
_log.Info($"Starting new torch session for {MySession.Static.Name}");
_log.Info($"Starting new torch session for {_instanceManager.SelectedWorld.FolderName}");
_currentSession = new TorchSession(Torch, MySession.Static);
_currentSession = new TorchSession(Torch, MySession.Static, _instanceManager.SelectedWorld);
SetState(TorchSessionState.Loading);
}
catch (Exception e)
@@ -123,11 +129,9 @@ namespace Torch.Session
{
try
{
if (_currentSession == null)
{
_log.Warn("Session loaded event occurred when we don't have a session.");
return;
}
if (_currentSession is null)
throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
foreach (SessionManagerFactoryDel factory in _factories)
{
IManager manager = factory(CurrentSession);
@@ -135,7 +139,7 @@ namespace Torch.Session
CurrentSession.Managers.AddManager(manager);
}
(CurrentSession as TorchSession)?.Attach();
_log.Info($"Loaded torch session for {MySession.Static.Name}");
_log.Info($"Loaded torch session for {CurrentSession.World.FolderName}");
SetState(TorchSessionState.Loaded);
}
catch (Exception e)
@@ -149,12 +153,10 @@ namespace Torch.Session
{
try
{
if (_currentSession == null)
{
_log.Warn("Session unloading event occurred when we don't have a session.");
return;
}
_log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
if (_currentSession is null)
throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
_log.Info($"Unloading torch session for {_currentSession.World.FolderName}");
SetState(TorchSessionState.Unloading);
_currentSession.Detach();
}
@@ -169,12 +171,10 @@ namespace Torch.Session
{
try
{
if (_currentSession == null)
{
_log.Warn("Session unloading event occurred when we don't have a session.");
return;
}
_log.Info($"Unloaded torch session for {_currentSession.KeenSession.Name}");
if (_currentSession is null)
throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
_log.Info($"Unloaded torch session for {_currentSession.World.FolderName}");
SetState(TorchSessionState.Unloaded);
_currentSession = null;
}

View File

@@ -20,10 +20,10 @@
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup>
<PackageReference Include="ControlzEx" Version="5.0.1" />
<PackageReference Include="InfoOf.Fody" Version="2.1.0" />
<PackageReference Include="InfoOf.Fody" Version="2.1.0" PrivateAssets="all" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.1.4.3" />
<PackageReference Include="NLog" Version="4.7.13" />
<PackageReference Include="NLog" Version="5.0.0-rc2" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Torch.SixLabors.ImageSharp" Version="1.0.0-beta6" />