Compare commits

...

6 Commits
1.4.2 ... 1.4.4

Author SHA1 Message Date
zznty
067d8802b6 change instance path resolving back to bin folder
i dont think we need to place our instance at game dir by default
2022-10-15 00:55:44 +07:00
zznty
8f32f64ede change script compilation manager lifetime
seems game loads faster than torch internals
2022-10-15 00:50:10 +07:00
zznty
90ff3f93f0 disable gc patch because conflicting with the same from plugin 2022-10-15 00:44:49 +07:00
zznty
ad28b302f9 add harmony logging 2022-10-15 00:42:23 +07:00
zznty
a0d0976a6a c# 10 and assembly unloading for in-game scripts 2022-10-15 00:16:00 +07:00
zznty
a9c9a0de68 refactor restart and config handling 2022-10-12 16:36:25 +07:00
8 changed files with 386 additions and 281 deletions

View File

@@ -90,8 +90,6 @@ namespace Torch.Server
var gamePath = configuration.GetValue("gamePath", workingDir); var gamePath = configuration.GetValue("gamePath", workingDir);
var binDir = Path.Combine(gamePath, "DedicatedServer64"); var binDir = Path.Combine(gamePath, "DedicatedServer64");
Directory.SetCurrentDirectory(gamePath);
var instanceName = configuration.GetValue("instanceName", "Instance"); var instanceName = configuration.GetValue("instanceName", "Instance");
string instancePath; string instancePath;
@@ -105,6 +103,8 @@ namespace Torch.Server
instancePath = Directory.CreateDirectory(instanceName!).FullName; instancePath = Directory.CreateDirectory(instanceName!).FullName;
} }
Directory.SetCurrentDirectory(gamePath);
return new ApplicationContext(new(workingDir), new(gamePath), new(binDir), return new ApplicationContext(new(workingDir), new(gamePath), new(binDir),
new(instancePath), instanceName, isService); new(instancePath), instanceName, isService);
} }

View File

