Skip to content

optimize post processor by avoiding unnecessary reading of referenced assemblies #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Assets/Sample/Test.unity
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,6 @@ MonoBehaviour:
references:
version: 1
00000000:
type: {class: ILibInterface`1<System_Int32>/LibGenericObject, ns: <GenericSerializeReference>, asm: Assembly-CSharp}
type: {class: ILibInterface`1<System_Int32>/LibIntObject, ns: <GenericSerializeReference>, asm: Assembly-CSharp}
00000001:
type: {class: ILibInterface`1<System_Single>/LibGenericObject_2, ns: <GenericSerializeReference>, asm: Assembly-CSharp}
2 changes: 1 addition & 1 deletion Assets/Sample/TestSO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace GenericSerializeReference.Sample
[CreateAssetMenu(fileName = "TestSO", menuName = "TestSO", order = 0)]
public class TestSO : ScriptableObject
{
[GenericSerializeReference]
[GenericSerializeReference(mode: GenerateMode.Embed)]
public MultipleGeneric.IInterface<int, float> IntFloat { get; set; }
}
}
5 changes: 3 additions & 2 deletions Assets/__.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace GenericSerializeReference.Library.CodeGen
namespace GenericSerializeReference
{
internal static class ___ {}
// just make sure there has `AssemblyCSharp.dll`
static class ___ {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,104 +28,106 @@ public override bool WillProcess(ICompiledAssembly compiledAssembly)

public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
var logger = new ILPostProcessorLogger(new List<DiagnosticMessage>());
using var resolver = new PostProcessorAssemblyResolver(compiledAssembly.References);
using var assembly = compiledAssembly.LoadAssembly(resolver);
var referenceAssemblies = compiledAssembly.LoadLibraryAssemblies(resolver).ToArray();
try
var logger = assembly.CreateLogger();
logger.Info($"process GenericSerializeReference on {assembly.Name.Name}({string.Join(",", compiledAssembly.References.Where(r => r.StartsWith("Library")))})");
var modified = Process(compiledAssembly, assembly, resolver, logger);
if (!modified) return new ILPostProcessResult(null, logger.Messages);

var pe = new MemoryStream();
var pdb = new MemoryStream();
var writerParameters = new WriterParameters
{
var loggerAttributes = assembly.GetAttributesOf<GenericSerializeReferenceLoggerAttribute>();
if (loggerAttributes.Any()) logger.LogLevel = (LogLevel)loggerAttributes.First().ConstructorArguments[0].Value;
logger.Info($"process GenericSerializeReference on {assembly.Name.Name}({string.Join(",", referenceAssemblies.Select(r => r.Name.Name))})");
var allTypes = referenceAssemblies.Append(assembly)
.Where(asm => !asm.Name.Name.StartsWith("Unity.")
&& !asm.Name.Name.StartsWith("UnityEditor.")
&& !asm.Name.Name.StartsWith("UnityEngine.")
)
.SelectMany(asm => asm.MainModule.GetAllTypes())
;
logger.Debug($"all types: {string.Join(", ", allTypes.Select(t => t.Name))}");
var typeTree = new TypeTree(allTypes);
logger.Debug($"tree: {typeTree}");
var modified = Process(assembly.MainModule, typeTree, logger);
if (!modified) return new ILPostProcessResult(null, logger.Messages);
SymbolWriterProvider = new PortablePdbWriterProvider(), SymbolStream = pdb, WriteSymbols = true
};
assembly.Write(pe, writerParameters);
// assembly.Write();
var inMemoryAssembly = new InMemoryAssembly(pe.ToArray(), pdb.ToArray());
return new ILPostProcessResult(inMemoryAssembly, logger.Messages);
}

var pe = new MemoryStream();
var pdb = new MemoryStream();
var writerParameters = new WriterParameters
{
SymbolWriterProvider = new PortablePdbWriterProvider(), SymbolStream = pdb, WriteSymbols = true
};
assembly.Write(pe, writerParameters);
// assembly.Write();
var inMemoryAssembly = new InMemoryAssembly(pe.ToArray(), pdb.ToArray());
return new ILPostProcessResult(inMemoryAssembly, logger.Messages);
private bool Process(ICompiledAssembly compiledAssembly, AssemblyDefinition assembly, PostProcessorAssemblyResolver resolver, ILPostProcessorLogger logger)
{
var module = assembly.MainModule;
IReadOnlyList<AssemblyDefinition> referenceAssemblies = Array.Empty<AssemblyDefinition>();
TypeTree typeTree = null;

try
{
return ProcessProperties();
}
finally
{
foreach (var reference in referenceAssemblies) reference.Dispose();
foreach (var @ref in referenceAssemblies) @ref.Dispose();
}
}

private bool Process(ModuleDefinition module, TypeTree typeTree, ILPostProcessorLogger logger)
{
var modified = false;
foreach (var (type, property, attribute) in
from type in module.GetAllTypes()
where type.IsClass && !type.IsAbstract
from property in type.Properties.ToArray() // able to change `Properties` during looping
from attribute in property.GetAttributesOf<GenericSerializeReferenceAttribute>()
select (type, property, attribute)
)
bool ProcessProperties()
{
if (property.GetMethod == null)
var modified = false;
foreach (var (type, property, attribute) in
from type in module.GetAllTypes()
where type.IsClass && !type.IsAbstract
from property in type.Properties.ToArray() // able to change `Properties` during looping
from attribute in property.GetAttributesOf<GenericSerializeReferenceAttribute>()
select (type, property, attribute)
)
{
logger.Warning($"Cannot process on property {property} without getter");
continue;
}
if (property.GetMethod == null)
{
logger.Warning($"Cannot process on property {property} without getter");
continue;
}

if (!property.PropertyType.IsGenericInstance)
{
logger.Warning($"Cannot process on property {property} with non-generic type {property.PropertyType.Name}");
continue;
}
if (!property.PropertyType.IsGenericInstance)
{
logger.Warning($"Cannot process on property {property} with non-generic type {property.PropertyType.Name}");
continue;
}

TypeReference baseInterface;
var mode = (GenerateMode)attribute.ConstructorArguments[1].Value;
if (mode == GenerateMode.Embed)
{
var wrapperName = $"<{property.Name}>__generic_serialize_reference";
var wrapper = property.DeclaringType.CreateNestedStaticPrivateClass(wrapperName);
baseInterface = CreateInterface(wrapper);
CreateDerivedClasses(property, wrapper, baseInterface);
}
else
{
baseInterface = module.ImportReference(typeof(IBase));
}
TypeReference baseInterface;
var mode = (GenerateMode)attribute.ConstructorArguments[1].Value;
if (mode == GenerateMode.Embed)
{
var wrapperName = $"<{property.Name}>__generic_serialize_reference";
var wrapper = property.DeclaringType.CreateNestedStaticPrivateClass(wrapperName);
baseInterface = CreateInterface(wrapper);
CreateDerivedClasses(property, wrapper, baseInterface);
}
else
{
baseInterface = module.ImportReference(typeof(IBase));
}

logger.Info($"generate nested class with interface {baseInterface.FullName}");
var fieldNamePrefix = (string)attribute.ConstructorArguments[0].Value;
GenerateField(module, property, baseInterface, fieldNamePrefix);
logger.Info($"generate nested class with interface {baseInterface.FullName}");
var fieldNamePrefix = (string)attribute.ConstructorArguments[0].Value;
GenerateField(module, property, baseInterface, fieldNamePrefix);

modified = true;
modified = true;
}
return modified;
}
return modified;

TypeDefinition CreateInterface(TypeDefinition wrapper, string interfaceName = "IBase")
void CreateTypeTree()
{
// .class interface nested public abstract auto ansi
var interfaceAttributes = TypeAttributes.Class |
TypeAttributes.Interface |
TypeAttributes.NestedPublic |
TypeAttributes.Abstract;
var baseInterface = new TypeDefinition("", interfaceName, interfaceAttributes);
wrapper.NestedTypes.Add(baseInterface);
return baseInterface;
if (typeTree != null) return;

referenceAssemblies = compiledAssembly.LoadLibraryAssemblies(resolver).ToArray();
var allTypes = referenceAssemblies.Append(assembly)
.Where(asm => !asm.Name.Name.StartsWith("Unity.")
&& !asm.Name.Name.StartsWith("UnityEditor.")
&& !asm.Name.Name.StartsWith("UnityEngine.")
)
.SelectMany(asm => asm.MainModule.GetAllTypes())
;
logger.Debug($"all types: {string.Join(", ", allTypes.Select(t => t.Name))}");
typeTree = new TypeTree(allTypes);
logger.Debug($"tree: {typeTree}");
}

void CreateDerivedClasses(PropertyDefinition property, TypeDefinition wrapper, TypeReference baseInterface)
{
if (typeTree == null) CreateTypeTree();
logger.Debug($"get derived {property.PropertyType.Module} {property.PropertyType} {property.PropertyType.Resolve()}");
foreach (var derived in typeTree.GetOrCreateAllDerivedReference(property.PropertyType))
{
Expand All @@ -151,7 +153,19 @@ void CreateDerivedClasses(PropertyDefinition property, TypeDefinition wrapper, T
}
}

internal static void GenerateField(
private TypeDefinition CreateInterface(TypeDefinition wrapper, string interfaceName = "IBase")
{
// .class interface nested public abstract auto ansi
var interfaceAttributes = TypeAttributes.Class |
TypeAttributes.Interface |
TypeAttributes.NestedPublic |
TypeAttributes.Abstract;
var baseInterface = new TypeDefinition("", interfaceName, interfaceAttributes);
wrapper.NestedTypes.Add(baseInterface);
return baseInterface;
}

private static void GenerateField(
ModuleDefinition module,
PropertyDefinition property,
TypeReference fieldType,
Expand All @@ -162,7 +176,7 @@ internal static void GenerateField(
InjectSetter(property, serializedField);
}

internal static FieldDefinition CreateSerializeReferenceField(
private static FieldDefinition CreateSerializeReferenceField(
ModuleDefinition module,
PropertyDefinition property,
TypeReference @interface,
Expand All @@ -183,7 +197,7 @@ internal static FieldDefinition CreateSerializeReferenceField(
return serializedField;
}

internal static void InjectGetter(PropertyDefinition property, FieldDefinition serializedField)
private static void InjectGetter(PropertyDefinition property, FieldDefinition serializedField)
{
// --------add--------
// IL_0000: ldarg.0 // this
Expand All @@ -206,7 +220,7 @@ internal static void InjectGetter(PropertyDefinition property, FieldDefinition s
instructions.Insert(4, Instruction.Create(OpCodes.Pop));
}

internal static void InjectSetter(PropertyDefinition property, FieldDefinition serializedField)
private static void InjectSetter(PropertyDefinition property, FieldDefinition serializedField)
{
//IL_0000: ldarg.0 // this
//IL_0001: ldarg.1 // 'value'
Expand Down
2 changes: 1 addition & 1 deletion Packages/generic-serialize-reference/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "com.quabug.generic-serialize-reference",
"description": "Automatically alter generic field of SerializeReference into its non-generic form",
"version": "1.2.0",
"version": "1.2.1",
"unity": "2020.2",
"displayName": "GenericSerializeReference",
"samples": [
Expand Down