From 1c92f69bd49f1e42fd993ccb8c4267d8291e5178 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 commands autocomplete in torch GUI chat in torch GUI now has entered history (like console, use up/down arrows) abstraction of instance manager session name now correctly displaying in torchSessionManager messages TorchSession now has loaded world property (as torch now has more control about world load process) now only dedicated config locks after session start --- NLog.config | 11 +- Torch.API/ITorchConfig.cs | 2 +- Torch.API/Managers/IInstanceManager.cs | 21 +++ 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/LogViewerViewModel.cs | 13 ++ Torch.Server/ViewModels/ModItemInfo.cs | 4 +- Torch.Server/Views/ChatControl.xaml | 5 +- Torch.Server/Views/ChatControl.xaml.cs | 48 +++++-- .../Views/CommandSuggestionsProvider.cs | 48 +++++++ Torch.Server/Views/ConfigControl.xaml | 7 +- Torch.Server/Views/ConfigControl.xaml.cs | 19 ++- Torch.Server/Views/LogViewerControl.xaml | 56 ++++++++ Torch.Server/Views/LogViewerControl.xaml.cs | 50 +++++++ 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/EntityIdentifierPatch.cs | 39 ++++++ Torch/Patches/KeenLogPatch.cs | 95 +++++++------ Torch/Patches/LoaderHook.cs | 68 +++++++++ Torch/Session/TorchSession.cs | 5 +- Torch/Session/TorchSessionManager.cs | 40 +++--- Torch/Torch.csproj | 4 +- 36 files changed, 599 insertions(+), 298 deletions(-) create mode 100644 Torch.API/Managers/IInstanceManager.cs delete mode 100644 Torch.Server/FlowDocumentTarget.cs create mode 100644 Torch.Server/LogViewerTarget.cs create mode 100644 Torch.Server/ViewModels/LogViewerViewModel.cs create mode 100644 Torch.Server/Views/CommandSuggestionsProvider.cs create mode 100644 Torch.Server/Views/LogViewerControl.xaml create mode 100644 Torch.Server/Views/LogViewerControl.xaml.cs create mode 100644 Torch/Patches/EntityIdentifierPatch.cs create mode 100644 Torch/Patches/LoaderHook.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/Managers/IInstanceManager.cs b/Torch.API/Managers/IInstanceManager.cs new file mode 100644 index 0000000..14ab8b8 --- /dev/null +++ b/Torch.API/Managers/IInstanceManager.cs @@ -0,0 +1,21 @@ +using VRage.Game; + +namespace Torch.API.Managers; + +public interface IInstanceManager : IManager +{ + IWorld SelectedWorld { get; } + void LoadInstance(string path, bool validate = true); + void SelectCreatedWorld(string worldPath); + void SelectWorld(string worldPath, bool modsOnly = true); + void ImportSelectedWorldConfig(); + void SaveConfig(); +} + +public interface IWorld +{ + string FolderName { get; } + string WorldPath { get; } + MyObjectBuilder_SessionSettings KeenSessionSettings { get; } + MyObjectBuilder_Checkpoint KeenCheckpoint { get; } +} \ 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/LogViewerViewModel.cs b/Torch.Server/ViewModels/LogViewerViewModel.cs new file mode 100644 index 0000000..6aa6f1c --- /dev/null +++ b/Torch.Server/ViewModels/LogViewerViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.ObjectModel; +using System.Windows.Media; + +namespace Torch.Server.ViewModels; + +public class LogViewerViewModel : ViewModel +{ + public ObservableCollection LogEntries { get; set; } = new(); +} + + +public record LogEntry(DateTime Timestamp, string Message, SolidColorBrush Color); \ No newline at end of file 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/CommandSuggestionsProvider.cs b/Torch.Server/Views/CommandSuggestionsProvider.cs new file mode 100644 index 0000000..6c18a22 --- /dev/null +++ b/Torch.Server/Views/CommandSuggestionsProvider.cs @@ -0,0 +1,48 @@ +using System.Collections; +using System.Linq; +using AutoCompleteTextBox.Editors; +using Sandbox; +using Torch.API; +using Torch.API.Managers; +using Torch.Commands; + +namespace Torch.Server.Views; + +public class CommandSuggestionsProvider : ISuggestionProvider +{ + private readonly ITorchServer _server; + private CommandManager _commandManager; + + public CommandSuggestionsProvider(ITorchServer server) + { + _server = server; + if (_server.CurrentSession is null) + _server.GameStateChanged += ServerOnGameStateChanged; + else + _commandManager = _server.CurrentSession.Managers.GetManager(); + } + + private void ServerOnGameStateChanged(MySandboxGame game, TorchGameState newState) + { + if (_server.CurrentSession is { }) + _commandManager = _server.CurrentSession.Managers.GetManager(); + } + + public IEnumerable GetSuggestions(string filter) + { + if (_commandManager is null || !_commandManager.IsCommand(filter)) + yield break; + var args = filter[1..].Split(' ').ToList(); + var skip = _commandManager.Commands.GetNode(args, out var node); + if (skip == -1) + yield break; + var lastArg = args.Last(); + + foreach (var subcommandsKey in node.Subcommands.Keys) + { + if (lastArg != node.Name && !subcommandsKey.Contains(lastArg)) + continue; + yield return $"!{string.Join(' ', node.GetPath())} {subcommandsKey}"; + } + } +} \ No newline at end of file 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/LogViewerControl.xaml b/Torch.Server/Views/LogViewerControl.xaml new file mode 100644 index 0000000..5f63c22 --- /dev/null +++ b/Torch.Server/Views/LogViewerControl.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Torch.Server/Views/LogViewerControl.xaml.cs b/Torch.Server/Views/LogViewerControl.xaml.cs new file mode 100644 index 0000000..2aa47d7 --- /dev/null +++ b/Torch.Server/Views/LogViewerControl.xaml.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Torch.Collections; +using Torch.Server.ViewModels; + +namespace Torch.Server.Views; + +public partial class LogViewerControl : UserControl +{ + private bool _isAutoscrollEnabled = true; + private readonly List _viewers = new(); + public LogViewerControl() + { + InitializeComponent(); + ((LogViewerViewModel)DataContext).LogEntries.CollectionChanged += LogEntriesOnCollectionChanged; + } + + private void LogEntriesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action != NotifyCollectionChangedAction.Add || !_isAutoscrollEnabled) + return; + foreach (var scrollViewer in _viewers) + { + scrollViewer.ScrollToEnd(); + } + } + + private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e) + { + var scrollViewer = (ScrollViewer) sender; + if (e.ExtentHeightChange == 0) + // ReSharper disable once CompareOfFloatsByEqualityOperator + _isAutoscrollEnabled = scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight; + } + + private void ScrollViewer_OnLoaded(object sender, RoutedEventArgs e) + { + _viewers.Add((ScrollViewer) sender); + } + + private void ScrollViewer_OnUnloaded(object sender, RoutedEventArgs e) + { + _viewers.Remove((ScrollViewer) sender); + } +} 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/EntityIdentifierPatch.cs b/Torch/Patches/EntityIdentifierPatch.cs new file mode 100644 index 0000000..4a6229d --- /dev/null +++ b/Torch/Patches/EntityIdentifierPatch.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Torch.Managers.PatchManager; +using Torch.Utils; +using VRage; +using VRage.ModAPI; + +namespace Torch.Patches; + +internal static class EntityIdentifierPatch +{ + [ReflectedGetter(Type = typeof(MyEntityIdentifier), Name = "m_perThreadData")] + private static Func _perThreadGetter = null!; + + [ReflectedGetter(TypeName = "VRage.MyEntityIdentifier+PerThreadData, VRage.Game", Name = "EntityList")] + private static Func> _entityDataGetter = null; + + [ReflectedMethodInfo(typeof(MyEntityIdentifier), "GetPerThreadEntities")] + private static MethodInfo _getPerThreadMethod = null!; + + public static void Patch(PatchContext context) + { + context.GetPattern(_getPerThreadMethod).AddPrefix(nameof(GetPerThreadPrefix)); + } + + // Rider DPA + // Large Object Heap: Allocated 286,3 MB (300156688 B) of type VRage.ModAPI.IMyEntity[] by + // List<__Canon>.AddWithResize() -> MyEntityIdentifier.GetPerThreadEntities(List) + private static bool GetPerThreadPrefix(List result) + { + /* + * This is better than 100500 calls of .Add because .Values returns ICollection<> + * .AddRange will work with it without additional enumerations + */ + result.AddRange(_entityDataGetter(_perThreadGetter()).Values); + return false; + } +} \ No newline at end of file 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/Patches/LoaderHook.cs b/Torch/Patches/LoaderHook.cs new file mode 100644 index 0000000..71ea5b1 --- /dev/null +++ b/Torch/Patches/LoaderHook.cs @@ -0,0 +1,68 @@ +using System.Collections; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using NLog; +using Sandbox.Definitions; +using Sandbox.Game.World; +using Torch.Managers.PatchManager; +using Torch.Utils; + +namespace Torch.Patches; + +[PatchShim] +internal static class LoaderHook +{ + private static readonly ILogger Log = LogManager.GetCurrentClassLogger(); + private static Stopwatch _stopwatch; + + [ReflectedMethodInfo(typeof(MyScriptManager), nameof(MyScriptManager.LoadData))] + private static MethodInfo _compilerLoadData = null!; + + public static void Patch(PatchContext context) + { + var pattern = context.GetPattern(_compilerLoadData); + pattern.AddPrefix(nameof(CompilePrefix)); + pattern.AddSuffix(nameof(CompileSuffix)); + + var methods = typeof(MyDefinitionManager) + .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic); + + pattern = context.GetPattern(methods.First(b => + b.Name == "LoadDefinitions" && b.GetParameters()[0].ParameterType.Name.Contains("List"))); + pattern.AddPrefix(nameof(LoadDefinitionsPrefix)); + pattern.AddSuffix(nameof(LoadDefinitionsSuffix)); + } + + private static void CompilePrefix() + { + _stopwatch?.Reset(); + _stopwatch = Stopwatch.StartNew(); + Log.Info("Mod scripts compilation started"); + } + + private static void CompileSuffix() + { + _stopwatch.Stop(); + Log.Info($"Compilation finished. Took {_stopwatch.Elapsed:g}"); + _stopwatch = null; + } + + private static void LoadDefinitionsPrefix(MyDefinitionManager __instance) + { + if (!__instance.Loading) + return; + _stopwatch?.Reset(); + _stopwatch = Stopwatch.StartNew(); + Log.Info("Definitions loading started"); + } + + private static void LoadDefinitionsSuffix(MyDefinitionManager __instance) + { + if (!__instance.Loading) + return; + _stopwatch.Stop(); + Log.Info($"Definitions load finished. Took {_stopwatch.Elapsed:g}"); + _stopwatch = null; + } +} \ No newline at end of file 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 @@ - + - +