@@ -1,167 +1,114 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Xml.Serialization; using System.Xml.Serialization;
using NLog;
using Torch.API; using Torch.API;
using Torch.Views; using Torch.Views;
namespace Torch.Server namespace Torch.Server;
{
// TODO: redesign this gerbage
public class TorchConfig : CommandLine, ITorchConfig, INotifyPropertyChanged
{
private static Logger _log = LogManager.GetLogger("Config");
public class TorchConfig : ViewModel, ITorchConfig
{
public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate; public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate;
public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate; public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate;
private bool _autostart;
private bool _restartOnCrash;
private bool _noGui;
private bool _getPluginUpdates = true;
private bool _getTorchUpdates = true;
private int _tickTimeout = 60;
private bool _localPlugins;
private bool _disconnectOnRestart;
private string _chatName = "Server";
private string _chatColor = "Red";
private bool _enableWhitelist = false;
private List<ulong> _whitelist = new List<ulong>();
private int _windowWidth = 980;
private int _windowHeight = 588;
private bool _independentConsole = false;
private bool _enableAsserts = false;
private int _fontSize = 16;
private UGCServiceType _ugcServiceType = UGCServiceType.Steam;
private bool _entityManagerEnabled = true;
/// <inheritdoc /> /// <inheritdoc />
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")] [XmlIgnore]
public bool NoUpdate { get; set; } public bool NoUpdate { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[XmlIgnore, Arg("forceupdate", "Manually check for and install updates.")] [XmlIgnore]
public bool ForceUpdate { get; set; } public bool ForceUpdate { get; set; }
/// <summary> /// <summary>
/// Permanent flag to ALWAYS automatically start the server /// Permanent flag to ALWAYS automatically start the server
/// </summary> /// </summary>
[Display(Name = "Auto Start", Description = "Permanent flag to ALWAYS automatically start the server.", GroupName = "Server")] [Display(Name = "Auto Start", Description = "Permanent flag to ALWAYS automatically start the server.", GroupName = "Server")]
public bool Autostart { get => _autostart; set => Set(value, ref _autostart); } public bool Autostart { get; set; }
/// <summary> /// <summary>
/// Temporary flag to automatically start the server only on the next run /// Temporary flag to automatically start the server only on the next run
/// </summary> /// </summary>
[Arg("autostart", "Start the server immediately.")]
[XmlIgnore] [XmlIgnore]
public bool TempAutostart { get; set; } public bool TempAutostart { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
[Display(Name = "Restart On Crash", Description = "Automatically restart the server if it crashes.", GroupName = "Server")] [Display(Name = "Restart On Crash", Description = "Automatically restart the server if it crashes.", GroupName = "Server")]
public bool RestartOnCrash { get => _restartOnCrash; set => Set(value, ref _restartOnCrash); } public bool RestartOnCrash { get; set; }
public string InstancePath { get; set; } public string InstancePath { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Arg("nogui", "Do not show the Torch UI.")]
[Display(Name = "No GUI", Description = "Do not show the Torch UI.", GroupName = "Window")] [Display(Name = "No GUI", Description = "Do not show the Torch UI.", GroupName = "Window")]
public bool NoGui { get => _noGui; set => Set(value, ref _noGui); } public bool NoGui { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Display(Name = "Update Torch", Description = "Check every start for new versions of torch.", GroupName = "Server")] [Display(Name = "Update Torch", Description = "Check every start for new versions of torch.",
public bool GetTorchUpdates { get => _getTorchUpdates; set => Set(value, ref _getTorchUpdates); } GroupName = "Server")]
public bool GetTorchUpdates { get; set; } = true;
public string InstanceName { get; set; } public string InstanceName { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[Display(Name = "Update Plugins", Description = "Check every start for new versions of plugins.", GroupName = "Server")] [Display(Name = "Update Plugins", Description = "Check every start for new versions of plugins.",
public bool GetPluginUpdates { get => _getPluginUpdates; set => Set(value, ref _getPluginUpdates); } GroupName = "Server")]
public bool GetPluginUpdates { get; set; } = true;
/// <inheritdoc /> /// <inheritdoc />
[Display(Name = "Watchdog Timeout", Description = "Watchdog timeout (in seconds).", GroupName = "Server")] [Display(Name = "Watchdog Timeout", Description = "Watchdog timeout (in seconds).", GroupName = "Server")]
public int TickTimeout { get => _tickTimeout; set => Set(value, ref _tickTimeout); } public int TickTimeout { get; set; } = 60;
/// <inheritdoc /> /// <inheritdoc />
[Arg("plugins", "Starts Torch with the given plugin GUIDs (space delimited).")] public List<Guid> Plugins { get; set; } = new();
public List<Guid> Plugins { get; set; } = new List<Guid>();
[Arg("localplugins", "Loads all pluhins from disk, ignores the plugins defined in config.")]
[Display(Name = "Local Plugins", Description = "Loads all pluhins from disk, ignores the plugins defined in config.", GroupName = "In-Game")] [Display(Name = "Local Plugins", Description = "Loads all pluhins from disk, ignores the plugins defined in config.", GroupName = "In-Game")]
public bool LocalPlugins { get => _localPlugins; set => Set(value, ref _localPlugins); } public bool LocalPlugins { get; set; }
[Arg("disconnect", "When server restarts, all clients are rejected to main menu to prevent auto rejoin.")]
[Display(Name = "Auto Disconnect", Description = "When server restarts, all clients are rejected to main menu to prevent auto rejoin.", GroupName = "In-Game")] [Display(Name = "Auto Disconnect", Description = "When server restarts, all clients are rejected to main menu to prevent auto rejoin.", GroupName = "In-Game")]
public bool DisconnectOnRestart { get => _disconnectOnRestart; set => Set(value, ref _disconnectOnRestart); } public bool DisconnectOnRestart { get; set; }
[Display(Name = "Chat Name", Description = "Default name for chat from gui, broadcasts etc..", GroupName = "In-Game")] [Display(Name = "Chat Name", Description = "Default name for chat from gui, broadcasts etc..",
public string ChatName { get => _chatName; set => Set(value, ref _chatName); } GroupName = "In-Game")]
public string ChatName { get; set; } = "Server";
[Display(Name = "Chat Color", Description = "Default color for chat from gui, broadcasts etc.. (Red, Blue, White, Green)", GroupName = "In-Game")] [Display(Name = "Chat Color",
public string ChatColor { get => _chatColor; set => Set(value, ref _chatColor); } Description = "Default color for chat from gui, broadcasts etc.. (Red, Blue, White, Green)",
GroupName = "In-Game")]
public string ChatColor { get; set; } = "Red";
[Display(Name = "Enable Whitelist", Description = "Enable Whitelist to prevent random players join while maintance, tests or other.", GroupName = "In-Game")] [Display(Name = "Enable Whitelist", Description = "Enable Whitelist to prevent random players join while maintance, tests or other.", GroupName = "In-Game")]
public bool EnableWhitelist { get => _enableWhitelist; set => Set(value, ref _enableWhitelist); } public bool EnableWhitelist { get; set; }
[Display(Name = "Whitelist", Description = "Collection of whitelisted steam ids.", GroupName = "In-Game")] [Display(Name = "Whitelist", Description = "Collection of whitelisted steam ids.", GroupName = "In-Game")]
public List<ulong> Whitelist { get => _whitelist; set => Set(value, ref _whitelist); } public List<ulong> Whitelist { get; set; } = new();
[Display(Name = "Width", Description = "Default window width.", GroupName = "Window")] [Display(Name = "Width", Description = "Default window width.", GroupName = "Window")]
public int WindowWidth { get => _windowWidth; set => Set(value, ref _windowWidth); } public int WindowWidth { get; set; } = 980;
[Display(Name = "Height", Description = "Default window height", GroupName = "Window")] [Display(Name = "Height", Description = "Default window height", GroupName = "Window")]
public int WindowHeight { get => _windowHeight; set => Set(value, ref _windowHeight); } public int WindowHeight { get; set; } = 588;
[Display(Name = "Font Size", Description = "Font size for logging text box. (default is 16)", GroupName = "Window")] [Display(Name = "Font Size", Description = "Font size for logging text box. (default is 16)",
public int FontSize { get => _fontSize; set => Set(value, ref _fontSize); } GroupName = "Window")]
public int FontSize { get; set; } = 16;
[Display(Name = "UGC Service Type", Description = "Service for downloading mods", GroupName = "Server")] [Display(Name = "UGC Service Type", Description = "Service for downloading mods", GroupName = "Server")]
public UGCServiceType UgcServiceType public UGCServiceType UgcServiceType { get; set; } = UGCServiceType.Steam;
{
get => _ugcServiceType;
set => Set(value, ref _ugcServiceType);
}
public string LastUsedTheme { get; set; } = "Torch Theme"; public string LastUsedTheme { get; set; } = "Torch Theme";
//Prevent reserved players being written to disk, but allow it to be read
//remove this when ReservedPlayers is removed
private bool ShouldSerializeReservedPlayers() => false;
[Arg("console", "Keeps a separate console window open after the main UI loads.")]
[Display(Name = "Independent Console", Description = "Keeps a separate console window open after the main UI loads.", GroupName = "Window")] [Display(Name = "Independent Console", Description = "Keeps a separate console window open after the main UI loads.", GroupName = "Window")]
public bool IndependentConsole { get => _independentConsole; set => Set(value, ref _independentConsole); } public bool IndependentConsole { get; set; }
[XmlIgnore] [XmlIgnore]
[Arg("testplugin", "Path to a plugin to debug. For development use only.")]
public string TestPlugin { get; set; } public string TestPlugin { get; set; }
[Arg("asserts", "Enable Keen's assert logging.")]
[Display(Name = "Enable Asserts", Description = "Enable Keen's assert logging.", GroupName = "Server")] [Display(Name = "Enable Asserts", Description = "Enable Keen's assert logging.", GroupName = "Server")]
public bool EnableAsserts { get => _enableAsserts; set => Set(value, ref _enableAsserts); } public bool EnableAsserts { get; set; }
[Display(Name = "Enable Entity Manager", Description = "Enable Entity Manager tab. (can affect performance)", [Display(Name = "Enable Entity Manager", Description = "Enable Entity Manager tab. (can affect performance)",
GroupName = "Server")] GroupName = "Server")]
public bool EntityManagerEnabled public bool EntityManagerEnabled { get; set; } = true;
{
get => _entityManagerEnabled;
set => Set(value, ref _entityManagerEnabled);
}
public event PropertyChangedEventHandler PropertyChanged;
public TorchConfig() { }
protected void Set<T>(T value, ref T field, [CallerMemberName] string callerName = default)
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(callerName));
}
// for backward compatibility // for backward compatibility
public void Save(string path = null) => Initializer.Instance?.ConfigPersistent?.Save(path); public void Save(string path = null) => Initializer.Instance?.ConfigPersistent?.Save(path);
} }
}

View File

@@ -205,16 +205,30 @@ namespace Torch.Server
new Thread(() => new Thread(() =>
{ {
StopInternal(); StopInternal();
var config = (TorchConfig)Config;
LogManager.Flush(); LogManager.Flush();
string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe"); #if DEBUG
config.TempAutostart = true;
Process.Start(exe, $"-waitForPid {Environment.ProcessId} {config}");
Environment.Exit(0); Environment.Exit(0);
#endif
var exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe");
var args = Environment.GetCommandLineArgs();
for (var i = 0; i < args.Length; i++)
{
if (args[i].Contains(' '))
args[i] = $"\"{args[i]}\"";
if (!args[i].Contains("--tempAutostart", StringComparison.InvariantCultureIgnoreCase) &&
!args[i].Contains("--waitForPid", StringComparison.InvariantCultureIgnoreCase))
continue;
args[i] = string.Empty;
args[++i] = string.Empty;
}
Process.Start(exe, $"--waitForPid {Environment.ProcessId} --tempAutostart true {string.Join(" ", args)}");
}) })
{ {
Name = "Restart thread" Name = "Restart thread"

View File

@@ -1,138 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using NLog;
namespace Torch
{
/// <summary>
/// Base class that adds tools for setting type properties through the command line.
/// </summary>
public abstract class CommandLine
{
private readonly string _argPrefix;
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
private readonly Logger _log = LogManager.GetCurrentClassLogger();
protected CommandLine(string argPrefix = "-")
{
_argPrefix = argPrefix;
foreach (var prop in GetType().GetProperties())
{
var attr = prop.GetCustomAttribute<ArgAttribute>();
if (attr == null)
continue;
_args.Add(attr, prop);
}
}
public string GetHelp()
{
var sb = new StringBuilder();
foreach (var property in _args)
{
var attr = property.Key;
sb.AppendLine($"{_argPrefix}{attr.Name.PadRight(24)}{attr.Description}");
}
return sb.ToString();
}
public override string ToString()
{
var args = new List<string>();
foreach (var prop in _args)
{
var attr = prop.Key;
if (prop.Value.PropertyType == typeof(bool) && (bool)prop.Value.GetValue(this))
{
args.Add($"{_argPrefix}{attr.Name}");
}
else if (prop.Value.PropertyType == typeof(string))
{
var str = (string)prop.Value.GetValue(this);
if (string.IsNullOrEmpty(str))
continue;
args.Add($"{_argPrefix}{attr.Name} \"{str}\"");
}
}
return string.Join(" ", args);
}
public bool Parse(string[] args)
{
if (args.Length == 0)
return true;
if (args[0] == $"{_argPrefix}help")
{
Console.WriteLine(GetHelp());
return false;
}
for (var i = 0; i < args.Length; i++)
{
if (!args[i].StartsWith(_argPrefix))
continue;
foreach (var property in _args)
{
var argName = property.Key.Name;
if (argName == null)
continue;
try
{
if (string.Compare(argName, 0, args[i], 1, argName.Length, StringComparison.InvariantCultureIgnoreCase) == 0)
{
if (property.Value.PropertyType == typeof(bool))
property.Value.SetValue(this, true);
if (property.Value.PropertyType == typeof(string))
property.Value.SetValue(this, args[++i]);
if (property.Value.PropertyType == typeof(List<Guid>))
{
i++;
var l = new List<Guid>(16);
while (i < args.Length && !args[i].StartsWith(_argPrefix))
{
if (Guid.TryParse(args[i], out Guid g))
{
l.Add(g);
_log.Info($"added plugin {g}");
}
else
_log.Warn($"Failed to parse GUID {args[i]}");
i++;
}
property.Value.SetValue(this, l);
}
}
}
catch
{
Console.WriteLine($"Error parsing arg {argName}");
}
}
}
return true;
}
public class ArgAttribute : Attribute
{
public string Name { get; }
public string Description { get; }
public ArgAttribute(string name, string description)
{
Name = name;
Description = description;
}
}
}
}

View File

@@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Emit;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.EntityComponents;
using Sandbox.ModAPI;
using Sandbox.ModAPI.Ingame;
using Torch.API;
using Torch.Utils;
using VRage;
using VRage.ModAPI;
using VRage.Scripting;
namespace Torch.Managers;
public class ScriptCompilationManager : Manager
{
[ReflectedSetter(Name = "m_terminationReason")]
private static Action<MyProgrammableBlock, MyProgrammableBlock.ScriptTerminationReason> TerminationReasonSetter = null!;
[ReflectedGetter(Name = "ScriptComponent")]
private static Func<MyProgrammableBlock, MyIngameScriptComponent> ScriptComponentGetter = null!;
[ReflectedMethod]
private static Action<MyProgrammableBlock, string> SetDetailedInfo = null!;
[ReflectedSetter(Name = "m_instance")]
private static Action<MyProgrammableBlock, IMyGridProgram> InstanceSetter = null!;
[ReflectedSetter(Name = "m_assembly")]
private static Action<MyProgrammableBlock, Assembly> AssemblySetter = null!;
[ReflectedMethod]
private static Func<MyProgrammableBlock, Assembly, IEnumerable<string>, string, bool> CreateInstance = null!;
[ReflectedGetter(Name = "m_compilerErrors")]
private static Func<MyProgrammableBlock, List<string>> CompilerErrorsGetter = null!;
[ReflectedGetter(Name = "m_modApiWhitelistDiagnosticAnalyzer")]
private static Func<MyScriptCompiler, DiagnosticAnalyzer> ModWhitelistAnalyzer = null!;
[ReflectedGetter(Name = "m_ingameWhitelistDiagnosticAnalyzer")]
private static Func<MyScriptCompiler, DiagnosticAnalyzer> ScriptWhitelistAnalyzer = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CSharpCompilation, SyntaxTree, int, SyntaxTree> InjectMod = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CSharpCompilation, SyntaxTree, SyntaxTree> InjectInstructionCounter = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CompilationWithAnalyzers, EmitResult, List<Message>, bool, Task<bool>> EmitDiagnostics = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, MyApiTarget, string, IList<SyntaxTree>, string, Task> WriteDiagnostics = null!;
[ReflectedMethod(Name = "WriteDiagnostics")]
private static Func<MyScriptCompiler, MyApiTarget, string, IEnumerable<Message>, bool, Task> WriteDiagnostics2 = null!;
[ReflectedGetter(Name = "m_metadataReferences")]
private static Func<MyScriptCompiler, List<MetadataReference>> MetadataReferencesGetter = null!;
private readonly ConditionalWeakTable<MyProgrammableBlock, AssemblyLoadContext> _contexts = new();
public ScriptCompilationManager(ITorchBase torchInstance) : base(torchInstance)
{
}
public async void CompileAsync(MyProgrammableBlock block, string program, string storage, bool instantiate)
{
TerminationReasonSetter(block, MyProgrammableBlock.ScriptTerminationReason.None);
var component = ScriptComponentGetter(block);
component.NextUpdate = UpdateType.None;
component.NeedsUpdate = MyEntityUpdateEnum.NONE;
try
{
if (_contexts.TryGetValue(block, out var context))
{
InstanceSetter(block, null);
AssemblySetter(block, null);
context!.Unload();
}
_contexts.AddOrUpdate(block, context = new AssemblyLoadContext(null, true));
var messages = new List<Message>();
var assembly = await CompileAsync(context, MyApiTarget.Ingame,
$"pb_{block.EntityId}_{Random.Shared.NextInt64()}",
new[]
{
MyVRage.Platform.Scripting.GetIngameScript(
program, "Program", nameof(MyGridProgram))
}, messages, $"PB: {block.DisplayName} ({block.EntityId})");
AssemblySetter(block, assembly);
var errors = CompilerErrorsGetter(block);
errors.Clear();
errors.AddRange(messages.Select(b => b.Text));
if (instantiate)
await Torch.InvokeAsync(() => CreateInstance(block, assembly, errors, storage));
}
catch (Exception e)
{
await Torch.InvokeAsync(() => SetDetailedInfo(block, e.ToString()));
throw;
}
}
public async Task<Assembly> CompileAsync(AssemblyLoadContext context, MyApiTarget target, string assemblyName, IEnumerable<Script> scripts, List<Message> messages, string friendlyName, bool enableDebugInformation = false)
{
friendlyName ??= "<No Name>";
Func<CSharpCompilation, SyntaxTree, SyntaxTree> syntaxTreeInjector;
DiagnosticAnalyzer whitelistAnalyzer;
switch (target)
{
case MyApiTarget.None:
whitelistAnalyzer = null;
syntaxTreeInjector = null;
break;
case MyApiTarget.Mod:
{
var modId = MyModWatchdog.AllocateModId(friendlyName);
whitelistAnalyzer = ModWhitelistAnalyzer(MyScriptCompiler.Static);
syntaxTreeInjector = (c, st) => InjectMod(MyScriptCompiler.Static, c, st, modId);
break;
}
case MyApiTarget.Ingame:
syntaxTreeInjector = (c, t) => InjectInstructionCounter(MyScriptCompiler.Static, c, t);
whitelistAnalyzer = ScriptWhitelistAnalyzer(MyScriptCompiler.Static);
break;
default:
throw new ArgumentOutOfRangeException(nameof(target), target, "Invalid compilation target");
}
var compilation = CreateCompilation(assemblyName, scripts);
await WriteDiagnostics(MyScriptCompiler.Static, target, assemblyName, compilation.SyntaxTrees, null).ConfigureAwait(false);
var injectionFailed = false;
var compilationWithoutInjection = compilation;
if (syntaxTreeInjector != null)
{
SyntaxTree[] newSyntaxTrees = null;
try
{
var syntaxTrees = compilation.SyntaxTrees;
if (syntaxTrees.Length == 1)
{
newSyntaxTrees = new[] { syntaxTreeInjector(compilation, syntaxTrees[0]) };
}
else
{
newSyntaxTrees = await Task
.WhenAll(syntaxTrees.Select(
x => Task.Run(() => syntaxTreeInjector(compilation, x))))
.ConfigureAwait(false);
}
}
catch
{
injectionFailed = true;
}
if (!injectionFailed)
{
await WriteDiagnostics(MyScriptCompiler.Static, target, assemblyName, newSyntaxTrees, ".injected").ConfigureAwait(false);
compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newSyntaxTrees);
}
}
CompilationWithAnalyzers analyticCompilation = null;
if (whitelistAnalyzer != null)
{
analyticCompilation = compilation.WithAnalyzers(ImmutableArray.Create(whitelistAnalyzer));
compilation = (CSharpCompilation)analyticCompilation.Compilation;
}
using var assemblyStream = new MemoryStream();
var emitResult = compilation.Emit(assemblyStream);
var success = emitResult.Success;
var myBlacklistSyntaxVisitor = new MyBlacklistSyntaxVisitor();
foreach (var syntaxTree in compilation.SyntaxTrees)
{
myBlacklistSyntaxVisitor.SetSemanticModel(compilation.GetSemanticModel(syntaxTree, false));
myBlacklistSyntaxVisitor.Visit(await syntaxTree.GetRootAsync());
}
if (myBlacklistSyntaxVisitor.HasAnyResult())
{
myBlacklistSyntaxVisitor.GetResultMessages(messages);
}
else
{
success = await EmitDiagnostics(MyScriptCompiler.Static, analyticCompilation, emitResult, messages, success).ConfigureAwait(false);
await WriteDiagnostics2(MyScriptCompiler.Static, target, assemblyName, messages, success).ConfigureAwait(false);
assemblyStream.Seek(0, SeekOrigin.Begin);
if (injectionFailed) return null;
if (success)
return context.LoadFromStream(assemblyStream);
await EmitDiagnostics(MyScriptCompiler.Static, analyticCompilation, compilationWithoutInjection.Emit(assemblyStream), messages, false).ConfigureAwait(false);
}
return null;
}
private readonly CSharpCompilationOptions _compilationOptions = new(OutputKind.DynamicallyLinkedLibrary);
private readonly CSharpParseOptions _parseOptions = new(LanguageVersion.CSharp10, DocumentationMode.None);
private CSharpCompilation CreateCompilation(string assemblyFileName, IEnumerable<Script> scripts)
{
if (scripts == null)
return CSharpCompilation.Create(assemblyFileName, null, MetadataReferencesGetter(MyScriptCompiler.Static),
_compilationOptions);
var parseOptions = _parseOptions.WithPreprocessorSymbols(MyScriptCompiler.Static.ConditionalCompilationSymbols);
var enumerable = scripts.Select(s => CSharpSyntaxTree.ParseText(s.Code, parseOptions, s.Name, Encoding.UTF8));
return CSharpCompilation.Create(assemblyFileName, enumerable, MetadataReferencesGetter(MyScriptCompiler.Static), _compilationOptions);
}
}

View File

@@ -1,4 +1,5 @@
using System; #if false
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
@@ -51,3 +52,4 @@ internal static class GcCollectPatch
} }
} }
} }
#endif

