From 70833adb4435019a6fd0fa47d46429a30e589776 Mon Sep 17 00:00:00 2001 From: z__ Date: Wed, 2 Feb 2022 14:09:08 +0700 Subject: [PATCH] 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 --- NLog.config | 11 +- Torch.API/ITorchConfig.cs | 2 +- Torch.API/Session/ITorchSession.cs | 5 + Torch.API/Torch.API.csproj | 2 +- Torch.Server.Tests/Torch.Server.Tests.csproj | 2 +- Torch.Server/FlowDocumentTarget.cs | 54 -------- Torch.Server/Initializer.cs | 10 +- Torch.Server/LogViewerTarget.cs | 51 +++++++ Torch.Server/Managers/InstanceManager.cs | 28 ++-- Torch.Server/Program.cs | 2 +- Torch.Server/Properties/launchSettings.json | 2 +- Torch.Server/Torch.Server.csproj | 3 +- Torch.Server/TorchServer.cs | 2 + .../ViewModels/ConfigDedicatedViewModel.cs | 11 +- Torch.Server/ViewModels/ModItemInfo.cs | 4 +- Torch.Server/Views/ChatControl.xaml | 5 +- Torch.Server/Views/ChatControl.xaml.cs | 48 +++++-- Torch.Server/Views/ConfigControl.xaml | 7 +- Torch.Server/Views/ConfigControl.xaml.cs | 19 ++- Torch.Server/Views/ModListControl.xaml | 3 - Torch.Server/Views/TorchUI.xaml | 11 +- Torch.Server/Views/TorchUI.xaml.cs | 130 ++++-------------- Torch.Tests/Torch.Tests.csproj | 2 +- Torch/Commands/CommandManager.cs | 19 +-- .../Managers/PatchManager/DecoratedMethod.cs | 25 ++++ Torch/Patches/KeenLogPatch.cs | 95 +++++++------ Torch/Session/TorchSession.cs | 5 +- Torch/Session/TorchSessionManager.cs | 40 +++--- Torch/Torch.csproj | 4 +- 29 files changed, 304 insertions(+), 298 deletions(-) delete mode 100644 Torch.Server/FlowDocumentTarget.cs create mode 100644 Torch.Server/LogViewerTarget.cs diff --git a/NLog.config b/NLog.config index deab41a..ec2aee3 100644 --- a/NLog.config +++ b/NLog.config @@ -4,14 +4,17 @@ - + + - - + + - + diff --git a/Torch.API/ITorchConfig.cs b/Torch.API/ITorchConfig.cs index 73a24bf..2156dfd 100644 --- a/Torch.API/ITorchConfig.cs +++ b/Torch.API/ITorchConfig.cs @@ -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); } } \ No newline at end of file diff --git a/Torch.API/Session/ITorchSession.cs b/Torch.API/Session/ITorchSession.cs index 59add06..0648fcb 100644 --- a/Torch.API/Session/ITorchSession.cs +++ b/Torch.API/Session/ITorchSession.cs @@ -22,6 +22,11 @@ namespace Torch.API.Session /// The Space Engineers game session this session is bound to. /// MySession KeenSession { get; } + + /// + /// Currently running world + /// + IWorld World { get; } /// IDependencyManager Managers { get; } diff --git a/Torch.API/Torch.API.csproj b/Torch.API/Torch.API.csproj index f5f2440..f29f500 100644 --- a/Torch.API/Torch.API.csproj +++ b/Torch.API/Torch.API.csproj @@ -17,7 +17,7 @@ - + diff --git a/Torch.Server.Tests/Torch.Server.Tests.csproj b/Torch.Server.Tests/Torch.Server.Tests.csproj index eb8f250..b31bcb2 100644 --- a/Torch.Server.Tests/Torch.Server.Tests.csproj +++ b/Torch.Server.Tests/Torch.Server.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/Torch.Server/FlowDocumentTarget.cs b/Torch.Server/FlowDocumentTarget.cs deleted file mode 100644 index 26a1fba..0000000 --- a/Torch.Server/FlowDocumentTarget.cs +++ /dev/null @@ -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 -{ - /// - /// NLog target that writes to a . - /// - [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); - } - - /// - 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 LogLevelColors = new Dictionary - { - [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), - }; - } -} diff --git a/Torch.Server/Initializer.cs b/Torch.Server/Initializer.cs index fa288f0..fe61e95 100644 --- a/Torch.Server/Initializer.cs +++ b/Torch.Server/Initializer.cs @@ -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); diff --git a/Torch.Server/LogViewerTarget.cs b/Torch.Server/LogViewerTarget.cs new file mode 100644 index 0000000..dacc1d3 --- /dev/null +++ b/Torch.Server/LogViewerTarget.cs @@ -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 +{ + /// + /// NLog target that writes to a . + /// + [Target("logViewer")] + public sealed class LogViewerTarget : TargetWithLayout + { + public IList LogEntries { get; set; } + public SynchronizationContext TargetContext { get; set; } + private readonly int _maxLines = 1000; + + /// + 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 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; + } + } +} diff --git a/Torch.Server/Managers/InstanceManager.cs b/Torch.Server/Managers/InstanceManager.cs index f807754..05fbbdc 100644 --- a/Torch.Server/Managers/InstanceManager.cs +++ b/Torch.Server/Managers/InstanceManager.cs @@ -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 diff --git a/Torch.Server/Program.cs b/Torch.Server/Program.cs index a42cc5b..a51ff6e 100644 --- a/Torch.Server/Program.cs +++ b/Torch.Server/Program.cs @@ -25,7 +25,7 @@ namespace Torch.Server [STAThread] public static void Main(string[] args) { - Target.Register("FlowDocument"); + Target.Register(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"); diff --git a/Torch.Server/Properties/launchSettings.json b/Torch.Server/Properties/launchSettings.json index 8236f31..80ab2ae 100644 --- a/Torch.Server/Properties/launchSettings.json +++ b/Torch.Server/Properties/launchSettings.json @@ -3,7 +3,7 @@ "profiles": { "Torch.Server": { "commandName": "Project", - + "commandLineArgs": "-noupdate", "use64Bit": true, "hotReloadEnabled": false } diff --git a/Torch.Server/Torch.Server.csproj b/Torch.Server/Torch.Server.csproj index c5db6e9..d6e2bbc 100644 --- a/Torch.Server/Torch.Server.csproj +++ b/Torch.Server/Torch.Server.csproj @@ -39,12 +39,13 @@ + - + diff --git a/Torch.Server/TorchServer.cs b/Torch.Server/TorchServer.cs index 248087d..e2d3ab1 100644 --- a/Torch.Server/TorchServer.cs +++ b/Torch.Server/TorchServer.cs @@ -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) diff --git a/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs b/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs index 491660f..ca24e9b 100644 --- a/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs +++ b/Torch.Server/ViewModels/ConfigDedicatedViewModel.cs @@ -18,11 +18,6 @@ namespace Torch.Server.ViewModels private MyConfigDedicated _config; public MyConfigDedicated Model => _config; - public ConfigDedicatedViewModel() : this(new MyConfigDedicated("")) - { - - } - public ConfigDedicatedViewModel(MyConfigDedicated 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 modInfos; diff --git a/Torch.Server/ViewModels/ModItemInfo.cs b/Torch.Server/ViewModels/ModItemInfo.cs index 6872103..5848c55 100644 --- a/Torch.Server/ViewModels/ModItemInfo.cs +++ b/Torch.Server/ViewModels/ModItemInfo.cs @@ -85,8 +85,9 @@ namespace Torch.Server.ViewModels /// via the Steam web API. /// /// - public async Task UpdateModInfoAsync() + public Task 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() diff --git a/Torch.Server/Views/ChatControl.xaml b/Torch.Server/Views/ChatControl.xaml index 79c1148..45db245 100644 --- a/Torch.Server/Views/ChatControl.xaml +++ b/Torch.Server/Views/ChatControl.xaml @@ -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"> @@ -18,7 +19,7 @@ - + diff --git a/Torch.Server/Views/ChatControl.xaml.cs b/Torch.Server/Views/ChatControl.xaml.cs index 268668d..a9dcc1f 100644 --- a/Torch.Server/Views/ChatControl.xaml.cs +++ b/Torch.Server/Views/ChatControl.xaml.cs @@ -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 _lastMessages = new(); + private LinkedListNode _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(); 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().SendMessageAsSelf(text); } - Message.Text = ""; + if (_currentLastMessageNode is { } && _currentLastMessageNode.Value == text) + { + _lastMessages.Remove(_currentLastMessageNode); + } + _lastMessages.AddLast(text); + _currentLastMessageNode = null; + MessageBox.Text = ""; } } } diff --git a/Torch.Server/Views/ConfigControl.xaml b/Torch.Server/Views/ConfigControl.xaml index 798c4af..0cb4f02 100644 --- a/Torch.Server/Views/ConfigControl.xaml +++ b/Torch.Server/Views/ConfigControl.xaml @@ -16,9 +16,6 @@ - - - @@ -58,7 +55,7 @@ - + @@ -133,7 +130,7 @@ - + diff --git a/Torch.Server/Views/ConfigControl.xaml.cs b/Torch.Server/Views/ConfigControl.xaml.cs index 96e17e9..1759ea3 100644 --- a/Torch.Server/Views/ConfigControl.xaml.cs +++ b/Torch.Server/Views/ConfigControl.xaml.cs @@ -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(); +#pragma warning disable CS0618 + var instance = TorchBase.Instance; +#pragma warning restore CS0618 + instance.GameStateChanged += InstanceOnGameStateChanged; + + _instanceManager = instance.Managers.GetManager(); _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); diff --git a/Torch.Server/Views/ModListControl.xaml b/Torch.Server/Views/ModListControl.xaml index b24ec07..027528a 100644 --- a/Torch.Server/Views/ModListControl.xaml +++ b/Torch.Server/Views/ModListControl.xaml @@ -25,9 +25,6 @@ - - - diff --git a/Torch.Server/Views/TorchUI.xaml b/Torch.Server/Views/TorchUI.xaml index 5a37ce2..aa2c6b9 100644 --- a/Torch.Server/Views/TorchUI.xaml +++ b/Torch.Server/Views/TorchUI.xaml @@ -17,11 +17,6 @@ - @@ -63,10 +58,10 @@ - + - + @@ -93,7 +88,7 @@ - + diff --git a/Torch.Server/Views/TorchUI.xaml.cs b/Torch.Server/Views/TorchUI.xaml.cs index 1850351..7bbefd8 100644 --- a/Torch.Server/Views/TorchUI.xaml.cs +++ b/Torch.Server/Views/TorchUI.xaml.cs @@ -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 /// 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(ConsoleText); - scrollViewer.ScrollChanged += ConsoleText_OnScrollChanged; + AttachConsole(); } private void AttachConsole() { - const string target = "wpf"; - var doc = LogManager.Configuration.FindTargetByName(target)?.Document; - if (doc == null) + const string targetName = "wpf"; + var target = LogManager.Configuration.FindTargetByName(targetName); + if (target == null) { - var wrapped = LogManager.Configuration.FindTargetByName(target); - doc = (wrapped?.WrappedTarget as FlowDocumentTarget)?.Document; + var wrapped = LogManager.Configuration.FindTargetByName(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(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(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().LoadInstance(_config.InstancePath); - } } } diff --git a/Torch.Tests/Torch.Tests.csproj b/Torch.Tests/Torch.Tests.csproj index d973b51..d3e2c1f 100644 --- a/Torch.Tests/Torch.Tests.csproj +++ b/Torch.Tests/Torch.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/Torch/Commands/CommandManager.cs b/Torch/Commands/CommandManager.cs index 7d5a083..87a25b4 100644 --- a/Torch/Commands/CommandManager.cs +++ b/Torch/Commands/CommandManager.cs @@ -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(); @@ -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 HandleCommandFromServerInternal(string message, Action subscriber = null) diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index d2cd2c3..5bbb249 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -23,6 +23,12 @@ namespace Torch.Managers.PatchManager { private static Action 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)) diff --git a/Torch/Patches/KeenLogPatch.cs b/Torch/Patches/KeenLogPatch.cs index 13bff83..c6e6e8c 100644 --- a/Torch/Patches/KeenLogPatch.cs +++ b/Torch/Patches/KeenLogPatch.cs @@ -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 GetIndentByThread = null!; + private static Func _getIndentByThread = null!; - [ThreadStatic] - private static StringBuilder _tmpStringBuilder; - - private static StringBuilder PrepareLog(MyLog log) + [ReflectedGetter(Name = "m_lock")] + private static Func _lockGetter = null!; + + [ReflectedSetter(Name = "m_enabled")] + private static Action _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; } diff --git a/Torch/Session/TorchSession.cs b/Torch/Session/TorchSession.cs index 2120ef1..c8b04f4 100644 --- a/Torch/Session/TorchSession.cs +++ b/Torch/Session/TorchSession.cs @@ -26,13 +26,16 @@ namespace Torch.Session /// public MySession KeenSession { get; } + public IWorld World { get; } + /// 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); } diff --git a/Torch/Session/TorchSessionManager.cs b/Torch/Session/TorchSessionManager.cs index 05b415a..050e177 100644 --- a/Torch/Session/TorchSessionManager.cs +++ b/Torch/Session/TorchSessionManager.cs @@ -28,6 +28,9 @@ namespace Torch.Session private readonly Dictionary _overrideMods; + [Dependency] + private IInstanceManager _instanceManager = null!; + public event Action OverrideModsChanged; /// @@ -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; } diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index 8ded7bc..a7d20f7 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -20,10 +20,10 @@ - + - +