From a0d0976a6a116471254954d802a219fb06553b1f Mon Sep 17 00:00:00 2001 From: zznty <94796179+zznty@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:16:00 +0700 Subject: [PATCH] c# 10 and assembly unloading for in-game scripts --- Torch/Managers/ScriptCompilationManager.cs | 231 +++++++++++++++++++++ Torch/Patches/ProgramableBlockPatch.cs | 33 +++ Torch/TorchBase.cs | 1 + 3 files changed, 265 insertions(+) create mode 100644 Torch/Managers/ScriptCompilationManager.cs create mode 100644 Torch/Patches/ProgramableBlockPatch.cs diff --git a/Torch/Managers/ScriptCompilationManager.cs b/Torch/Managers/ScriptCompilationManager.cs new file mode 100644 index 0000000..e233726 --- /dev/null +++ b/Torch/Managers/ScriptCompilationManager.cs @@ -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 TerminationReasonSetter = null!; + + [ReflectedGetter(Name = "ScriptComponent")] + private static Func ScriptComponentGetter = null!; + + [ReflectedMethod] + private static Action SetDetailedInfo = null!; + + [ReflectedSetter(Name = "m_instance")] + private static Action InstanceSetter = null!; + + [ReflectedSetter(Name = "m_assembly")] + private static Action AssemblySetter = null!; + + [ReflectedMethod] + private static Func, string, bool> CreateInstance = null!; + + [ReflectedGetter(Name = "m_compilerErrors")] + private static Func> CompilerErrorsGetter = null!; + + [ReflectedGetter(Name = "m_modApiWhitelistDiagnosticAnalyzer")] + private static Func ModWhitelistAnalyzer = null!; + + [ReflectedGetter(Name = "m_ingameWhitelistDiagnosticAnalyzer")] + private static Func ScriptWhitelistAnalyzer = null!; + + [ReflectedMethod] + private static Func InjectMod = null!; + + [ReflectedMethod] + private static Func InjectInstructionCounter = null!; + + [ReflectedMethod] + private static Func, bool, Task> EmitDiagnostics = null!; + + [ReflectedMethod] + private static Func, string, Task> WriteDiagnostics = null!; + + [ReflectedMethod(Name = "WriteDiagnostics")] + private static Func, bool, Task> WriteDiagnostics2 = null!; + + [ReflectedGetter(Name = "m_metadataReferences")] + private static Func> MetadataReferencesGetter = null!; + + private readonly ConditionalWeakTable _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(); + 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 CompileAsync(AssemblyLoadContext context, MyApiTarget target, string assemblyName, IEnumerable