View File

@@ -0,0 +1,33 @@
using System;
using System.Reflection;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.World;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Patches;
[PatchShim]
public static class ProgramableBlockPatch
{
[ReflectedMethodInfo(typeof(MyProgrammableBlock), "Compile")]
private static MethodInfo CompileMethod = null!;
public static void Patch(PatchContext context)
{
context.GetPattern(CompileMethod).AddPrefix();
}
private static bool Prefix(MyProgrammableBlock __instance, string program, string storage, bool instantiate)
{
if (!MySession.Static.EnableIngameScripts || __instance.CubeGrid is {IsPreview: true} or {CreatePhysics: false})
return false;
#pragma warning disable CS0618
TorchBase.Instance.CurrentSession.Managers.GetManager<ScriptCompilationManager>().CompileAsync(__instance, program, storage, instantiate);
#pragma warning restore CS0618
return false;
}
}

View File

@@ -92,6 +92,7 @@ namespace Torch
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception> /// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception>
protected TorchBase(ITorchConfig config) protected TorchBase(ITorchConfig config)
{ {
@@ -133,6 +134,7 @@ namespace Torch
#pragma warning disable CS0618 #pragma warning disable CS0618
Managers.AddManager(Plugins); Managers.AddManager(Plugins);
#pragma warning restore CS0618 #pragma warning restore CS0618
Managers.AddManager(new ScriptCompilationManager(this));
TorchAPI.Instance = this; TorchAPI.Instance = this;
GameStateChanged += (game, state) => GameStateChanged += (game, state) =>
@@ -146,6 +148,20 @@ namespace Torch
PatchManager.CommitInternal(); PatchManager.CommitInternal();
} }
}; };
var harmonyLog = LogManager.GetLogger("HarmonyX");
HarmonyLib.Tools.Logger.ChannelFilter = HarmonyLib.Tools.Logger.LogChannel.Debug;
HarmonyLib.Tools.Logger.MessageReceived += (_, args) => harmonyLog.Log(args.LogChannel switch
{
HarmonyLib.Tools.Logger.LogChannel.None => LogLevel.Off,
HarmonyLib.Tools.Logger.LogChannel.Info => LogLevel.Info,
HarmonyLib.Tools.Logger.LogChannel.IL => LogLevel.Trace,
HarmonyLib.Tools.Logger.LogChannel.Warn => LogLevel.Warn,
HarmonyLib.Tools.Logger.LogChannel.Error => LogLevel.Error,
HarmonyLib.Tools.Logger.LogChannel.Debug => LogLevel.Debug,
HarmonyLib.Tools.Logger.LogChannel.All => LogLevel.Debug,
_ => throw new ArgumentOutOfRangeException()
}, args.Message);
} }
[Obsolete("Prefer using Managers.GetManager for global managers")] [Obsolete("Prefer using Managers.GetManager for global managers")]