From a6098f1a8ab0cf3c0859ad697c2d93b7ebd05f4f Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 16 Jun 2026 17:53:48 +0300 Subject: [PATCH 1/2] Optimize Roslyn reference loading and re-enable Webcil publishing. --- .../MainWindow.xaml.cs | 3 +- .../ProvidedSolutionTests.cs | 2 +- .../LearnJsonEverything.csproj | 6 +- LearnJsonEverything/Program.cs | 2 - .../Services/CompilationHelpers.cs | 96 +++++-------------- 5 files changed, 30 insertions(+), 79 deletions(-) diff --git a/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs b/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs index 7a37d68..a7599dd 100644 --- a/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs +++ b/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs @@ -8,7 +8,6 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; -using LearnJsonEverything.Services; namespace LearnJsonEverything.LessonEditor; @@ -21,6 +20,6 @@ public MainWindow() { InitializeComponent(); - CompilationHelpers.TestOnly_SetReferences(ReferenceLoader.Load()); + ReferenceLoader.Load(); } } diff --git a/LearnJsonEverything.Tests/ProvidedSolutionTests.cs b/LearnJsonEverything.Tests/ProvidedSolutionTests.cs index 7a9b9aa..d338de8 100644 --- a/LearnJsonEverything.Tests/ProvidedSolutionTests.cs +++ b/LearnJsonEverything.Tests/ProvidedSolutionTests.cs @@ -15,7 +15,7 @@ public class ProvidedSolutionTests [OneTimeSetUp] public void Setup() { - CompilationHelpers.TestOnly_SetReferences(ReferenceLoader.Load()); + ReferenceLoader.Load(); } private static IEnumerable GetLessons(string filename) diff --git a/LearnJsonEverything/LearnJsonEverything.csproj b/LearnJsonEverything/LearnJsonEverything.csproj index 3271d40..9f90498 100644 --- a/LearnJsonEverything/LearnJsonEverything.csproj +++ b/LearnJsonEverything/LearnJsonEverything.csproj @@ -4,11 +4,11 @@ net10.0 enable enable - NU1701;BL0007 - latest - false + NU1701;BL0007 + latest true false + true diff --git a/LearnJsonEverything/Program.cs b/LearnJsonEverything/Program.cs index bf08585..8bf7678 100644 --- a/LearnJsonEverything/Program.cs +++ b/LearnJsonEverything/Program.cs @@ -18,8 +18,6 @@ var host = builder.Build(); _ = host.Services.GetService(); -_ = CompilationHelpers.LoadAssemblyReferences(host.Services.GetService()!); - // Initialize theme before the UI renders so controls get the correct initial theme. var localStorage = host.Services.GetRequiredService(); var savedTheme = await localStorage.GetItemAsync("theme"); diff --git a/LearnJsonEverything/Services/CompilationHelpers.cs b/LearnJsonEverything/Services/CompilationHelpers.cs index 08ebb72..72ced78 100644 --- a/LearnJsonEverything/Services/CompilationHelpers.cs +++ b/LearnJsonEverything/Services/CompilationHelpers.cs @@ -1,7 +1,8 @@ -using Json.Schema.Generation.XmlComments; +using Json.Schema.Generation.XmlComments; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System.Reflection; +using System.Reflection.Metadata; using System.Runtime.Loader; using Microsoft.CodeAnalysis.Emit; using static LearnJsonEverything.Services.Iconography; @@ -11,78 +12,32 @@ namespace LearnJsonEverything.Services; public static class CompilationHelpers { private static AssemblyLoadContext? _assemblyLoadContext; - private static MetadataReference[]? _references; - private static bool _isLoading; - private static readonly bool IsBrowserRuntime = OperatingSystem.IsBrowser(); + private static readonly Compilation _baseCompilation = CSharpCompilation.Create("BaseCompilation") + .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) + .AddReferences(GetAssemblyReferences()); - private static readonly string[] EnsuredAssemblies = - [ - "Json.More", - "JsonE.Net", - "JsonLogic", - "JsonPatch.Net", - "JsonPath.Net", - "JsonPointer.Net", - "JsonSchema.Net", - "JsonSchema.Net.Generation", - "LearnJsonEverything.Template", - //"Yaml2JsonNode", - ]; - - public static void TestOnly_SetReferences(MetadataReference[] references) => _references = references; - - public static async Task LoadAssemblyReferences(HttpClient client) + private static unsafe MetadataReference? TryCreateReferenceFromRawMetadata(Assembly assembly) { - if (_references is null) + if (!assembly.TryGetRawMetadata(out var metadataBlob, out var metadataLength)) { - if (_isLoading) return null; - _isLoading = true; - - try - { - var refs = AppDomain.CurrentDomain.GetAssemblies(); - var names = refs - .Where(x => !x.IsDynamic) - .Select(x => x.FullName!.Split(',')[0]) - .Concat(EnsuredAssemblies) - .Distinct() - .OrderBy(x => x) - .ToArray(); - - var references = new List(names.Length); - foreach (var assemblyName in names) - { - var source = $"/_framework/{assemblyName}.dll"; - try - { - Console.WriteLine($"Loading {assemblyName}..."); - var bytes = await client.GetByteArrayAsync(source); - using var stream = new MemoryStream(bytes, writable: false); - references.Add(MetadataReference.CreateFromStream(stream)); - } - catch (Exception e) - { - Console.WriteLine(e); - Console.WriteLine(source); - } - } - - _references = [.. references]; - } - finally - { - _isLoading = false; - } + return null; } - - return _references; + var metadata = ModuleMetadata.CreateFromMetadata((nint)metadataBlob, metadataLength, () => GC.KeepAlive(assembly)); + var location = assembly.Location; + if (location == "") + { + location = null; + } + return AssemblyMetadata.Create(metadata).GetReference(filePath: location, display: assembly.GetName().Name); } + public static IEnumerable GetAssemblyReferences() => AppDomain.CurrentDomain.GetAssemblies() + .Where(assembly => !assembly.IsDynamic) + .Select(TryCreateReferenceFromRawMetadata) + .Where(reference => reference is not null)!; + public static (ILessonRunner?, string[]) GetRunner(LessonData lesson) { - if (_references is null) - return (null, ["Compilation assemblies still loading. Please wait until complete and try again."]); - var fullSource = lesson.UserCode ?? string.Empty; Console.WriteLine($"Compiling...\n\n{fullSource}"); @@ -90,16 +45,15 @@ public static (ILessonRunner?, string[]) GetRunner(LessonData lesson) var syntaxTree = CSharpSyntaxTree.ParseText(fullSource, new CSharpParseOptions(LanguageVersion.Latest)); var assemblyPath = Path.ChangeExtension(Path.GetTempFileName(), "dll"); - var compilation = CSharpCompilation.Create(Path.GetFileName(assemblyPath)) - .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) - .AddReferences(_references) + var compilation = _baseCompilation + .WithAssemblyName(Path.GetFileName(assemblyPath)) .AddSyntaxTrees(syntaxTree); using var dllStream = new MemoryStream(); using var pdbStream = new MemoryStream(); using var xmlStream = new MemoryStream(); EmitResult emitResult; - if (IsBrowserRuntime) + if (OperatingSystem.IsBrowser()) { // Avoid debugger-agent assertions in WASM by not emitting debug symbols. emitResult = compilation.Emit(dllStream, xmlDocumentationStream: xmlStream); @@ -127,14 +81,14 @@ public static (ILessonRunner?, string[]) GetRunner(LessonData lesson) dllStream.Position = 0; xmlStream.Position = 0; - if (!IsBrowserRuntime) + if (!OperatingSystem.IsBrowser()) pdbStream.Position = 0; #pragma warning disable IL2026 #pragma warning disable IL2072 #pragma warning disable IL2070 Assembly assembly; - if (IsBrowserRuntime) + if (OperatingSystem.IsBrowser()) { assembly = Assembly.Load(dllStream.ToArray()); } From 84e35df9d1af58671e087023fffe19be8a811bd4 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Tue, 16 Jun 2026 18:20:21 +0300 Subject: [PATCH 2/2] Move loading mandatory dependencies to `CompilationHelpers`' static constructor. --- .../MainWindow.xaml.cs | 2 - .../ReferenceLoader.cs | 47 ------------------ .../ProvidedSolutionTests.cs | 6 --- LearnJsonEverything.Tests/ReferenceLoader.cs | 49 ------------------- .../Services/CompilationHelpers.cs | 18 +++++++ 5 files changed, 18 insertions(+), 104 deletions(-) delete mode 100644 LearnJsonEverything.LessonEditor/ReferenceLoader.cs delete mode 100644 LearnJsonEverything.Tests/ReferenceLoader.cs diff --git a/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs b/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs index a7599dd..83ea4b9 100644 --- a/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs +++ b/LearnJsonEverything.LessonEditor/MainWindow.xaml.cs @@ -19,7 +19,5 @@ public partial class MainWindow : Window public MainWindow() { InitializeComponent(); - - ReferenceLoader.Load(); } } diff --git a/LearnJsonEverything.LessonEditor/ReferenceLoader.cs b/LearnJsonEverything.LessonEditor/ReferenceLoader.cs deleted file mode 100644 index ed8028f..0000000 --- a/LearnJsonEverything.LessonEditor/ReferenceLoader.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Json.JsonE; -using Json.Logic; -using Json.More; -using Json.Path; -using Json.Schema; -using Json.Schema.Generation; -using Microsoft.CodeAnalysis; - -namespace LearnJsonEverything.LessonEditor; - -public static class ReferenceLoader -{ - static ReferenceLoader() - { - // force some assemblies to load - Load>(); - Load>(); - Load(); - Load(); - Load(); - Load(); - Load(); - } - - private static void Load(){} - - public static MetadataReference[] Load() - { - var refs = AppDomain.CurrentDomain.GetAssemblies(); - var assemblies = refs - .Where(x => !x.IsDynamic) - .OrderBy(x => x.FullName) - .ToArray(); - - var references = new MetadataReference[assemblies.Length]; - int i = 0; - foreach (var assembly in assemblies) - { - Console.WriteLine($"Loading {assembly.FullName}..."); - references[i] = MetadataReference.CreateFromFile(assembly.Location); - i++; - } - - return references; - } - -} \ No newline at end of file diff --git a/LearnJsonEverything.Tests/ProvidedSolutionTests.cs b/LearnJsonEverything.Tests/ProvidedSolutionTests.cs index d338de8..41d7805 100644 --- a/LearnJsonEverything.Tests/ProvidedSolutionTests.cs +++ b/LearnJsonEverything.Tests/ProvidedSolutionTests.cs @@ -12,12 +12,6 @@ public class ProvidedSolutionTests PropertyNameCaseInsensitive = true }; - [OneTimeSetUp] - public void Setup() - { - ReferenceLoader.Load(); - } - private static IEnumerable GetLessons(string filename) { var json = File.ReadAllText(filename); diff --git a/LearnJsonEverything.Tests/ReferenceLoader.cs b/LearnJsonEverything.Tests/ReferenceLoader.cs deleted file mode 100644 index d32e8d9..0000000 --- a/LearnJsonEverything.Tests/ReferenceLoader.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Json.JsonE; -using Json.Logic; -using Json.More; -using Json.Patch; -using Json.Path; -using Json.Schema; -using Json.Schema.Generation; -using Microsoft.CodeAnalysis; - -namespace LearnJsonEverything.Tests; - -public static class ReferenceLoader -{ - static ReferenceLoader() - { - // force some assemblies to load - Load>(); - Load>(); - Load(); - Load(); - Load(); - Load(); - Load(); - Load(); - } - - private static void Load(){} - - public static MetadataReference[] Load() - { - var refs = AppDomain.CurrentDomain.GetAssemblies(); - var assemblies = refs - .Where(x => !x.IsDynamic) - .OrderBy(x => x.FullName) - .ToArray(); - - var references = new MetadataReference[assemblies.Length]; - int i = 0; - foreach (var assembly in assemblies) - { - Console.WriteLine($"Loading {assembly.FullName}..."); - references[i] = MetadataReference.CreateFromFile(assembly.Location); - i++; - } - - return references; - } - -} \ No newline at end of file diff --git a/LearnJsonEverything/Services/CompilationHelpers.cs b/LearnJsonEverything/Services/CompilationHelpers.cs index 72ced78..a9b9e89 100644 --- a/LearnJsonEverything/Services/CompilationHelpers.cs +++ b/LearnJsonEverything/Services/CompilationHelpers.cs @@ -1,3 +1,9 @@ +using Json.Logic; +using Json.JsonE; +using Json.More; +using Json.Path; +using Json.Schema; +using Json.Schema.Generation; using Json.Schema.Generation.XmlComments; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -16,6 +22,18 @@ public static class CompilationHelpers .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) .AddReferences(GetAssemblyReferences()); + static CompilationHelpers() + { + // force some assemblies to load + _ = typeof(ILessonRunner); + _ = typeof(EnumStringConverter); + _ = typeof(JsonSchema); + _ = typeof(MinimumAttribute); + _ = typeof(JsonPath); + _ = typeof(JsonFunction); + _ = typeof(Rule); + } + private static unsafe MetadataReference? TryCreateReferenceFromRawMetadata(Assembly assembly) { if (!assembly.TryGetRawMetadata(out var metadataBlob, out var metadataLength))