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
This commit is contained in:
11
NLog.config
11
NLog.config
@@ -4,14 +4,17 @@
|
|||||||
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
<variable name="logStamp" value="${time} ${pad:padding=-8:inner=[${level:uppercase=true}]}" />
|
||||||
<variable name="logContent" value="${message:withException=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="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" keepFileOpen="true" concurrentWrites="false" name="keen" layout="${var:logStamp} ${logger}: ${var:logContent}"
|
||||||
<target xsi:type="File" name="main" layout="${var:logStamp} ${logger}: ${var:logContent}" fileName="Logs\Torch-${shortdate}.log" />
|
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="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="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="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>
|
</targets>
|
||||||
|
|
||||||
<rules>
|
<rules>
|
||||||
|
@@ -29,7 +29,7 @@ namespace Torch
|
|||||||
int WindowHeight { get; set; }
|
int WindowHeight { get; set; }
|
||||||
int FontSize { get; set; }
|
int FontSize { get; set; }
|
||||||
UGCServiceType UgcServiceType { get; set; }
|
UGCServiceType UgcServiceType { get; set; }
|
||||||
|
bool EntityManagerEnabled { get; set; }
|
||||||
void Save(string path = null);
|
void Save(string path = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
21
Torch.API/Managers/IInstanceManager.cs
Normal file
21
Torch.API/Managers/IInstanceManager.cs
Normal file
@@ -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; }
|
||||||
|
}
|
@@ -23,6 +23,11 @@ namespace Torch.API.Session
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
MySession KeenSession { get; }
|
MySession KeenSession { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Currently running world
|
||||||
|
/// </summary>
|
||||||
|
IWorld World { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IDependencyManager"/>
|
/// <inheritdoc cref="IDependencyManager"/>
|
||||||
IDependencyManager Managers { get; }
|
IDependencyManager Managers { get; }
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NLog" Version="4.7.13" />
|
<PackageReference Include="NLog" Version="5.0.0-rc2" />
|
||||||
<PackageReference Include="SemanticVersioning" Version="2.0.0" />
|
<PackageReference Include="SemanticVersioning" Version="2.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<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" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -156,6 +157,10 @@ quit";
|
|||||||
gameThread.Start();
|
gameThread.Start();
|
||||||
|
|
||||||
var ui = new TorchUI(_server);
|
var ui = new TorchUI(_server);
|
||||||
|
|
||||||
|
SynchronizationContext.SetSynchronizationContext(
|
||||||
|
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
|
||||||
|
|
||||||
ui.ShowDialog();
|
ui.ShowDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,8 +197,9 @@ quit";
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
log.Info("Downloading SteamCMD.");
|
log.Info("Downloading SteamCMD.");
|
||||||
using (var client = new WebClient())
|
using (var client = new HttpClient())
|
||||||
client.DownloadFile("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip", STEAMCMD_ZIP);
|
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);
|
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, STEAMCMD_DIR);
|
||||||
File.Delete(STEAMCMD_ZIP);
|
File.Delete(STEAMCMD_ZIP);
|
||||||
|
51
Torch.Server/LogViewerTarget.cs
Normal file
51
Torch.Server/LogViewerTarget.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@ using System.ComponentModel;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Havok;
|
using Havok;
|
||||||
@@ -30,7 +31,7 @@ using VRage.Plugins;
|
|||||||
|
|
||||||
namespace Torch.Server.Managers
|
namespace Torch.Server.Managers
|
||||||
{
|
{
|
||||||
public class InstanceManager : Manager
|
public class InstanceManager : Manager, IInstanceManager
|
||||||
{
|
{
|
||||||
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
private const string CONFIG_NAME = "SpaceEngineers-Dedicated.cfg";
|
||||||
|
|
||||||
@@ -45,6 +46,8 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IWorld SelectedWorld => DedicatedConfig.SelectedWorld;
|
||||||
|
|
||||||
public void LoadInstance(string path, bool validate = true)
|
public void LoadInstance(string path, bool validate = true)
|
||||||
{
|
{
|
||||||
Log.Info($"Loading instance {path}");
|
Log.Info($"Loading instance {path}");
|
||||||
@@ -221,15 +224,12 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
public void SaveConfig()
|
public void SaveConfig()
|
||||||
{
|
{
|
||||||
if (((TorchServer)Torch).HasRun)
|
if (!((TorchServer)Torch).HasRun)
|
||||||
{
|
{
|
||||||
Log.Warn("Checkpoint cache is stale, not saving dedicated config.");
|
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
|
||||||
return;
|
Log.Info("Saved dedicated config.");
|
||||||
}
|
}
|
||||||
|
|
||||||
DedicatedConfig.Save(Path.Combine(Torch.Config.InstancePath, CONFIG_NAME));
|
|
||||||
Log.Info("Saved dedicated config.");
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var world = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == DedicatedConfig.LoadWorld) ?? new WorldViewModel(DedicatedConfig.LoadWorld);
|
var world = DedicatedConfig.Worlds.FirstOrDefault(x => x.WorldPath == DedicatedConfig.LoadWorld) ?? new WorldViewModel(DedicatedConfig.LoadWorld);
|
||||||
@@ -255,7 +255,7 @@ namespace Torch.Server.Managers
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
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);
|
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();
|
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
public string FolderName { get; set; }
|
public string FolderName { get; set; }
|
||||||
public string WorldPath { get; }
|
public string WorldPath { get; }
|
||||||
|
public MyObjectBuilder_SessionSettings KeenSessionSettings => WorldConfiguration.Settings;
|
||||||
|
public MyObjectBuilder_Checkpoint KeenCheckpoint => Checkpoint;
|
||||||
public long WorldSizeKB { get; }
|
public long WorldSizeKB { get; }
|
||||||
private string _checkpointPath;
|
private string _checkpointPath;
|
||||||
private string _worldConfigPath;
|
private string _worldConfigPath;
|
||||||
@@ -329,13 +331,15 @@ namespace Torch.Server.Managers
|
|||||||
|
|
||||||
public void LoadSandbox()
|
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);
|
Checkpoint = new CheckpointViewModel(checkpoint);
|
||||||
|
|
||||||
// migrate old saves
|
// migrate old saves
|
||||||
if (File.Exists(_worldConfigPath))
|
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);
|
WorldConfiguration = new WorldConfigurationViewModel(worldConfig);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -25,7 +25,7 @@ namespace Torch.Server
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
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.
|
//Ensures that all the files are downloaded in the Torch directory.
|
||||||
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory!.FullName;
|
var workingDir = new FileInfo(typeof(Program).Assembly.Location).Directory!.FullName;
|
||||||
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
var binDir = Path.Combine(workingDir, "DedicatedServer64");
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"Torch.Server": {
|
"Torch.Server": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "-noupdate",
|
||||||
"use64Bit": true,
|
"use64Bit": true,
|
||||||
"hotReloadEnabled": false
|
"hotReloadEnabled": false
|
||||||
}
|
}
|
||||||
|
@@ -39,12 +39,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoCompleteTextBox" Version="1.3.0" />
|
||||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||||
<PackageReference Include="ControlzEx" Version="5.0.1" />
|
<PackageReference Include="ControlzEx" Version="5.0.1" />
|
||||||
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
|
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
|
||||||
<PackageReference Include="MdXaml" Version="1.12.0" />
|
<PackageReference Include="MdXaml" Version="1.12.0" />
|
||||||
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.0.226801" />
|
<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="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Management" Version="6.0.0" />
|
<PackageReference Include="System.Management" Version="6.0.0" />
|
||||||
|
@@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Diagnostics.Runtime;
|
using Microsoft.Diagnostics.Runtime;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using PropertyChanged;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
@@ -212,6 +213,7 @@ namespace Torch.Server
|
|||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressPropertyChangedWarnings]
|
||||||
private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState)
|
private void OnSessionStateChanged(ITorchSession session, TorchSessionState newState)
|
||||||
{
|
{
|
||||||
if (newState == TorchSessionState.Unloading || newState == TorchSessionState.Unloaded)
|
if (newState == TorchSessionState.Unloading || newState == TorchSessionState.Unloaded)
|
||||||
|
@@ -18,11 +18,6 @@ namespace Torch.Server.ViewModels
|
|||||||
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
|
private MyConfigDedicated<MyObjectBuilder_SessionSettings> _config;
|
||||||
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Model => _config;
|
public MyConfigDedicated<MyObjectBuilder_SessionSettings> Model => _config;
|
||||||
|
|
||||||
public ConfigDedicatedViewModel() : this(new MyConfigDedicated<MyObjectBuilder_SessionSettings>(""))
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated)
|
public ConfigDedicatedViewModel(MyConfigDedicated<MyObjectBuilder_SessionSettings> configDedicated)
|
||||||
{
|
{
|
||||||
_config = configDedicated;
|
_config = configDedicated;
|
||||||
@@ -36,8 +31,7 @@ namespace Torch.Server.ViewModels
|
|||||||
Validate();
|
Validate();
|
||||||
|
|
||||||
_config.SessionSettings = SessionSettings;
|
_config.SessionSettings = SessionSettings;
|
||||||
// Never ever
|
_config.IgnoreLastSession = true;
|
||||||
//_config.IgnoreLastSession = true;
|
|
||||||
_config.Save(path);
|
_config.Save(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +67,9 @@ namespace Torch.Server.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateAllModInfosAsync()
|
public Task UpdateAllModInfosAsync()
|
||||||
{
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
/*if (!Mods.Any())
|
/*if (!Mods.Any())
|
||||||
return;
|
return;
|
||||||
List<MyWorkshopItem> modInfos;
|
List<MyWorkshopItem> modInfos;
|
||||||
|
13
Torch.Server/ViewModels/LogViewerViewModel.cs
Normal file
13
Torch.Server/ViewModels/LogViewerViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Torch.Server.ViewModels;
|
||||||
|
|
||||||
|
public class LogViewerViewModel : ViewModel
|
||||||
|
{
|
||||||
|
public ObservableCollection<LogEntry> LogEntries { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public record LogEntry(DateTime Timestamp, string Message, SolidColorBrush Color);
|
@@ -85,8 +85,9 @@ namespace Torch.Server.ViewModels
|
|||||||
/// via the Steam web API.
|
/// via the Steam web API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<bool> UpdateModInfoAsync()
|
public Task<bool> UpdateModInfoAsync()
|
||||||
{
|
{
|
||||||
|
return Task.FromResult(true);
|
||||||
/*if (UgcService.ToLower() == "mod.io")
|
/*if (UgcService.ToLower() == "mod.io")
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -104,7 +105,6 @@ namespace Torch.Server.ViewModels
|
|||||||
Log.Info("Mod Info successfully retrieved!");
|
Log.Info("Mod Info successfully retrieved!");
|
||||||
FriendlyName = modInfo.Title;
|
FriendlyName = modInfo.Title;
|
||||||
Description = modInfo.Description;*/
|
Description = modInfo.Description;*/
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
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">
|
mc:Ignorable="d">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Button Grid.Column="1" Content="Send" DockPanel.Dock="Right" Width="50" Margin="5" Click="SendButton_Click"></Button>
|
<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>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -28,7 +28,9 @@ using Torch.API.Managers;
|
|||||||
using Torch.API.Session;
|
using Torch.API.Session;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
|
using Torch.Server.Views;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
|
using Color = VRageMath.Color;
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
{
|
{
|
||||||
@@ -38,12 +40,17 @@ namespace Torch.Server
|
|||||||
public partial class ChatControl : UserControl
|
public partial class ChatControl : UserControl
|
||||||
{
|
{
|
||||||
private static Logger _log = LogManager.GetCurrentClassLogger();
|
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()
|
public ChatControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.IsVisibleChanged += OnIsVisibleChanged;
|
this.IsVisibleChanged += OnIsVisibleChanged;
|
||||||
|
MessageBox.Provider = new CommandSuggestionsProvider(_server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||||
@@ -57,8 +64,8 @@ namespace Torch.Server
|
|||||||
|
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
Message.Focus();
|
MessageBox.Focus();
|
||||||
Keyboard.Focus(Message);
|
Keyboard.Focus(MessageBox);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -160,35 +167,50 @@ namespace Torch.Server
|
|||||||
|
|
||||||
private void Message_OnKeyDown(object sender, KeyEventArgs e)
|
private void Message_OnKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Key == Key.Enter)
|
switch (e.Key)
|
||||||
OnMessageEntered();
|
{
|
||||||
|
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()
|
private void OnMessageEntered()
|
||||||
{
|
{
|
||||||
//Can't use Message.Text directly because of object ownership in WPF.
|
//Can't use Message.Text directly because of object ownership in WPF.
|
||||||
var text = Message.Text;
|
var text = MessageBox.Text;
|
||||||
if (string.IsNullOrEmpty(text))
|
if (string.IsNullOrEmpty(text))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
|
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
|
||||||
if (commands != null && commands.IsCommand(text))
|
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(() =>
|
_server.Invoke(() =>
|
||||||
{
|
{
|
||||||
if (!commands.HandleCommandFromServer(text, InsertMessage))
|
if (commands.HandleCommandFromServer(text, InsertMessage)) return;
|
||||||
{
|
InsertMessage(new(_server.Config.ChatName, "Invalid command.", Color.Red, _server.Config.ChatColor));
|
||||||
InsertMessage(new TorchChatMessage(TorchBase.Instance.Config.ChatName, "Invalid command.", TorchBase.Instance.Config.ChatColor));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_server.CurrentSession?.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf(text);
|
_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 = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
Torch.Server/Views/CommandSuggestionsProvider.cs
Normal file
48
Torch.Server/Views/CommandSuggestionsProvider.cs
Normal file
@@ -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<CommandManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ServerOnGameStateChanged(MySandboxGame game, TorchGameState newState)
|
||||||
|
{
|
||||||
|
if (_server.CurrentSession is { })
|
||||||
|
_commandManager = _server.CurrentSession.Managers.GetManager<CommandManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -16,9 +16,6 @@
|
|||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<UserControl.DataContext>
|
|
||||||
<viewModels:ConfigDedicatedViewModel />
|
|
||||||
</UserControl.DataContext>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -58,7 +55,7 @@
|
|||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="DediConfigScrollViewer">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
@@ -133,7 +130,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
<TabControl Grid.Column="1" Margin="3">
|
<TabControl Grid.Column="1" Margin="3">
|
||||||
<TabItem Header="World">
|
<TabItem Header="World">
|
||||||
<views:PropertyGrid DataContext="{Binding SessionSettings}" IgnoreDisplay ="True" />
|
<views:PropertyGrid DataContext="{Binding SessionSettings}" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Torch">
|
<TabItem Header="Torch">
|
||||||
<views:PropertyGrid x:Name="TorchSettings" />
|
<views:PropertyGrid x:Name="TorchSettings" />
|
||||||
|
@@ -8,6 +8,8 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using Sandbox;
|
||||||
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.Server.Annotations;
|
using Torch.Server.Annotations;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
@@ -32,15 +34,26 @@ namespace Torch.Server.Views
|
|||||||
|
|
||||||
public ConfigControl()
|
public ConfigControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
#pragma warning disable CS0618
|
||||||
_instanceManager = TorchBase.Instance.Managers.GetManager<InstanceManager>();
|
var instance = TorchBase.Instance;
|
||||||
|
#pragma warning restore CS0618
|
||||||
|
instance.GameStateChanged += InstanceOnGameStateChanged;
|
||||||
|
|
||||||
|
_instanceManager = instance.Managers.GetManager<InstanceManager>();
|
||||||
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
|
_instanceManager.InstanceLoaded += _instanceManager_InstanceLoaded;
|
||||||
DataContext = _instanceManager.DedicatedConfig;
|
DataContext = _instanceManager.DedicatedConfig;
|
||||||
TorchSettings.DataContext = (TorchConfig)TorchBase.Instance.Config;
|
InitializeComponent();
|
||||||
|
TorchSettings.DataContext = (TorchConfig)instance.Config;
|
||||||
// Gets called once all children are loaded
|
// Gets called once all children are loaded
|
||||||
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(ApplyStyles));
|
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()
|
private void CheckValid()
|
||||||
{
|
{
|
||||||
ConfigValid = !_bindingExpressions.Any(x => x.HasError);
|
ConfigValid = !_bindingExpressions.Any(x => x.HasError);
|
||||||
|
56
Torch.Server/Views/LogViewerControl.xaml
Normal file
56
Torch.Server/Views/LogViewerControl.xaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<UserControl x:Class="Torch.Server.Views.LogViewerControl"
|
||||||
|
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:local="clr-namespace:Torch.Server.Views"
|
||||||
|
xmlns:viewModels="clr-namespace:Torch.Server.ViewModels"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="300" d:DesignWidth="300">
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<viewModels:LogViewerViewModel />
|
||||||
|
</UserControl.DataContext>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<Style TargetType="ItemsControl" x:Key="LogViewerStyle">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate>
|
||||||
|
<ScrollViewer CanContentScroll="True" ScrollChanged="ScrollViewer_OnScrollChanged" Loaded="ScrollViewer_OnLoaded" Unloaded="ScrollViewer_OnUnloaded">
|
||||||
|
<ItemsPresenter />
|
||||||
|
</ScrollViewer>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
|
||||||
|
<Setter Property="ItemsPanel">
|
||||||
|
<Setter.Value>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<VirtualizingStackPanel IsItemsHost="True" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type TextBlock}" x:Key="TextBlockBaseStyle">
|
||||||
|
<Setter Property="FontFamily" Value="Consolas" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type viewModels:LogEntry}">
|
||||||
|
<Grid IsSharedSizeScope="True">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition SharedSizeGroup="Date" Width="Auto" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding Timestamp, StringFormat=HH:mm:ss.fff}" Grid.Column="0"
|
||||||
|
FontWeight="Bold" Style="{StaticResource TextBlockBaseStyle}" Foreground="{Binding Color}" Margin="5,0,5,0" />
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding Message}" Grid.Column="1"
|
||||||
|
TextWrapping="Wrap" Style="{StaticResource TextBlockBaseStyle}" Foreground="{Binding Color}" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<Grid Background="#0c0c0c">
|
||||||
|
<ItemsControl ItemsSource="{Binding LogEntries}" Style="{StaticResource LogViewerStyle}" />
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
50
Torch.Server/Views/LogViewerControl.xaml.cs
Normal file
50
Torch.Server/Views/LogViewerControl.xaml.cs
Normal file
@@ -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<ScrollViewer> _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);
|
||||||
|
}
|
||||||
|
}
|
@@ -25,9 +25,6 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<UserControl.DataContext>
|
|
||||||
<viewModels:ConfigDedicatedViewModel />
|
|
||||||
</UserControl.DataContext>
|
|
||||||
<Grid Style="{StaticResource RootGridStyle}">
|
<Grid Style="{StaticResource RootGridStyle}">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="500px"/>
|
<ColumnDefinition Width="500px"/>
|
||||||
|
@@ -17,11 +17,6 @@
|
|||||||
<converters:InverseBooleanConverter x:Key="InverseBool"/>
|
<converters:InverseBooleanConverter x:Key="InverseBool"/>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
<!--
|
|
||||||
<Window.DataContext>
|
|
||||||
<local:TorchServer/>
|
|
||||||
</Window.DataContext>
|
|
||||||
-->
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"></RowDefinition>
|
<RowDefinition Height="Auto"></RowDefinition>
|
||||||
@@ -63,10 +58,10 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TabControl Grid.Row="2" Height="Auto" x:Name="TabControl" Margin="5,10,5,5">
|
<TabControl Grid.Row="2" Height="Auto" x:Name="TabControl" Margin="5,10,5,5">
|
||||||
<TabItem Header="Log">
|
<TabItem Header="Log">
|
||||||
<RichTextBox x:Name="ConsoleText" VerticalScrollBarVisibility="Visible" FontFamily="Consolas" IsReadOnly="True" Background="#0c0c0c"/>
|
<views:LogViewerControl x:Name="ConsoleText" Margin="3" />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Configuration">
|
<TabItem Header="Configuration">
|
||||||
<Grid IsEnabled="{Binding Path=HasRun, Converter={StaticResource InverseBool}}">
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
@@ -93,7 +88,7 @@
|
|||||||
<local:PlayerListControl Grid.Column="1" x:Name="PlayerList" DockPanel.Dock="Right"/>
|
<local:PlayerListControl Grid.Column="1" x:Name="PlayerList" DockPanel.Dock="Right"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Entity Manager" x:Name="EntityManagerTab">
|
<TabItem Header="Entity Manager" x:Name="EntityManagerTab" IsEnabled="{Binding Config.EntityManagerEnabled}">
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Plugins">
|
<TabItem Header="Plugins">
|
||||||
<views:PluginsControl x:Name="Plugins" />
|
<views:PluginsControl x:Name="Plugins" />
|
||||||
|
@@ -14,12 +14,14 @@ using System.Windows.Media;
|
|||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Navigation;
|
using System.Windows.Navigation;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
|
using System.Windows.Threading;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Targets.Wrappers;
|
using NLog.Targets.Wrappers;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
|
using Torch.Server.ViewModels;
|
||||||
using Torch.Server.Views;
|
using Torch.Server.Views;
|
||||||
using MessageBoxResult = System.Windows.MessageBoxResult;
|
using MessageBoxResult = System.Windows.MessageBoxResult;
|
||||||
|
|
||||||
@@ -30,23 +32,22 @@ namespace Torch.Server
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class TorchUI : Window
|
public partial class TorchUI : Window
|
||||||
{
|
{
|
||||||
private TorchServer _server;
|
private readonly TorchServer _server;
|
||||||
private TorchConfig _config;
|
private ITorchConfig Config => _server.Config;
|
||||||
|
|
||||||
private bool _autoscrollLog = true;
|
|
||||||
|
|
||||||
public TorchUI(TorchServer server)
|
public TorchUI(TorchServer server)
|
||||||
{
|
{
|
||||||
WindowStartupLocation = WindowStartupLocation.Manual;
|
|
||||||
_config = (TorchConfig)server.Config;
|
|
||||||
Width = _config.WindowWidth;
|
|
||||||
Height = _config.WindowHeight;
|
|
||||||
_server = server;
|
_server = server;
|
||||||
//TODO: data binding for whole server
|
//TODO: data binding for whole server
|
||||||
DataContext = server;
|
DataContext = server;
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
AttachConsole();
|
WindowStartupLocation = WindowStartupLocation.Manual;
|
||||||
|
Width = Config.WindowWidth;
|
||||||
|
Height = Config.WindowHeight;
|
||||||
|
InitializeComponent();
|
||||||
|
ConsoleText.FontSize = Config.FontSize;
|
||||||
|
|
||||||
|
Loaded += OnLoaded;
|
||||||
|
|
||||||
//Left = _config.WindowPosition.X;
|
//Left = _config.WindowPosition.X;
|
||||||
//Top = _config.WindowPosition.Y;
|
//Top = _config.WindowPosition.Y;
|
||||||
@@ -56,94 +57,35 @@ namespace Torch.Server
|
|||||||
Chat.BindServer(server);
|
Chat.BindServer(server);
|
||||||
PlayerList.BindServer(server);
|
PlayerList.BindServer(server);
|
||||||
Plugins.BindServer(server);
|
Plugins.BindServer(server);
|
||||||
LoadConfig((TorchConfig)server.Config);
|
|
||||||
|
if (Config.EntityManagerEnabled)
|
||||||
|
{
|
||||||
|
EntityManagerTab.Content = new EntitiesControl();
|
||||||
|
}
|
||||||
|
|
||||||
Themes.uiSource = this;
|
Themes.uiSource = this;
|
||||||
Themes.SetConfig(_config);
|
Themes.SetConfig((TorchConfig) Config);
|
||||||
Title = $"{_config.InstanceName} - Torch {server.TorchVersion}, SE {server.GameVersion}";
|
Title = $"{Config.InstanceName} - Torch {server.TorchVersion}, SE {server.GameVersion}";
|
||||||
|
|
||||||
Loaded += TorchUI_Loaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TorchUI_Loaded(object sender, RoutedEventArgs e)
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var scrollViewer = FindDescendant<ScrollViewer>(ConsoleText);
|
AttachConsole();
|
||||||
scrollViewer.ScrollChanged += ConsoleText_OnScrollChanged;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AttachConsole()
|
private void AttachConsole()
|
||||||
{
|
{
|
||||||
const string target = "wpf";
|
const string targetName = "wpf";
|
||||||
var doc = LogManager.Configuration.FindTargetByName<FlowDocumentTarget>(target)?.Document;
|
var target = LogManager.Configuration.FindTargetByName<LogViewerTarget>(targetName);
|
||||||
if (doc == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
var wrapped = LogManager.Configuration.FindTargetByName<WrapperTargetBase>(target);
|
var wrapped = LogManager.Configuration.FindTargetByName<WrapperTargetBase>(targetName);
|
||||||
doc = (wrapped?.WrappedTarget as FlowDocumentTarget)?.Document;
|
target = wrapped?.WrappedTarget as LogViewerTarget;
|
||||||
}
|
}
|
||||||
ConsoleText.FontSize = _config.FontSize;
|
if (target is null) return;
|
||||||
ConsoleText.Document = doc ?? new FlowDocument(new Paragraph(new Run("No target!")));
|
var viewModel = (LogViewerViewModel)ConsoleText.DataContext;
|
||||||
ConsoleText.TextChanged += ConsoleText_OnTextChanged;
|
target.LogEntries = viewModel.LogEntries;
|
||||||
}
|
target.TargetContext = SynchronizationContext.Current;
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnStart_Click(object sender, RoutedEventArgs e)
|
private void BtnStart_Click(object sender, RoutedEventArgs e)
|
||||||
@@ -176,21 +118,5 @@ namespace Torch.Server
|
|||||||
|
|
||||||
Process.GetCurrentProcess().Kill();
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
<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" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -50,11 +50,12 @@ namespace Torch.Commands
|
|||||||
return !string.IsNullOrEmpty(command) && command[0] == Prefix;
|
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)))
|
if (!moduleType.IsSubclassOf(typeof(CommandModule)))
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
foreach (var method in moduleType.GetMethods())
|
foreach (var method in moduleType.GetMethods())
|
||||||
{
|
{
|
||||||
var commandAttrib = method.GetCustomAttribute<CommandAttribute>();
|
var commandAttrib = method.GetCustomAttribute<CommandAttribute>();
|
||||||
@@ -63,11 +64,14 @@ namespace Torch.Commands
|
|||||||
|
|
||||||
var command = new Command(plugin, method);
|
var command = new Command(plugin, method);
|
||||||
var cmdPath = string.Join(".", command.Path);
|
var cmdPath = string.Join(".", command.Path);
|
||||||
_log.Info($"Registering command '{cmdPath}'");
|
_log.Debug($"Registering command '{cmdPath}'");
|
||||||
|
i++;
|
||||||
|
|
||||||
if (!Commands.AddCommand(command))
|
if (!Commands.AddCommand(command))
|
||||||
_log.Error($"Command path {cmdPath} is already registered.");
|
_log.Error($"Command path {cmdPath} is already registered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnregisterPluginCommands(ITorchPlugin plugin)
|
public void UnregisterPluginCommands(ITorchPlugin plugin)
|
||||||
@@ -78,10 +82,9 @@ namespace Torch.Commands
|
|||||||
public void RegisterPluginCommands(ITorchPlugin plugin)
|
public void RegisterPluginCommands(ITorchPlugin plugin)
|
||||||
{
|
{
|
||||||
var assembly = plugin.GetType().Assembly;
|
var assembly = plugin.GetType().Assembly;
|
||||||
foreach (var type in assembly.ExportedTypes)
|
var count = assembly.ExportedTypes.Sum(type => RegisterCommandModule(type, plugin));
|
||||||
{
|
if (count > 0)
|
||||||
RegisterCommandModule(type, plugin);
|
_log.Info($"Registered {count} commands from {plugin.Name}");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TorchChatMessage> HandleCommandFromServerInternal(string message, Action<TorchChatMessage> subscriber = null)
|
private List<TorchChatMessage> HandleCommandFromServerInternal(string message, Action<TorchChatMessage> subscriber = null)
|
||||||
|
@@ -23,6 +23,12 @@ namespace Torch.Managers.PatchManager
|
|||||||
{
|
{
|
||||||
private static Action<ILHook, bool> IsAppliedSetter;
|
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 static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
private readonly MethodBase _method;
|
private readonly MethodBase _method;
|
||||||
|
|
||||||
@@ -103,6 +109,7 @@ namespace Torch.Managers.PatchManager
|
|||||||
public const string INSTANCE_PARAMETER = "__instance";
|
public const string INSTANCE_PARAMETER = "__instance";
|
||||||
public const string RESULT_PARAMETER = "__result";
|
public const string RESULT_PARAMETER = "__result";
|
||||||
public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped";
|
public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped";
|
||||||
|
public const string ORIGINAL_PARAMETER = "__original";
|
||||||
public const string LOCAL_PARAMETER = "__local";
|
public const string LOCAL_PARAMETER = "__local";
|
||||||
|
|
||||||
private void SavePatchedMethod(string target)
|
private void SavePatchedMethod(string target)
|
||||||
@@ -320,6 +327,24 @@ namespace Torch.Managers.PatchManager
|
|||||||
yield return new MsilInstruction(OpCodes.Ldarg_0);
|
yield return new MsilInstruction(OpCodes.Ldarg_0);
|
||||||
break;
|
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:
|
case PREFIX_SKIPPED_PARAMETER:
|
||||||
{
|
{
|
||||||
if (param.ParameterType != typeof(bool))
|
if (param.ParameterType != typeof(bool))
|
||||||
|
39
Torch/Patches/EntityIdentifierPatch.cs
Normal file
39
Torch/Patches/EntityIdentifierPatch.cs
Normal file
@@ -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<object> _perThreadGetter = null!;
|
||||||
|
|
||||||
|
[ReflectedGetter(TypeName = "VRage.MyEntityIdentifier+PerThreadData, VRage.Game", Name = "EntityList")]
|
||||||
|
private static Func<object, Dictionary<long, IMyEntity>> _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<IMyEntity> 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,7 @@ using NLog;
|
|||||||
using Torch.API;
|
using Torch.API;
|
||||||
using Torch.Managers.PatchManager;
|
using Torch.Managers.PatchManager;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
|
using VRage;
|
||||||
using VRage.Utils;
|
using VRage.Utils;
|
||||||
|
|
||||||
namespace Torch.Patches
|
namespace Torch.Patches
|
||||||
@@ -42,71 +43,81 @@ namespace Torch.Patches
|
|||||||
|
|
||||||
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLineAndConsole), Parameters = new[] { typeof(string) })]
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLineAndConsole), Parameters = new[] { typeof(string) })]
|
||||||
private static MethodInfo _logWriteLineAndConsole;
|
private static MethodInfo _logWriteLineAndConsole;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Init))]
|
||||||
|
private static MethodInfo _logInit;
|
||||||
#pragma warning restore 649
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
|
||||||
public static void Patch(PatchContext context)
|
public static void Patch(PatchContext context)
|
||||||
{
|
{
|
||||||
context.GetPattern(_logStringBuilder).Prefixes.Add(Method(nameof(PrefixLogStringBuilder)));
|
context.GetPattern(_logStringBuilder).AddPrefix(nameof(PrefixLogStringBuilder));
|
||||||
context.GetPattern(_logFormatted).Prefixes.Add(Method(nameof(PrefixLogFormatted)));
|
context.GetPattern(_logFormatted).AddPrefix(nameof(PrefixLogFormatted));
|
||||||
|
|
||||||
context.GetPattern(_logWriteLine).Prefixes.Add(Method(nameof(PrefixWriteLine)));
|
context.GetPattern(_logWriteLine).AddPrefix(nameof(PrefixWriteLine));
|
||||||
context.GetPattern(_logAppendToClosedLog).Prefixes.Add(Method(nameof(PrefixAppendToClosedLog)));
|
context.GetPattern(_logAppendToClosedLog).AddPrefix(nameof(PrefixAppendToClosedLog));
|
||||||
context.GetPattern(_logWriteLineAndConsole).Prefixes.Add(Method(nameof(PrefixWriteLineConsole)));
|
context.GetPattern(_logWriteLineAndConsole).AddPrefix(nameof(PrefixWriteLineConsole));
|
||||||
|
|
||||||
context.GetPattern(_logWriteLineException).Prefixes.Add(Method(nameof(PrefixWriteLineException)));
|
context.GetPattern(_logWriteLineException).AddPrefix(nameof(PrefixWriteLineException));
|
||||||
context.GetPattern(_logAppendToClosedLogException).Prefixes.Add(Method(nameof(PrefixAppendToClosedLogException)));
|
context.GetPattern(_logAppendToClosedLogException).AddPrefix(nameof(PrefixAppendToClosedLogException));
|
||||||
|
|
||||||
context.GetPattern(_logWriteLineOptions).Prefixes.Add(Method(nameof(PrefixWriteLineOptions)));
|
context.GetPattern(_logWriteLineOptions).AddPrefix(nameof(PrefixWriteLineOptions));
|
||||||
|
|
||||||
}
|
context.GetPattern(_logInit).AddPrefix(nameof(PrefixInit));
|
||||||
|
|
||||||
private static MethodInfo Method(string name)
|
|
||||||
{
|
|
||||||
return typeof(KeenLogPatch).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ReflectedMethod(Name = "GetIdentByThread")]
|
[ReflectedMethod(Name = "GetIdentByThread")]
|
||||||
private static Func<MyLog, int, int> GetIndentByThread = null!;
|
private static Func<MyLog, int, int> _getIndentByThread = null!;
|
||||||
|
|
||||||
[ThreadStatic]
|
[ReflectedGetter(Name = "m_lock")]
|
||||||
private static StringBuilder _tmpStringBuilder;
|
private static Func<MyLog, FastResourceLock> _lockGetter = null!;
|
||||||
|
|
||||||
private static StringBuilder PrepareLog(MyLog log)
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
_tmpStringBuilder.Clear();
|
private static bool PrefixInit(MyLog __instance, StringBuilder appVersionString)
|
||||||
var t = GetIndentByThread(log, Environment.CurrentManagedThreadId);
|
{
|
||||||
|
__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.Append(' ', t * 3);
|
byThreadField.SetValue(__instance, Activator.CreateInstance(byThreadField.FieldType));
|
||||||
return _tmpStringBuilder;
|
indentsField.SetValue(__instance, Activator.CreateInstance(indentsField.FieldType));
|
||||||
|
_enabledSetter(__instance, true);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PrefixWriteLine(MyLog __instance, string msg)
|
private static bool PrefixWriteLine(MyLog __instance, string msg)
|
||||||
{
|
{
|
||||||
if (__instance.LogEnabled)
|
if (__instance.LogEnabled && _log.IsDebugEnabled)
|
||||||
_log.Debug(PrepareLog(__instance).Append(msg));
|
_log.Debug($"{" ".PadRight(3 * GetIndentByCurrentThread())}{msg}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PrefixWriteLineConsole(MyLog __instance, string msg)
|
private static bool PrefixWriteLineConsole(MyLog __instance, string msg)
|
||||||
{
|
{
|
||||||
if (__instance.LogEnabled)
|
if (__instance.LogEnabled && _log.IsInfoEnabled)
|
||||||
_log.Info(PrepareLog(__instance).Append(msg));
|
_log.Info($"{" ".PadRight(3 * GetIndentByCurrentThread())}{msg}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PrefixAppendToClosedLog(MyLog __instance, string text)
|
private static bool PrefixAppendToClosedLog(MyLog __instance, string text)
|
||||||
{
|
{
|
||||||
if (__instance.LogEnabled)
|
if (__instance.LogEnabled && _log.IsDebugEnabled)
|
||||||
_log.Info(PrepareLog(__instance).Append(text));
|
_log.Debug($"{" ".PadRight(3 * GetIndentByCurrentThread())}{text}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option)
|
private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option)
|
||||||
{
|
{
|
||||||
if (__instance.LogEnabled && __instance.LogFlag(option))
|
if (__instance.LogEnabled && __instance.LogFlag(option) && _log.IsDebugEnabled)
|
||||||
_log.Info(PrepareLog(__instance).Append(message));
|
_log.Info($"{" ".PadRight(3 * GetIndentByCurrentThread())}{message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,22 +137,22 @@ namespace Torch.Patches
|
|||||||
{
|
{
|
||||||
if (__instance.LogEnabled)
|
if (__instance.LogEnabled)
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PrefixLogStringBuilder(MyLog __instance, MyLogSeverity severity, StringBuilder builder)
|
private static bool PrefixLogStringBuilder(MyLog __instance, MyLogSeverity severity, StringBuilder builder)
|
||||||
{
|
{
|
||||||
if (__instance.LogEnabled)
|
if (!__instance.LogEnabled) return false;
|
||||||
_log.Log(LogLevelFor(severity), PrepareLog(__instance).Append(builder));
|
var indent = GetIndentByCurrentThread() * 3;
|
||||||
|
|
||||||
|
// because append resizes every char
|
||||||
|
builder.EnsureCapacity(indent);
|
||||||
|
builder.Append(' ', indent);
|
||||||
|
|
||||||
|
_log.Log(LogLevelFor(severity), builder);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
Torch/Patches/LoaderHook.cs
Normal file
68
Torch/Patches/LoaderHook.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -26,13 +26,16 @@ namespace Torch.Session
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MySession KeenSession { get; }
|
public MySession KeenSession { get; }
|
||||||
|
|
||||||
|
public IWorld World { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IDependencyManager"/>
|
/// <inheritdoc cref="IDependencyManager"/>
|
||||||
public IDependencyManager Managers { get; }
|
public IDependencyManager Managers { get; }
|
||||||
|
|
||||||
public TorchSession(ITorchBase torch, MySession keenSession)
|
public TorchSession(ITorchBase torch, MySession keenSession, IWorld world)
|
||||||
{
|
{
|
||||||
Torch = torch;
|
Torch = torch;
|
||||||
KeenSession = keenSession;
|
KeenSession = keenSession;
|
||||||
|
World = world;
|
||||||
Managers = new DependencyManager(torch.Managers);
|
Managers = new DependencyManager(torch.Managers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,6 +28,9 @@ namespace Torch.Session
|
|||||||
|
|
||||||
private readonly Dictionary<ulong, MyObjectBuilder_Checkpoint.ModItem> _overrideMods;
|
private readonly Dictionary<ulong, MyObjectBuilder_Checkpoint.ModItem> _overrideMods;
|
||||||
|
|
||||||
|
[Dependency]
|
||||||
|
private IInstanceManager _instanceManager = null!;
|
||||||
|
|
||||||
public event Action<CollectionChangeEventArgs> OverrideModsChanged;
|
public event Action<CollectionChangeEventArgs> OverrideModsChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -101,15 +104,18 @@ namespace Torch.Session
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (_instanceManager.SelectedWorld is null)
|
||||||
|
throw new InvalidOperationException("No valid worlds selected! Please select world first.");
|
||||||
|
|
||||||
if (_currentSession != null)
|
if (_currentSession != null)
|
||||||
{
|
{
|
||||||
_log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
|
_log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
|
||||||
_currentSession.Detach();
|
_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);
|
SetState(TorchSessionState.Loading);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -123,11 +129,9 @@ namespace Torch.Session
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_currentSession == null)
|
if (_currentSession is null)
|
||||||
{
|
throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
|
||||||
_log.Warn("Session loaded event occurred when we don't have a session.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
foreach (SessionManagerFactoryDel factory in _factories)
|
foreach (SessionManagerFactoryDel factory in _factories)
|
||||||
{
|
{
|
||||||
IManager manager = factory(CurrentSession);
|
IManager manager = factory(CurrentSession);
|
||||||
@@ -135,7 +139,7 @@ namespace Torch.Session
|
|||||||
CurrentSession.Managers.AddManager(manager);
|
CurrentSession.Managers.AddManager(manager);
|
||||||
}
|
}
|
||||||
(CurrentSession as TorchSession)?.Attach();
|
(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);
|
SetState(TorchSessionState.Loaded);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -149,12 +153,10 @@ namespace Torch.Session
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_currentSession == null)
|
if (_currentSession is null)
|
||||||
{
|
throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
|
||||||
_log.Warn("Session unloading event occurred when we don't have a session.");
|
|
||||||
return;
|
_log.Info($"Unloading torch session for {_currentSession.World.FolderName}");
|
||||||
}
|
|
||||||
_log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
|
|
||||||
SetState(TorchSessionState.Unloading);
|
SetState(TorchSessionState.Unloading);
|
||||||
_currentSession.Detach();
|
_currentSession.Detach();
|
||||||
}
|
}
|
||||||
@@ -169,12 +171,10 @@ namespace Torch.Session
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_currentSession == null)
|
if (_currentSession is null)
|
||||||
{
|
throw new InvalidOperationException("Session loaded event occurred when we don't have a session.");
|
||||||
_log.Warn("Session unloading event occurred when we don't have a session.");
|
|
||||||
return;
|
_log.Info($"Unloaded torch session for {_currentSession.World.FolderName}");
|
||||||
}
|
|
||||||
_log.Info($"Unloaded torch session for {_currentSession.KeenSession.Name}");
|
|
||||||
SetState(TorchSessionState.Unloaded);
|
SetState(TorchSessionState.Unloaded);
|
||||||
_currentSession = null;
|
_currentSession = null;
|
||||||
}
|
}
|
||||||
|
@@ -20,10 +20,10 @@
|
|||||||
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ControlzEx" Version="5.0.1" />
|
<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="MahApps.Metro" Version="2.4.9" />
|
||||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.1.4.3" />
|
<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="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="Torch.SixLabors.ImageSharp" Version="1.0.0-beta6" />
|
<PackageReference Include="Torch.SixLabors.ImageSharp" Version="1.0.0-beta6" />
|
||||||
|
Reference in New Issue
Block a user