diff --git a/CodeGen.meta b/CodeGen.meta new file mode 100644 index 0000000..f041337 --- /dev/null +++ b/CodeGen.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c2bfff1573c10da47ae73a47b8841279 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CodeGen/Editor.meta b/CodeGen/Editor.meta new file mode 100644 index 0000000..cada1a3 --- /dev/null +++ b/CodeGen/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c297548ebc9165f448111a35488d368e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CodeGen/Editor/EventBusILPostProcessor.cs b/CodeGen/Editor/EventBusILPostProcessor.cs new file mode 100644 index 0000000..c6b86a7 --- /dev/null +++ b/CodeGen/Editor/EventBusILPostProcessor.cs @@ -0,0 +1,162 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Unity.CompilationPipeline.Common.Diagnostics; +using Unity.CompilationPipeline.Common.ILPostProcessing; + +namespace ShrinkEventBus.CodeGen +{ + public class EventBusILPostProcessor : ILPostProcessor + { + + private readonly List _diagnostics = new(); + private PostProcessorAssemblyResolver _assemblyResolver; + private MethodReference _autoRegisterMethodRef; + + public override ILPostProcessor GetInstance() => this; + + public override bool WillProcess(ICompiledAssembly compiledAssembly) + { + var result = compiledAssembly.References.Any( + r => Path.GetFileNameWithoutExtension(r) == "ShrinkEventBus.Runtime"); + return result; + } + + public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) + { + if (!WillProcess(compiledAssembly)) return null; + _diagnostics.Clear(); + + var assemblyDefinition = AssemblyDefinitionFor(compiledAssembly); + var mainModule = assemblyDefinition.MainModule; + + ImportReferences(mainModule); + + foreach (var type in mainModule.Types) + { + if (!HasAttribute(type, "EventBusSubscriberAttribute")) continue; + if (!InheritsFromMonoBehaviour(type)) continue; + + InjectAutoRegister(type, mainModule); + } + + return GetResult(assemblyDefinition, _diagnostics); + } + + private void InjectAutoRegister(TypeDefinition type, ModuleDefinition module) + { + var awake = type.Methods.FirstOrDefault(m => m.Name == "Awake" && !m.IsStatic); + if (awake == null) + { + awake = new MethodDefinition("Awake", + MethodAttributes.Private | MethodAttributes.HideBySig, + module.TypeSystem.Void); + var il = awake.Body.GetILProcessor(); + il.Emit(OpCodes.Ret); + type.Methods.Add(awake); + } + + var processor = awake.Body.GetILProcessor(); + + var instructions = new List + { + processor.Create(OpCodes.Ldarg_0), + processor.Create(OpCodes.Call, _autoRegisterMethodRef), + processor.Create(OpCodes.Nop) + }; + + instructions.Reverse(); + instructions.ForEach(i => processor.Body.Instructions.Insert(0, i)); + } + + private void ImportReferences(ModuleDefinition module) + { + var runtimeModule = FindRuntimeModule(module); + var eventBusType = runtimeModule.GetAllTypes() + .First(t => t.Name == "EventBus"); + var autoRegisterMethod = eventBusType.Methods + .First(m => m.Name == "AutoRegister"); + + _autoRegisterMethodRef = module.ImportReference(autoRegisterMethod); + } + + private ModuleDefinition FindRuntimeModule(ModuleDefinition module) + { + ModuleDefinition result = null; + var visited = new HashSet(); + SearchRecursive( + _assemblyResolver.Resolve(module.Assembly.Name), + ref result, visited); + return result; + } + + private void SearchRecursive(AssemblyDefinition asm, ref ModuleDefinition found, HashSet visited) + { + foreach (var mod in asm.Modules) + { + if (mod.Name == "ShrinkEventBus.Runtime.dll") + { + found = mod; + return; + } + } + foreach (var reference in asm.MainModule.AssemblyReferences) + { + if (visited.Contains(reference.Name)) continue; + visited.Add(reference.Name); + var resolved = _assemblyResolver.Resolve(reference); + if (resolved == null) continue; + SearchRecursive(resolved, ref found, visited); + if (found != null) return; + } + } + + private AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly) + { + _assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly); + var readerParameters = new ReaderParameters + { + SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()), + SymbolReaderProvider = new PortablePdbReaderProvider(), + AssemblyResolver = _assemblyResolver, + ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(), + ReadingMode = ReadingMode.Immediate + }; + var peStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray()); + var assemblyDef = AssemblyDefinition.ReadAssembly(peStream, readerParameters); + _assemblyResolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDef); + return assemblyDef; + } + + private static ILPostProcessResult GetResult(AssemblyDefinition asm, List diagnostics) + { + var pe = new MemoryStream(); + var pdb = new MemoryStream(); + asm.Write(pe, new WriterParameters + { + SymbolWriterProvider = new PortablePdbWriterProvider(), + SymbolStream = pdb, + WriteSymbols = true + }); + return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), diagnostics); + } + + private static bool HasAttribute(TypeDefinition type, string name) + => type.CustomAttributes.Any(a => a.AttributeType.Name == name); + + private static bool InheritsFromMonoBehaviour(TypeDefinition type) + { + var current = type.BaseType; + while (current != null) + { + if (current.Name == "MonoBehaviour") return true; + try { current = current.Resolve()?.BaseType; } + catch { break; } + } + return false; + } + } +} \ No newline at end of file diff --git a/CodeGen/Editor/EventBusILPostProcessor.cs.meta b/CodeGen/Editor/EventBusILPostProcessor.cs.meta new file mode 100644 index 0000000..6f56fd9 --- /dev/null +++ b/CodeGen/Editor/EventBusILPostProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f611b284dae01a46bc990b87b8bca90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CodeGen/Editor/PostProcessorAssemblyResolver.cs b/CodeGen/Editor/PostProcessorAssemblyResolver.cs new file mode 100644 index 0000000..3bc86a9 --- /dev/null +++ b/CodeGen/Editor/PostProcessorAssemblyResolver.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.IO; +using System.Threading; +using Mono.Cecil; +using Unity.CompilationPipeline.Common.ILPostProcessing; + +namespace ShrinkEventBus.CodeGen +{ + internal class PostProcessorAssemblyResolver : IAssemblyResolver + { + private readonly string[] _references; + private readonly Dictionary _cache = new(); + private AssemblyDefinition _self; + + public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly) + { + _references = compiledAssembly.References; + } + + public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition) + { + _self = assemblyDefinition; + } + + public AssemblyDefinition Resolve(AssemblyNameReference name) + => Resolve(name, new ReaderParameters(ReadingMode.Deferred)); + + public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + { + lock (_cache) + { + if (name.Name == _self?.Name.Name) + return _self; + + var path = FindPath(name); + if (path == null) return null; + + var key = $"{path}{File.GetLastWriteTime(path)}"; + if (_cache.TryGetValue(key, out var cached)) return cached; + + parameters.AssemblyResolver = this; + var ms = ReadFileWithRetry(path); + + var pdbPath = Path.ChangeExtension(path, ".pdb"); + if (File.Exists(pdbPath)) + parameters.SymbolStream = ReadFileWithRetry(pdbPath); + + var assembly = AssemblyDefinition.ReadAssembly(ms, parameters); + _cache[key] = assembly; + return assembly; + } + } + + private string FindPath(AssemblyNameReference name) + { + foreach (var r in _references) + { + if (Path.GetFileNameWithoutExtension(r) == name.Name) + return r; + } + + var dirs = new HashSet(); + foreach (var r in _references) + { + var dir = Path.GetDirectoryName(r); + if (dir != null) dirs.Add(dir); + } + + foreach (var dir in dirs) + { + var candidate = Path.Combine(dir, $"{name.Name}.dll"); + if (File.Exists(candidate)) return candidate; + } + + return null; + } + + private static MemoryStream ReadFileWithRetry(string path, int retries = 5) + { + for (var i = 0; i < retries; i++) + { + try + { + var bytes = File.ReadAllBytes(path); + return new MemoryStream(bytes); + } + catch (IOException) when (i < retries - 1) + { + Thread.Sleep(100); + } + } + throw new IOException($"[ShrinkEventBus] 无法读取文件: {path}"); + } + + public void Dispose() { } + } +} \ No newline at end of file diff --git a/CodeGen/Editor/PostProcessorAssemblyResolver.cs.meta b/CodeGen/Editor/PostProcessorAssemblyResolver.cs.meta new file mode 100644 index 0000000..c577e75 --- /dev/null +++ b/CodeGen/Editor/PostProcessorAssemblyResolver.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aeea50f82bab4ee2833c671d801c95e3 +timeCreated: 1772908894 \ No newline at end of file diff --git a/CodeGen/Editor/PostProcessorReflectionImporter.cs b/CodeGen/Editor/PostProcessorReflectionImporter.cs new file mode 100644 index 0000000..5b4db06 --- /dev/null +++ b/CodeGen/Editor/PostProcessorReflectionImporter.cs @@ -0,0 +1,32 @@ +using System.Linq; +using System.Reflection; +using Mono.Cecil; + +namespace ShrinkEventBus.CodeGen +{ + internal class PostProcessorReflectionImporter : DefaultReflectionImporter + { + private const string CoreLibName = "System.Private.CoreLib"; + private readonly AssemblyNameReference _corlib; + + public PostProcessorReflectionImporter(ModuleDefinition module) : base(module) + { + _corlib = module.AssemblyReferences.FirstOrDefault( + a => a.Name is "mscorlib" or "netstandard"); + } + + public override AssemblyNameReference ImportReference(AssemblyName reference) + { + if (_corlib != null && reference.Name == CoreLibName) + return _corlib; + + return base.ImportReference(reference); + } + } + + internal class PostProcessorReflectionImporterProvider : IReflectionImporterProvider + { + public IReflectionImporter GetReflectionImporter(ModuleDefinition module) + => new PostProcessorReflectionImporter(module); + } +} \ No newline at end of file diff --git a/CodeGen/Editor/PostProcessorReflectionImporter.cs.meta b/CodeGen/Editor/PostProcessorReflectionImporter.cs.meta new file mode 100644 index 0000000..b1f2737 --- /dev/null +++ b/CodeGen/Editor/PostProcessorReflectionImporter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0a6c2401798b42f0bbcde72796d2fa9c +timeCreated: 1772908906 \ No newline at end of file diff --git a/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef b/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef new file mode 100644 index 0000000..f8f2955 --- /dev/null +++ b/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef @@ -0,0 +1,23 @@ +{ + "name": "Unity.ShrinkEventBus.CodeGen", + "rootNamespace": "ShrinkEventBus.CodeGen", + "references": [ + "ShrinkEventBus.Runtime" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "Unity.Burst.Cecil.dll", + "Unity.Burst.Cecil.Mdb.dll", + "Unity.Burst.Cecil.Pdb.dll", + "Unity.Burst.Cecil.Rocks.dll" + ], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef.meta b/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef.meta new file mode 100644 index 0000000..7fd1430 --- /dev/null +++ b/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 91161d4c2ae3ae74c9184ba33d9aa52e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/EventBase.cs b/Runtime/EventBase.cs index b562463..c146cc5 100644 --- a/Runtime/EventBase.cs +++ b/Runtime/EventBase.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Reflection; namespace ShrinkEventBus diff --git a/Runtime/EventBus.cs b/Runtime/EventBus.cs index 75dab1d..8911f3b 100644 --- a/Runtime/EventBus.cs +++ b/Runtime/EventBus.cs @@ -9,9 +9,8 @@ namespace ShrinkEventBus { public static class EventBus { - public static bool EnableDebugRecord { get; set; } - #if UNITY_EDITOR + public static bool EnableDebugRecord { get; set; } = true; public static event Action OnEventTriggeredForEditor; #endif