From 1b3a1a48d0d0d7e2423db54f3266cabd80a5a9b3 Mon Sep 17 00:00:00 2001
From: Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com>
Date: Fri, 8 Jul 2022 19:02:33 -0400
Subject: refactor assembly resolver to avoid repeatedly copying search
 directory list

---
 src/SMAPI/Constants.cs                             |  4 +-
 .../ModLoading/AssemblyDefinitionResolver.cs       | 70 ++++++++++++++++++++--
 src/SMAPI/Framework/ModLoading/AssemblyLoader.cs   |  5 +-
 3 files changed, 67 insertions(+), 12 deletions(-)

(limited to 'src/SMAPI')

diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 33468717..442f2ec8 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -244,8 +244,8 @@ namespace StardewModdingAPI
         internal static void ConfigureAssemblyResolver(AssemblyDefinitionResolver resolver)
         {
             // add search paths
-            resolver.AddSearchDirectory(Constants.GamePath);
-            resolver.AddSearchDirectory(Constants.InternalFilesPath);
+            resolver.TryAddSearchDirectory(Constants.GamePath);
+            resolver.TryAddSearchDirectory(Constants.InternalFilesPath);
 
             // add SMAPI explicitly
             // Normally this would be handled automatically by the search paths, but for some reason there's a specific
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
index b3378ad1..5a850255 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs
@@ -4,18 +4,31 @@ using Mono.Cecil;
 namespace StardewModdingAPI.Framework.ModLoading
 {
     /// <summary>A minimal assembly definition resolver which resolves references to known assemblies.</summary>
-    internal class AssemblyDefinitionResolver : DefaultAssemblyResolver
+    internal class AssemblyDefinitionResolver : IAssemblyResolver
     {
         /*********
         ** Fields
         *********/
+        /// <summary>The underlying assembly resolver.</summary>
+        private readonly DefaultAssemblyResolverWrapper Resolver = new();
+
         /// <summary>The known assemblies.</summary>
         private readonly IDictionary<string, AssemblyDefinition> Lookup = new Dictionary<string, AssemblyDefinition>();
 
+        /// <summary>The directory paths to search for assemblies.</summary>
+        private readonly HashSet<string> SearchPaths = new();
+
 
         /*********
         ** Public methods
         *********/
+        /// <summary>Construct an instance.</summary>
+        public AssemblyDefinitionResolver()
+        {
+            foreach (string path in this.Resolver.GetSearchDirectories())
+                this.SearchPaths.Add(path);
+        }
+
         /// <summary>Add known assemblies to the resolver.</summary>
         /// <param name="assemblies">The known assemblies.</param>
         public void Add(params AssemblyDefinition[] assemblies)
@@ -29,7 +42,7 @@ namespace StardewModdingAPI.Framework.ModLoading
         /// <param name="names">The assembly names for which it should be returned.</param>
         public void AddWithExplicitNames(AssemblyDefinition assembly, params string[] names)
         {
-            this.RegisterAssembly(assembly);
+            this.Resolver.AddAssembly(assembly);
             foreach (string name in names)
                 this.Lookup[name] = assembly;
         }
@@ -37,18 +50,52 @@ namespace StardewModdingAPI.Framework.ModLoading
         /// <summary>Resolve an assembly reference.</summary>
         /// <param name="name">The assembly name.</param>
         /// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
-        public override AssemblyDefinition Resolve(AssemblyNameReference name)
+        public AssemblyDefinition Resolve(AssemblyNameReference name)
         {
-            return this.ResolveName(name.Name) ?? base.Resolve(name);
+            return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name);
         }
 
         /// <summary>Resolve an assembly reference.</summary>
         /// <param name="name">The assembly name.</param>
         /// <param name="parameters">The assembly reader parameters.</param>
         /// <exception cref="AssemblyResolutionException">The assembly can't be resolved.</exception>
-        public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
+        public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
         {
-            return this.ResolveName(name.Name) ?? base.Resolve(name, parameters);
+            return this.ResolveName(name.Name) ?? this.Resolver.Resolve(name, parameters);
+        }
+
+        /// <summary>Add a directory path to search for assemblies, if it's non-null and not already added.</summary>
+        /// <param name="path">The path to search.</param>
+        /// <returns>Returns whether the path was successfully added.</returns>
+        public bool TryAddSearchDirectory(string? path)
+        {
+            if (path is not null && this.SearchPaths.Add(path))
+            {
+                this.Resolver.AddSearchDirectory(path);
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>Remove a directory path to search for assemblies, if it's non-null.</summary>
+        /// <param name="path">The path to remove.</param>
+        /// <returns>Returns whether the path was in the list and removed.</returns>
+        public bool RemoveSearchDirectory(string? path)
+        {
+            if (path is not null && this.SearchPaths.Remove(path))
+            {
+                this.Resolver.RemoveSearchDirectory(path);
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <inheritdoc />
+        public void Dispose()
+        {
+            this.Resolver.Dispose();
         }
 
 
@@ -63,5 +110,16 @@ namespace StardewModdingAPI.Framework.ModLoading
                 ? match
                 : null;
         }
+
+        /// <summary>An internal wrapper around <see cref="DefaultAssemblyResolver"/> to allow access to its protected methods.</summary>
+        private class DefaultAssemblyResolverWrapper : DefaultAssemblyResolver
+        {
+            /// <summary>Add an assembly to the resolver.</summary>
+            /// <param name="assembly">The assembly to add.</param>
+            public void AddAssembly(AssemblyDefinition assembly)
+            {
+                this.RegisterAssembly(assembly);
+            }
+        }
     }
 }
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index bd29a159..01037870 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -267,11 +267,8 @@ namespace StardewModdingAPI.Framework.ModLoading
             // add the assembly's directory temporarily if needed
             // this is needed by F# mods which bundle FSharp.Core.dll, for example
             string? temporarySearchDir = null;
-            if (file.DirectoryName is not null && !this.AssemblyDefinitionResolver.GetSearchDirectories().Contains(file.DirectoryName))
-            {
-                this.AssemblyDefinitionResolver.AddSearchDirectory(file.DirectoryName);
+            if (this.AssemblyDefinitionResolver.TryAddSearchDirectory(file.DirectoryName))
                 temporarySearchDir = file.DirectoryName;
-            }
 
             // read assembly
             AssemblyDefinition assembly;
-- 
cgit