feat: 重构事件总线为可实例化架构并发布 1.3.0
- 新增 IShrinkEventBus、ShrinkEventBusBuilder 与 ShrinkEventBusInstance,支持独立 bus、异常策略、启动开关、类型校验和按 phase 派发 - 新增 SubscribeEvent 可释放订阅句柄与订阅快照接口,保留 EventBus 静态门面并委托到默认实例 - 重构监听列表为父子事件链共享锁与快照缓存,支持父事件监听子事件、跨父子数字优先级归并和防御性快照拷贝 - 优化 EventBase 元数据缓存、懒生成 EventId、派发快照挂接与对象池双重归还保护,移除旧 EventCache 热路径 - 恢复本地 ILPostProcessor 兜底并生成静态订阅注册表,避免共享 CodeGen 覆盖外程序集自动注册失效 - 改进事件查看器、Benchmark、README 与 CHANGELOG,并新增 Dispatch/Priority/Registration/Subscription 测试覆盖核心行为 BREAKING CHANGE: 移除 EventBase.GetListenerList(),int 数字优先级重载不再提供默认值,数字优先级 0 现在映射为 NORMAL。
This commit is contained in:
41 files changed
+3168
-888
No files matched your search
@@ -1,78 +1,198 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Mono.Cecil.Pdb;
|
||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||
|
||||
namespace ShrinkEventBus.CodeGen
|
||||
{
|
||||
public class EventBusILPostProcessor : ILPostProcessor
|
||||
public sealed class EventBusILPostProcessor : ILPostProcessor
|
||||
{
|
||||
private readonly List<DiagnosticMessage> _diagnostics = new();
|
||||
private PostProcessorAssemblyResolver _assemblyResolver;
|
||||
private MethodReference _autoRegisterMethodRef;
|
||||
private MethodReference _unregisterInstanceMethodRef;
|
||||
private const string RuntimeAssemblyName = "ShrinkEventBus.Runtime";
|
||||
private static readonly string[] SharedCoverageAssemblyNames =
|
||||
{
|
||||
"ShrinkCommand.Runtime",
|
||||
"ShrinkNetwork.Runtime",
|
||||
"ShrinkApp.Core.Runtime"
|
||||
};
|
||||
|
||||
public override ILPostProcessor GetInstance() => this;
|
||||
|
||||
public override bool WillProcess(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
return compiledAssembly.References.Any(
|
||||
r => Path.GetFileNameWithoutExtension(r) == "ShrinkEventBus.Runtime");
|
||||
if (!ReferencesAssembly(compiledAssembly, RuntimeAssemblyName))
|
||||
return false;
|
||||
|
||||
if (ShouldDeferToSharedCodeGen(compiledAssembly))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!WillProcess(compiledAssembly)) return null;
|
||||
_diagnostics.Clear();
|
||||
var diagnostics = new List<DiagnosticMessage>();
|
||||
if (!WillProcess(compiledAssembly))
|
||||
return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, diagnostics);
|
||||
|
||||
var assemblyDefinition = AssemblyDefinitionFor(compiledAssembly);
|
||||
var mainModule = assemblyDefinition.MainModule;
|
||||
var module = assemblyDefinition.MainModule;
|
||||
|
||||
bool needWeaving = false;
|
||||
foreach (var type in mainModule.Types)
|
||||
try
|
||||
{
|
||||
if (HasAttribute(type, "EventBusSubscriberAttribute") && InheritsFromMonoBehaviour(type))
|
||||
var subscriberType = FindType(module, "ShrinkEventBus.EventBusSubscriberAttribute", RuntimeAssemblyName);
|
||||
var subscribeAttributeType = FindType(module, "ShrinkEventBus.EventSubscribeAttribute", RuntimeAssemblyName);
|
||||
var eventBusType = FindType(module, "ShrinkEventBus.EventBus", RuntimeAssemblyName);
|
||||
var staticRegistryCtor = FindTypeArrayConstructor(
|
||||
module,
|
||||
"ShrinkEventBus.EventBusStaticRegistryAttribute",
|
||||
RuntimeAssemblyName);
|
||||
if (subscriberType == null || subscribeAttributeType == null || eventBusType == null)
|
||||
return GetResult(assemblyDefinition, diagnostics);
|
||||
|
||||
var eventBusTypeDef = eventBusType.Resolve();
|
||||
var autoRegisterMethod = eventBusTypeDef?.Methods.FirstOrDefault(method =>
|
||||
method.Name == "AutoRegister" &&
|
||||
method.IsStatic &&
|
||||
method.Parameters.Count == 1);
|
||||
var unregisterMethod = eventBusTypeDef?.Methods.FirstOrDefault(method =>
|
||||
method.Name == "UnregisterInstance" &&
|
||||
method.IsStatic &&
|
||||
method.Parameters.Count == 1);
|
||||
if (autoRegisterMethod == null || unregisterMethod == null)
|
||||
return GetResult(assemblyDefinition, diagnostics);
|
||||
|
||||
var autoRegisterMethodRef = module.ImportReference(autoRegisterMethod);
|
||||
var unregisterMethodRef = module.ImportReference(unregisterMethod);
|
||||
|
||||
var subscriberTypes = module.Types
|
||||
.Where(type => HasAttribute(type, subscriberType))
|
||||
.Where(InheritsFromMonoBehaviour)
|
||||
.Where(type => HasInstanceSubscribeMethod(type, subscribeAttributeType))
|
||||
.ToArray();
|
||||
|
||||
foreach (var type in subscriberTypes)
|
||||
{
|
||||
InjectAutoRegister(type, module, autoRegisterMethodRef);
|
||||
InjectAutoUnregister(type, module, unregisterMethodRef);
|
||||
}
|
||||
|
||||
if (staticRegistryCtor != null)
|
||||
{
|
||||
var staticSubscriberTypes = module.Types
|
||||
.Where(type => HasAttribute(type, subscriberType))
|
||||
.Where(type => type.Methods.Any(method =>
|
||||
method.IsStatic && HasAttribute(method, subscribeAttributeType)))
|
||||
.Select(type => module.ImportReference(type))
|
||||
.ToArray();
|
||||
if (staticSubscriberTypes.Length > 0)
|
||||
AddAssemblyTypeArrayAttribute(module, staticRegistryCtor, staticSubscriberTypes);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
diagnostics.Add(new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Error,
|
||||
MessageData = $"[ShrinkEventBus.CodeGen] {ex.Message}"
|
||||
});
|
||||
}
|
||||
|
||||
return GetResult(assemblyDefinition, diagnostics);
|
||||
}
|
||||
|
||||
private static bool ShouldDeferToSharedCodeGen(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
if (!IsSharedCodeGenAvailable())
|
||||
return false;
|
||||
|
||||
return SharedCoverageAssemblyNames.Any(name => ReferencesAssembly(compiledAssembly, name));
|
||||
}
|
||||
|
||||
private static bool IsSharedCodeGenAvailable()
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (string.Equals(assembly.GetName().Name, "Unity.ShrinkShared.CodeGen", StringComparison.Ordinal))
|
||||
return true;
|
||||
}
|
||||
|
||||
return Type.GetType("ShrinkShared.CodeGen.ShrinkRegistryILPostProcessor, Unity.ShrinkShared.CodeGen", false) != null;
|
||||
}
|
||||
|
||||
private static bool ReferencesAssembly(ICompiledAssembly compiledAssembly, string assemblyName)
|
||||
{
|
||||
return compiledAssembly.References.Any(reference =>
|
||||
string.Equals(Path.GetFileNameWithoutExtension(reference), assemblyName, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private static bool HasAttribute(ICustomAttributeProvider provider, TypeReference expectedAttributeType)
|
||||
{
|
||||
return provider.CustomAttributes.Any(attribute => attribute.AttributeType.FullName == expectedAttributeType.FullName);
|
||||
}
|
||||
|
||||
private static bool HasInstanceSubscribeMethod(TypeDefinition type, TypeReference subscribeAttributeType)
|
||||
{
|
||||
var current = type;
|
||||
while (current != null && current.Name != "MonoBehaviour")
|
||||
{
|
||||
if (current.Methods.Any(method => !method.IsStatic && HasAttribute(method, subscribeAttributeType)))
|
||||
return true;
|
||||
|
||||
var baseTypeRef = current.BaseType;
|
||||
if (baseTypeRef == null)
|
||||
return false;
|
||||
|
||||
TypeDefinition? resolvedBase;
|
||||
try
|
||||
{
|
||||
resolvedBase = baseTypeRef.Resolve();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resolvedBase == null)
|
||||
return true;
|
||||
|
||||
current = resolvedBase;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
needWeaving = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needWeaving) return null;
|
||||
|
||||
try
|
||||
{
|
||||
ImportReferences(mainModule);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
_diagnostics.Add(new DiagnosticMessage
|
||||
{
|
||||
DiagnosticType = DiagnosticType.Error,
|
||||
MessageData = $"[ShrinkEventBus] ILPostProcessor Error: {ex.Message}"
|
||||
});
|
||||
return GetResult(assemblyDefinition, _diagnostics);
|
||||
}
|
||||
|
||||
foreach (var type in mainModule.Types)
|
||||
{
|
||||
if (!HasAttribute(type, "EventBusSubscriberAttribute")) continue;
|
||||
if (!InheritsFromMonoBehaviour(type)) continue;
|
||||
|
||||
InjectAutoRegister(type, mainModule);
|
||||
InjectAutoUnregister(type, mainModule);
|
||||
}
|
||||
|
||||
return GetResult(assemblyDefinition, _diagnostics);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InjectAutoRegister(TypeDefinition type, ModuleDefinition module)
|
||||
private static void InjectAutoRegister(TypeDefinition type, ModuleDefinition module, MethodReference autoRegisterMethodRef)
|
||||
{
|
||||
var awake = type.Methods.FirstOrDefault(m => m.Name == "Awake" && !m.IsStatic);
|
||||
var awake = type.Methods.FirstOrDefault(method => method.Name == "Awake" && !method.IsStatic);
|
||||
if (awake == null)
|
||||
{
|
||||
var baseAwakeRef = FindBaseMethodReference(type, "Awake", module);
|
||||
@@ -85,7 +205,7 @@ namespace ShrinkEventBus.CodeGen
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, baseAwakeRef);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, _autoRegisterMethodRef);
|
||||
il.Emit(OpCodes.Call, autoRegisterMethodRef);
|
||||
il.Emit(OpCodes.Ret);
|
||||
type.Methods.Add(awake);
|
||||
return;
|
||||
@@ -103,16 +223,16 @@ namespace ShrinkEventBus.CodeGen
|
||||
var instructions = new List<Instruction>
|
||||
{
|
||||
processor.Create(OpCodes.Ldarg_0),
|
||||
processor.Create(OpCodes.Call, _autoRegisterMethodRef),
|
||||
processor.Create(OpCodes.Call, autoRegisterMethodRef),
|
||||
processor.Create(OpCodes.Nop)
|
||||
};
|
||||
instructions.Reverse();
|
||||
instructions.ForEach(i => processor.Body.Instructions.Insert(0, i));
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
|
||||
}
|
||||
|
||||
private void InjectAutoUnregister(TypeDefinition type, ModuleDefinition module)
|
||||
private static void InjectAutoUnregister(TypeDefinition type, ModuleDefinition module, MethodReference unregisterMethodRef)
|
||||
{
|
||||
var onDestroy = type.Methods.FirstOrDefault(m => m.Name == "OnDestroy" && !m.IsStatic);
|
||||
var onDestroy = type.Methods.FirstOrDefault(method => method.Name == "OnDestroy" && !method.IsStatic);
|
||||
if (onDestroy == null)
|
||||
{
|
||||
var baseOnDestroyRef = FindBaseMethodReference(type, "OnDestroy", module);
|
||||
@@ -123,7 +243,7 @@ namespace ShrinkEventBus.CodeGen
|
||||
module.TypeSystem.Void);
|
||||
var il = onDestroy.Body.GetILProcessor();
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, _unregisterInstanceMethodRef);
|
||||
il.Emit(OpCodes.Call, unregisterMethodRef);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, baseOnDestroyRef);
|
||||
il.Emit(OpCodes.Ret);
|
||||
@@ -143,14 +263,14 @@ namespace ShrinkEventBus.CodeGen
|
||||
var instructions = new List<Instruction>
|
||||
{
|
||||
processor.Create(OpCodes.Ldarg_0),
|
||||
processor.Create(OpCodes.Call, _unregisterInstanceMethodRef),
|
||||
processor.Create(OpCodes.Call, unregisterMethodRef),
|
||||
processor.Create(OpCodes.Nop)
|
||||
};
|
||||
instructions.Reverse();
|
||||
instructions.ForEach(i => processor.Body.Instructions.Insert(0, i));
|
||||
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
|
||||
}
|
||||
|
||||
private static MethodReference FindBaseMethodReference(TypeDefinition type, string methodName, ModuleDefinition module)
|
||||
private static MethodReference? FindBaseMethodReference(TypeDefinition type, string methodName, ModuleDefinition module)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -158,11 +278,11 @@ namespace ShrinkEventBus.CodeGen
|
||||
while (baseTypeRef != null)
|
||||
{
|
||||
var baseTypeDef = baseTypeRef.Resolve();
|
||||
if (baseTypeDef == null) break;
|
||||
|
||||
var method = baseTypeDef.Methods.FirstOrDefault(
|
||||
m => m.Name == methodName && !m.IsStatic && m.IsVirtual);
|
||||
if (baseTypeDef == null)
|
||||
break;
|
||||
|
||||
var method = baseTypeDef.Methods.FirstOrDefault(candidate =>
|
||||
candidate.Name == methodName && !candidate.IsStatic && candidate.IsVirtual);
|
||||
if (method != null)
|
||||
{
|
||||
if (baseTypeRef is GenericInstanceType genericInstance)
|
||||
@@ -187,106 +307,69 @@ namespace ShrinkEventBus.CodeGen
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ImportReferences(ModuleDefinition module)
|
||||
private static TypeReference? FindType(ModuleDefinition module, string fullName, string assemblyName)
|
||||
{
|
||||
var runtimeModule = FindRuntimeModule(module);
|
||||
if (runtimeModule == null)
|
||||
{
|
||||
throw new System.Exception("Cannot find ShrinkEventBus.Runtime module in assembly references.");
|
||||
}
|
||||
|
||||
var eventBusType = runtimeModule.GetAllTypes().FirstOrDefault(t => t.Name == "EventBus");
|
||||
if (eventBusType == null)
|
||||
{
|
||||
throw new System.Exception("Cannot find EventBus class in ShrinkEventBus.Runtime.");
|
||||
}
|
||||
|
||||
_autoRegisterMethodRef = module.ImportReference(
|
||||
eventBusType.Methods.First(m => m.Name == "AutoRegister"));
|
||||
_unregisterInstanceMethodRef = module.ImportReference(
|
||||
eventBusType.Methods.First(m => m.Name == "UnregisterInstance"));
|
||||
var resolved = Type.GetType($"{fullName}, {assemblyName}", false);
|
||||
return resolved == null ? null : module.ImportReference(resolved);
|
||||
}
|
||||
|
||||
private ModuleDefinition FindRuntimeModule(ModuleDefinition module)
|
||||
private static MethodReference? FindTypeArrayConstructor(ModuleDefinition module, string fullName, string assemblyName)
|
||||
{
|
||||
ModuleDefinition result = null;
|
||||
var visited = new HashSet<string>();
|
||||
SearchRecursive(_assemblyResolver.Resolve(module.Assembly.Name), ref result, visited);
|
||||
return result;
|
||||
var typeRef = FindType(module, fullName, assemblyName);
|
||||
var typeDef = typeRef?.Resolve();
|
||||
var ctor = typeDef?.Methods.FirstOrDefault(method =>
|
||||
method.IsConstructor &&
|
||||
method.Parameters.Count == 1 &&
|
||||
method.Parameters[0].ParameterType.IsArray &&
|
||||
method.Parameters[0].ParameterType.GetElementType().FullName == module.ImportReference(typeof(Type)).FullName);
|
||||
return ctor == null ? null : module.ImportReference(ctor);
|
||||
}
|
||||
|
||||
private void SearchRecursive(AssemblyDefinition asm, ref ModuleDefinition found, HashSet<string> visited)
|
||||
private static void AddAssemblyTypeArrayAttribute(ModuleDefinition module, MethodReference ctor, TypeReference[] types)
|
||||
{
|
||||
if (asm == null) return;
|
||||
|
||||
foreach (var mod in asm.Modules)
|
||||
{
|
||||
if (mod.Name == "ShrinkEventBus.Runtime.dll" || mod.Name == "ShrinkEventBus.Runtime")
|
||||
{
|
||||
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;
|
||||
}
|
||||
var attribute = new CustomAttribute(ctor);
|
||||
var typeTypeRef = module.ImportReference(typeof(Type));
|
||||
attribute.ConstructorArguments.Add(new CustomAttributeArgument(
|
||||
module.ImportReference(typeof(Type[])),
|
||||
types.Select(type => new CustomAttributeArgument(typeTypeRef, type)).ToArray()));
|
||||
module.Assembly.CustomAttributes.Add(attribute);
|
||||
}
|
||||
|
||||
private AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly)
|
||||
private static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly)
|
||||
{
|
||||
_assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly);
|
||||
var assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly);
|
||||
var readerParameters = new ReaderParameters
|
||||
{
|
||||
SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()),
|
||||
SymbolReaderProvider = new PortablePdbReaderProvider(),
|
||||
AssemblyResolver = _assemblyResolver,
|
||||
SymbolReaderProvider = new PdbReaderProvider(),
|
||||
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;
|
||||
|
||||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(
|
||||
new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray()),
|
||||
readerParameters);
|
||||
assemblyResolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition);
|
||||
return assemblyDefinition;
|
||||
}
|
||||
|
||||
private static ILPostProcessResult GetResult(AssemblyDefinition asm, List<DiagnosticMessage> diagnostics)
|
||||
private static ILPostProcessResult GetResult(AssemblyDefinition assemblyDefinition, List<DiagnosticMessage> diagnostics)
|
||||
{
|
||||
var pe = new MemoryStream();
|
||||
var pdb = new MemoryStream();
|
||||
asm.Write(pe, new WriterParameters
|
||||
assemblyDefinition.Write(pe, new WriterParameters
|
||||
{
|
||||
SymbolWriterProvider = new PortablePdbWriterProvider(),
|
||||
SymbolWriterProvider = new PdbWriterProvider(),
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user