diff options
Diffstat (limited to 'src/SMAPI/Framework/ModLoading')
-rw-r--r-- | src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 10 | ||||
-rw-r--r-- | src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs | 143 |
2 files changed, 95 insertions, 58 deletions
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index dbb5f696..f8c901e0 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -76,9 +76,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="mod">The mod for which the assembly is being loaded.</param> /// <param name="assemblyPath">The assembly file path.</param> /// <param name="assumeCompatible">Assume the mod is compatible, even if incompatible code is detected.</param> + /// <param name="rewriteInParallel">Whether to enable experimental parallel rewriting.</param> /// <returns>Returns the rewrite metadata for the preprocessed assembly.</returns> /// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception> - public Assembly Load(IModMetadata mod, string assemblyPath, bool assumeCompatible) + public Assembly Load(IModMetadata mod, string assemblyPath, bool assumeCompatible, bool rewriteInParallel) { // get referenced local assemblies AssemblyParseResult[] assemblies; @@ -108,7 +109,7 @@ namespace StardewModdingAPI.Framework.ModLoading continue; // rewrite assembly - bool changed = this.RewriteAssembly(mod, assembly.Definition, loggedMessages, logPrefix: " "); + bool changed = this.RewriteAssembly(mod, assembly.Definition, loggedMessages, logPrefix: " ", rewriteInParallel); // detect broken assembly reference foreach (AssemblyNameReference reference in assembly.Definition.MainModule.AssemblyReferences) @@ -262,9 +263,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// <param name="assembly">The assembly to rewrite.</param> /// <param name="loggedMessages">The messages that have already been logged for this mod.</param> /// <param name="logPrefix">A string to prefix to log messages.</param> + /// <param name="rewriteInParallel">Whether to enable experimental parallel rewriting.</param> /// <returns>Returns whether the assembly was modified.</returns> /// <exception cref="IncompatibleInstructionException">An incompatible CIL instruction was found while rewriting the assembly.</exception> - private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, HashSet<string> loggedMessages, string logPrefix) + private bool RewriteAssembly(IModMetadata mod, AssemblyDefinition assembly, HashSet<string> loggedMessages, string logPrefix, bool rewriteInParallel) { ModuleDefinition module = assembly.MainModule; string filename = $"{assembly.Name.Name}.dll"; @@ -313,7 +315,7 @@ namespace StardewModdingAPI.Framework.ModLoading return rewritten; } ); - bool anyRewritten = rewriter.RewriteModule(); + bool anyRewritten = rewriter.RewriteModule(rewriteInParallel); // handle rewrite flags foreach (IInstructionHandler handler in handlers) diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index 9dc3680f..34c78c7d 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -56,15 +57,20 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework } /// <summary>Rewrite the loaded module code.</summary> + /// <param name="rewriteInParallel">Whether to enable experimental parallel rewriting.</param> /// <returns>Returns whether the module was modified.</returns> - public bool RewriteModule() + public bool RewriteModule(bool rewriteInParallel) { - int typesChanged = 0; - Exception exception = null; + IEnumerable<TypeDefinition> types = this.Module.GetTypes().Where(type => type.BaseType != null); // skip special types like <Module> - Parallel.ForEach( - source: this.Module.GetTypes().Where(type => type.BaseType != null), // skip special types like <Module> - body: type => + // experimental parallel rewriting + // This may cause intermittent startup errors and is disabled by default: https://github.com/Pathoschild/SMAPI/issues/721 + if (rewriteInParallel) + { + int typesChanged = 0; + Exception exception = null; + + Parallel.ForEach(types, type => { if (exception != null) return; @@ -72,50 +78,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework bool changed = false; try { - changed |= this.RewriteCustomAttributes(type.CustomAttributes); - changed |= this.RewriteGenericParameters(type.GenericParameters); - - foreach (InterfaceImplementation @interface in type.Interfaces) - changed |= this.RewriteTypeReference(@interface.InterfaceType, newType => @interface.InterfaceType = newType); - - if (type.BaseType.FullName != "System.Object") - changed |= this.RewriteTypeReference(type.BaseType, newType => type.BaseType = newType); - - foreach (MethodDefinition method in type.Methods) - { - changed |= this.RewriteTypeReference(method.ReturnType, newType => method.ReturnType = newType); - changed |= this.RewriteGenericParameters(method.GenericParameters); - changed |= this.RewriteCustomAttributes(method.CustomAttributes); - - foreach (ParameterDefinition parameter in method.Parameters) - changed |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType); - - foreach (var methodOverride in method.Overrides) - changed |= this.RewriteMethodReference(methodOverride); - - if (method.HasBody) - { - foreach (VariableDefinition variable in method.Body.Variables) - changed |= this.RewriteTypeReference(variable.VariableType, newType => variable.VariableType = newType); - - // check CIL instructions - ILProcessor cil = method.Body.GetILProcessor(); - Collection<Instruction> instructions = cil.Body.Instructions; - for (int i = 0; i < instructions.Count; i++) - { - var instruction = instructions[i]; - if (instruction.OpCode.Code == Code.Nop) - continue; - - changed |= this.RewriteInstruction(instruction, cil, newInstruction => - { - changed = true; - cil.Replace(instruction, newInstruction); - instruction = newInstruction; - }); - } - } - } + changed = this.RewriteTypeDefinition(type); } catch (Exception ex) { @@ -124,18 +87,90 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework if (changed) Interlocked.Increment(ref typesChanged); + }); + + return exception == null + ? typesChanged > 0 + : throw new Exception($"Rewriting {this.Module.Name} failed.", exception); + } + + // non-parallel rewriting + { + bool changed = false; + + try + { + foreach (var type in types) + changed |= this.RewriteTypeDefinition(type); + } + catch (Exception ex) + { + throw new Exception($"Rewriting {this.Module.Name} failed.", ex); } - ); - return exception == null - ? typesChanged > 0 - : throw new Exception($"Rewriting {this.Module.Name} failed.", exception); + return changed; + } } /********* ** Private methods *********/ + /// <summary>Rewrite a loaded type definition.</summary> + /// <param name="type">The type definition to rewrite.</param> + /// <returns>Returns whether the type was modified.</returns> + private bool RewriteTypeDefinition(TypeDefinition type) + { + bool changed = false; + + changed |= this.RewriteCustomAttributes(type.CustomAttributes); + changed |= this.RewriteGenericParameters(type.GenericParameters); + + foreach (InterfaceImplementation @interface in type.Interfaces) + changed |= this.RewriteTypeReference(@interface.InterfaceType, newType => @interface.InterfaceType = newType); + + if (type.BaseType.FullName != "System.Object") + changed |= this.RewriteTypeReference(type.BaseType, newType => type.BaseType = newType); + + foreach (MethodDefinition method in type.Methods) + { + changed |= this.RewriteTypeReference(method.ReturnType, newType => method.ReturnType = newType); + changed |= this.RewriteGenericParameters(method.GenericParameters); + changed |= this.RewriteCustomAttributes(method.CustomAttributes); + + foreach (ParameterDefinition parameter in method.Parameters) + changed |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType); + + foreach (var methodOverride in method.Overrides) + changed |= this.RewriteMethodReference(methodOverride); + + if (method.HasBody) + { + foreach (VariableDefinition variable in method.Body.Variables) + changed |= this.RewriteTypeReference(variable.VariableType, newType => variable.VariableType = newType); + + // check CIL instructions + ILProcessor cil = method.Body.GetILProcessor(); + Collection<Instruction> instructions = cil.Body.Instructions; + for (int i = 0; i < instructions.Count; i++) + { + var instruction = instructions[i]; + if (instruction.OpCode.Code == Code.Nop) + continue; + + changed |= this.RewriteInstruction(instruction, cil, newInstruction => + { + changed = true; + cil.Replace(instruction, newInstruction); + instruction = newInstruction; + }); + } + } + } + + return changed; + } + /// <summary>Rewrite a CIL instruction if needed.</summary> /// <param name="instruction">The current CIL instruction.</param> /// <param name="cil">The CIL instruction processor.</param> |