Performance: Paralellize code generation by namespace, assembly and class
This commit is contained in:
@@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Reflection;
|
||||||
using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData;
|
using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData;
|
||||||
using MethodInfo = Il2CppInspector.Reflection.MethodInfo;
|
using MethodInfo = Il2CppInspector.Reflection.MethodInfo;
|
||||||
@@ -33,40 +34,44 @@ namespace Il2CppInspector
|
|||||||
private const string ExtAttribute = "System.Runtime.CompilerServices.ExtensionAttribute";
|
private const string ExtAttribute = "System.Runtime.CompilerServices.ExtensionAttribute";
|
||||||
private const string DMAttribute = "System.Reflection.DefaultMemberAttribute";
|
private const string DMAttribute = "System.Reflection.DefaultMemberAttribute";
|
||||||
|
|
||||||
|
// Assembly attributes we have already emitted
|
||||||
|
private HashSet<CustomAttributeData> usedAssemblyAttributes = new HashSet<CustomAttributeData>();
|
||||||
|
private readonly object usedAssemblyAttributesLock = new object();
|
||||||
|
|
||||||
public Il2CppCSharpDumper(Il2CppModel model) => this.model = model;
|
public Il2CppCSharpDumper(Il2CppModel model) => this.model = model;
|
||||||
|
|
||||||
public void WriteSingleFile(string outFile) => WriteSingleFile(outFile, t => t.Index);
|
public void WriteSingleFile(string outFile) => WriteSingleFile(outFile, t => t.Index);
|
||||||
|
|
||||||
public void WriteSingleFile<TKey>(string outFile, Func<TypeInfo, TKey> orderBy) => writeFile(outFile, model.Assemblies.SelectMany(x => x.DefinedTypes).OrderBy(orderBy));
|
public void WriteSingleFile<TKey>(string outFile, Func<TypeInfo, TKey> orderBy) {
|
||||||
|
usedAssemblyAttributes.Clear();
|
||||||
|
writeFile(outFile, model.Assemblies.SelectMany(x => x.DefinedTypes).OrderBy(orderBy));
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteFilesByNamespace<TKey>(string outPath, Func<TypeInfo, TKey> orderBy, bool flattenHierarchy) {
|
public void WriteFilesByNamespace<TKey>(string outPath, Func<TypeInfo, TKey> orderBy, bool flattenHierarchy) {
|
||||||
var namespaces = model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace);
|
usedAssemblyAttributes.Clear();
|
||||||
var usedAssemblyAttributes = new HashSet<CustomAttributeData>();
|
Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace), ns => {
|
||||||
foreach (var ns in namespaces) {
|
writeFile($"{outPath}\\{(!string.IsNullOrEmpty(ns.Key) ? ns.Key : "global").Replace('.', flattenHierarchy ? '.' : '\\')}.cs",
|
||||||
writeFile($"{outPath}\\{(!string.IsNullOrEmpty(ns.Key)? ns.Key : "global").Replace('.', flattenHierarchy? '.' : '\\')}.cs",
|
ns.OrderBy(orderBy));
|
||||||
ns.OrderBy(orderBy), excludedAssemblyAttributes: usedAssemblyAttributes);
|
});
|
||||||
usedAssemblyAttributes.UnionWith(ns.Select(t => t.Assembly).Distinct().SelectMany(a => a.CustomAttributes));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteFilesByAssembly<TKey>(string outPath, Func<TypeInfo, TKey> orderBy) {
|
public void WriteFilesByAssembly<TKey>(string outPath, Func<TypeInfo, TKey> orderBy) {
|
||||||
foreach (var asm in model.Assemblies) {
|
usedAssemblyAttributes.Clear();
|
||||||
|
Parallel.ForEach(model.Assemblies, asm => {
|
||||||
// Sort namespaces into alphabetical order, then sort types within the namespaces by the specified sort function
|
// Sort namespaces into alphabetical order, then sort types within the namespaces by the specified sort function
|
||||||
writeFile($"{outPath}\\{asm.ShortName}.cs", asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy));
|
writeFile($"{outPath}\\{asm.ShortName}.cs", asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy));
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteFilesByClass(string outPath, bool flattenHierarchy) {
|
public void WriteFilesByClass(string outPath, bool flattenHierarchy) {
|
||||||
var usedAssemblyAttributes = new HashSet<CustomAttributeData>();
|
usedAssemblyAttributes.Clear();
|
||||||
foreach (var type in model.Assemblies.SelectMany(x => x.DefinedTypes)) {
|
Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), type => {
|
||||||
writeFile($"{outPath}\\" + (type.Namespace + (type.Namespace.Length > 0? "." : "") + Regex.Replace(type.Name, "`[0-9]", ""))
|
writeFile($"{outPath}\\" + (type.Namespace + (type.Namespace.Length > 0 ? "." : "") + Regex.Replace(type.Name, "`[0-9]", ""))
|
||||||
.Replace('.', flattenHierarchy ? '.' : '\\') + ".cs",
|
.Replace('.', flattenHierarchy ? '.' : '\\') + ".cs",new[] {type});
|
||||||
new[] {type}, excludedAssemblyAttributes: usedAssemblyAttributes);
|
});
|
||||||
usedAssemblyAttributes.UnionWith(type.Assembly.CustomAttributes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeFile(string outFile, IEnumerable<TypeInfo> types, bool useNamespaceSyntax = true, IEnumerable<CustomAttributeData> excludedAssemblyAttributes = null) {
|
private void writeFile(string outFile, IEnumerable<TypeInfo> types, bool useNamespaceSyntax = true) {
|
||||||
|
|
||||||
var nsRefs = new HashSet<string>();
|
var nsRefs = new HashSet<string>();
|
||||||
var code = new StringBuilder();
|
var code = new StringBuilder();
|
||||||
@@ -156,40 +161,59 @@ namespace Il2CppInspector
|
|||||||
Directory.CreateDirectory(Path.GetDirectoryName(outFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(outFile));
|
||||||
|
|
||||||
// Create output file
|
// Create output file
|
||||||
using StreamWriter writer = new StreamWriter(new FileStream(outFile, FileMode.Create), Encoding.UTF8);
|
bool fileWritten = false;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
using StreamWriter writer = new StreamWriter(new FileStream(outFile, FileMode.Create), Encoding.UTF8);
|
||||||
|
|
||||||
// Write preamble
|
// Write preamble
|
||||||
writer.Write(@"/*
|
writer.Write(@"/*
|
||||||
* Generated code file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty
|
* Generated code file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty
|
||||||
*/
|
*/
|
||||||
|
|
||||||
");
|
");
|
||||||
|
|
||||||
// Output using directives
|
// Output using directives
|
||||||
writer.Write(string.Concat(usings.Select(n => $"using {n};\n")));
|
writer.Write(string.Concat(usings.Select(n => $"using {n};\n")));
|
||||||
if (nsRefs.Any())
|
if (nsRefs.Any())
|
||||||
writer.Write("\n");
|
writer.Write("\n");
|
||||||
|
|
||||||
// Output assembly information and attributes
|
// Output assembly information and attributes
|
||||||
writer.Write(generateAssemblyInfo(assemblies, nsRefs, excludedAssemblyAttributes) + "\n\n");
|
writer.Write(generateAssemblyInfo(assemblies, nsRefs) + "\n\n");
|
||||||
|
|
||||||
// Output type definitions
|
// Output type definitions
|
||||||
writer.Write(code);
|
writer.Write(code);
|
||||||
|
|
||||||
|
fileWritten = true;
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
// If we get "file is in use by another process", we are probably writing a duplicate class in another thread
|
||||||
|
// Wait a bit and try again
|
||||||
|
if ((uint) ex.HResult != 0x80070020)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
}
|
||||||
|
} while (!fileWritten);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string generateAssemblyInfo(IEnumerable<Reflection.Assembly> assemblies, IEnumerable<string> namespaces, IEnumerable<CustomAttributeData> excludedAttributes = null) {
|
private string generateAssemblyInfo(IEnumerable<Reflection.Assembly> assemblies, IEnumerable<string> namespaces) {
|
||||||
var text = new StringBuilder();
|
var text = new StringBuilder();
|
||||||
|
|
||||||
foreach (var asm in assemblies) {
|
foreach (var asm in assemblies) {
|
||||||
text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName} - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}\n");
|
text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName} - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}\n");
|
||||||
|
|
||||||
// Assembly-level attributes
|
// Assembly-level attributes
|
||||||
text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute)
|
lock (usedAssemblyAttributesLock) {
|
||||||
.Except(excludedAttributes ?? new List<CustomAttributeData>())
|
text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute)
|
||||||
.OrderBy(a => a.AttributeType.Name)
|
.Except(usedAssemblyAttributes ?? new HashSet<CustomAttributeData>())
|
||||||
.ToString(new Scope { Current = null, Namespaces = namespaces }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
|
.OrderBy(a => a.AttributeType.Name)
|
||||||
if (asm.CustomAttributes.Any())
|
.ToString(new Scope { Current = null, Namespaces = namespaces }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
|
||||||
text.Append("\n");
|
if (asm.CustomAttributes.Any())
|
||||||
|
text.Append("\n");
|
||||||
|
|
||||||
|
usedAssemblyAttributes.UnionWith(asm.CustomAttributes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return text.ToString().TrimEnd();
|
return text.ToString().TrimEnd();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user