From 8aa4c796043651fa6641804d6ae620d954e2610d Mon Sep 17 00:00:00 2001 From: cneicy Date: Sat, 5 Sep 2026 02:33:58 +0800 Subject: [PATCH] feat: add shared runtime and codegen support --- CodeGen/Editor/EventBusILPostProcessor.cs | 772 +----------------- .../Unity.ShrinkEventBus.CodeGen.asmdef | 3 +- Runtime/EventBus.cs | 4 + Runtime/ShrinkBusSchedulers.cs | 20 +- Runtime/ShrinkEventBus.Runtime.asmdef | 5 +- Runtime/ShrinkEventBusRuntime.cs | 80 ++ Runtime/ShrinkEventBusRuntime.cs.meta | 11 + package.json | 6 +- 8 files changed, 125 insertions(+), 776 deletions(-) create mode 100644 Runtime/ShrinkEventBusRuntime.cs create mode 100644 Runtime/ShrinkEventBusRuntime.cs.meta diff --git a/CodeGen/Editor/EventBusILPostProcessor.cs b/CodeGen/Editor/EventBusILPostProcessor.cs index 7b58f09..f920898 100644 --- a/CodeGen/Editor/EventBusILPostProcessor.cs +++ b/CodeGen/Editor/EventBusILPostProcessor.cs @@ -1,13 +1,7 @@ #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 ShrinkShared.CodeGen; using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.ILPostProcessing; @@ -15,766 +9,14 @@ namespace ShrinkEventBus.CodeGen { public sealed class EventBusILPostProcessor : ILPostProcessor { - private const string RuntimeAssemblyName = "ShrinkEventBus.Runtime"; public override ILPostProcessor GetInstance() => this; - public override bool WillProcess(ICompiledAssembly compiledAssembly) - { - if (!ReferencesAssembly(compiledAssembly, RuntimeAssemblyName)) - return false; + public override bool WillProcess(ICompiledAssembly compiledAssembly) => + UnityShrinkCodeGenAdapter.ReferencesAny(compiledAssembly, "ShrinkEventBus.Runtime"); - return true; - } - - public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) - { - var diagnostics = new List(); - if (!WillProcess(compiledAssembly)) - return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, diagnostics); - - var assemblyDefinition = AssemblyDefinitionFor(compiledAssembly); - var module = assemblyDefinition.MainModule; - - try - { - var generatedSubscriberType = FindType(module, "ShrinkEventBus.ShrinkEventSubscriberAttribute", RuntimeAssemblyName); - var generatedSubscribeType = FindType(module, "ShrinkEventBus.ShrinkSubscribeAttribute", RuntimeAssemblyName); - if (generatedSubscriberType == null || generatedSubscribeType == null) - return GetResult(assemblyDefinition, diagnostics); - - foreach (var type in GetAllTypes(module.Types) - .Where(type => !type.IsInterface) - .Where(type => HasAttribute(type, generatedSubscriberType)) - .Where(type => type.Methods.Any(method => - !method.IsStatic && HasAttribute(method, generatedSubscribeType)))) - { - var subscriberAttribute = type.CustomAttributes.First(attribute => - attribute.AttributeType.FullName == generatedSubscriberType.FullName); - InjectGeneratedBinding(type, module, generatedSubscribeType); - switch (ReadIntProperty(subscriberAttribute, "Lifetime", 0)) - { - case 0: - break; - case 1: - InjectAwakeToDestroyLifetime(type, module); - break; - default: - throw new InvalidOperationException( - $"Unsupported ShrinkSubscriberLifetime on {type.FullName}."); - } - } - - var staticSubscriberTypes = GetAllTypes(module.Types) - .Where(type => HasAttribute(type, generatedSubscriberType)) - .Where(type => type.Methods.Any(method => - method.IsStatic && HasAttribute(method, generatedSubscribeType))) - .ToArray(); - if (staticSubscriberTypes.Length > 0) - InjectStaticBootstrap(module, staticSubscriberTypes, generatedSubscribeType); - - } - catch (Exception ex) - { - diagnostics.Add(new DiagnosticMessage - { - DiagnosticType = DiagnosticType.Error, - MessageData = $"[ShrinkEventBus.CodeGen] {ex.Message}" - }); - } - - return GetResult(assemblyDefinition, diagnostics); - } - - 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 IEnumerable GetAllTypes(IEnumerable roots) - { - foreach (var type in roots) - { - yield return type; - foreach (var nested in GetAllTypes(type.NestedTypes)) - yield return nested; - } - } - - private static void InjectGeneratedBinding(TypeDefinition type, ModuleDefinition module, - TypeReference subscribeAttributeType) - { - var generatedInterface = FindType(module, - "ShrinkEventBus.IShrinkGeneratedSubscriber", RuntimeAssemblyName) - ?? throw new InvalidOperationException("IShrinkGeneratedSubscriber was not found."); - if (type.Interfaces.Any(item => item.InterfaceType.FullName == generatedInterface.FullName)) - return; - - var resolverType = FindType(module, "ShrinkEventBus.IShrinkBusResolver", RuntimeAssemblyName) - ?? throw new InvalidOperationException("IShrinkBusResolver was not found."); - var busKeyType = FindType(module, "ShrinkEventBus.ShrinkBusKey", RuntimeAssemblyName) - ?? throw new InvalidOperationException("ShrinkBusKey was not found."); - var bindingType = FindType(module, "ShrinkEventBus.ShrinkEventBinding", RuntimeAssemblyName) - ?? throw new InvalidOperationException("ShrinkEventBinding was not found."); - var bindingHelperType = FindType(module, "ShrinkEventBus.ShrinkGeneratedBinding", RuntimeAssemblyName) - ?? throw new InvalidOperationException("ShrinkGeneratedBinding was not found."); - var priorityType = FindType(module, "ShrinkEventBus.ShrinkEventPriority", RuntimeAssemblyName) - ?? throw new InvalidOperationException("ShrinkEventPriority was not found."); - var asyncHandlerType = FindType(module, "ShrinkEventBus.ShrinkAsyncEventHandler`1", RuntimeAssemblyName) - ?? throw new InvalidOperationException("ShrinkAsyncEventHandler was not found."); - - var nullableBusKey = new GenericInstanceType(module.ImportReference(typeof(Nullable<>))); - nullableBusKey.GenericArguments.Add(busKeyType); - var disposableType = module.ImportReference(typeof(IDisposable)); - var bindingDefinition = bindingType.Resolve() - ?? throw new InvalidOperationException("ShrinkEventBinding could not be resolved."); - var bindingCtor = module.ImportReference(bindingDefinition.Methods.Single(method => - method.IsConstructor && method.Parameters.Count == 0)); - var bindingAdd = module.ImportReference(bindingDefinition.Methods.Single(method => - method.Name == "Add" && method.Parameters.Count == 1)); - - var helperDefinition = bindingHelperType.Resolve() - ?? throw new InvalidOperationException("ShrinkGeneratedBinding could not be resolved."); - var syncSubscribe = module.ImportReference(helperDefinition.Methods.Single(method => - method.Name == "Subscribe" && method.HasGenericParameters)); - var asyncSubscribe = module.ImportReference(helperDefinition.Methods.Single(method => - method.Name == "SubscribeAsync" && method.HasGenericParameters)); - var legacyAsyncSubscribe = module.ImportReference(helperDefinition.Methods.Single(method => - method.Name == "SubscribeAsyncLegacy" && method.HasGenericParameters)); - - var interfaceDefinition = generatedInterface.Resolve() - ?? throw new InvalidOperationException("IShrinkGeneratedSubscriber could not be resolved."); - var interfaceMethod = module.ImportReference(interfaceDefinition.Methods.Single(method => - method.Name == "AttachGenerated")); - - var generatedMethod = new MethodDefinition( - "ShrinkEventBus.IShrinkGeneratedSubscriber.AttachGenerated", - MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.HideBySig | - MethodAttributes.NewSlot | MethodAttributes.Virtual, - disposableType); - generatedMethod.Parameters.Add(new ParameterDefinition("resolver", ParameterAttributes.None, resolverType)); - generatedMethod.Parameters.Add(new ParameterDefinition("defaultBus", ParameterAttributes.Optional, - nullableBusKey)); - generatedMethod.Overrides.Add(interfaceMethod); - generatedMethod.Body.InitLocals = true; - var bindingLocal = new VariableDefinition(bindingType); - generatedMethod.Body.Variables.Add(bindingLocal); - var il = generatedMethod.Body.GetILProcessor(); - il.Emit(OpCodes.Newobj, bindingCtor); - il.Emit(OpCodes.Stloc, bindingLocal); - - var classDefaultBus = ReadStringProperty( - type.CustomAttributes.First(attribute => - attribute.AttributeType.FullName == "ShrinkEventBus.ShrinkEventSubscriberAttribute"), - "DefaultBus"); - - foreach (var handler in type.Methods.Where(method => - !method.IsStatic && HasAttribute(method, subscribeAttributeType)).ToArray()) - { - EmitGeneratedSubscription(il, module, type, handler, - handler.CustomAttributes.First(attribute => - attribute.AttributeType.FullName == subscribeAttributeType.FullName), - classDefaultBus, bindingLocal, bindingAdd, syncSubscribe, asyncSubscribe, - legacyAsyncSubscribe, asyncHandlerType); - } - - il.Emit(OpCodes.Ldloc, bindingLocal); - il.Emit(OpCodes.Ret); - type.Interfaces.Add(new InterfaceImplementation(generatedInterface)); - type.Methods.Add(generatedMethod); - } - - private static void InjectAwakeToDestroyLifetime(TypeDefinition type, ModuleDefinition module) - { - if (!InheritsFrom(type, "UnityEngine.MonoBehaviour")) - { - throw new InvalidOperationException( - $"[ShrinkEventSubscriber(Lifetime = AwakeToDestroy)] requires MonoBehaviour: {type.FullName}."); - } - - const string bindingFieldName = "__shrinkEventBusAwakeToDestroyBinding"; - if (type.Fields.Any(field => field.Name == bindingFieldName)) - { - throw new InvalidOperationException( - $"Reserved generated field already exists on {type.FullName}: {bindingFieldName}."); - } - - var disposableType = module.ImportReference(typeof(IDisposable)); - var bindingField = new FieldDefinition(bindingFieldName, - FieldAttributes.Private, disposableType); - type.Fields.Add(bindingField); - - var eventBusType = FindType(module, "ShrinkEventBus.EventBus", RuntimeAssemblyName) - ?? throw new InvalidOperationException("EventBus was not found."); - var eventBusDefinition = eventBusType.Resolve() - ?? throw new InvalidOperationException("EventBus could not be resolved."); - var attachMethod = module.ImportReference(eventBusDefinition.Methods.Single(method => - method.Name == "Attach" && method.IsStatic && method.Parameters.Count == 2)); - var disposeMethod = module.ImportReference(typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose)) - ?? throw new InvalidOperationException("IDisposable.Dispose was not found.")); - - InjectAwake(type, module, bindingField, attachMethod); - InjectOnDestroy(type, module, bindingField, disposeMethod); - } - - private static void InjectAwake(TypeDefinition type, ModuleDefinition module, - FieldDefinition bindingField, MethodReference attachMethod) - { - var awake = type.Methods.FirstOrDefault(method => - method.Name == "Awake" && !method.IsStatic && method.Parameters.Count == 0); - if (awake != null) - { - InsertAttachAtStart(awake, module, bindingField, attachMethod); - return; - } - - var baseAwake = FindBaseMethodReference(type, "Awake", module); - awake = new MethodDefinition("Awake", - baseAwake != null - ? MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual - : MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual | - MethodAttributes.NewSlot, - module.TypeSystem.Void); - awake.Body.InitLocals = true; - var il = awake.Body.GetILProcessor(); - if (baseAwake != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Call, baseAwake); - } - EmitAttach(il, awake, module, bindingField, attachMethod); - il.Emit(OpCodes.Ret); - type.Methods.Add(awake); - } - - private static void InjectOnDestroy(TypeDefinition type, ModuleDefinition module, - FieldDefinition bindingField, MethodReference disposeMethod) - { - var onDestroy = type.Methods.FirstOrDefault(method => - method.Name == "OnDestroy" && !method.IsStatic && method.Parameters.Count == 0); - if (onDestroy != null) - { - InsertDisposeAtStart(onDestroy, bindingField, disposeMethod); - return; - } - - var baseOnDestroy = FindBaseMethodReference(type, "OnDestroy", module); - onDestroy = new MethodDefinition("OnDestroy", - baseOnDestroy != null - ? MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual - : MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual | - MethodAttributes.NewSlot, - module.TypeSystem.Void); - var il = onDestroy.Body.GetILProcessor(); - EmitDispose(il, bindingField, disposeMethod); - if (baseOnDestroy != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Call, baseOnDestroy); - } - il.Emit(OpCodes.Ret); - type.Methods.Add(onDestroy); - } - - private static void InsertAttachAtStart(MethodDefinition method, ModuleDefinition module, - FieldDefinition bindingField, MethodReference attachMethod) - { - if (!method.HasBody || method.Body.Instructions.Count == 0) - throw new InvalidOperationException($"Awake has no body: {method.FullName}."); - - method.Body.InitLocals = true; - var processor = method.Body.GetILProcessor(); - var instructions = BuildAttachInstructions(processor, method, module, bindingField, attachMethod); - var first = method.Body.Instructions[0]; - foreach (var instruction in instructions) - processor.InsertBefore(first, instruction); - } - - private static void EmitAttach(ILProcessor il, MethodDefinition method, ModuleDefinition module, - FieldDefinition bindingField, MethodReference attachMethod) - { - foreach (var instruction in BuildAttachInstructions(il, method, module, bindingField, attachMethod)) - il.Append(instruction); - } - - private static IReadOnlyList BuildAttachInstructions(ILProcessor il, - MethodDefinition method, ModuleDefinition module, FieldDefinition bindingField, - MethodReference attachMethod) - { - var defaultBusType = module.ImportReference(attachMethod.Parameters[1].ParameterType); - var defaultBus = new VariableDefinition(defaultBusType); - method.Body.Variables.Add(defaultBus); - var attached = il.Create(OpCodes.Nop); - return new[] - { - il.Create(OpCodes.Ldarg_0), - il.Create(OpCodes.Ldfld, bindingField), - il.Create(OpCodes.Brtrue_S, attached), - il.Create(OpCodes.Ldarg_0), - il.Create(OpCodes.Ldarg_0), - il.Create(OpCodes.Ldloca_S, defaultBus), - il.Create(OpCodes.Initobj, defaultBusType), - il.Create(OpCodes.Ldloc, defaultBus), - il.Create(OpCodes.Call, attachMethod), - il.Create(OpCodes.Stfld, bindingField), - attached - }; - } - - private static void InsertDisposeAtStart(MethodDefinition method, - FieldDefinition bindingField, MethodReference disposeMethod) - { - if (!method.HasBody || method.Body.Instructions.Count == 0) - throw new InvalidOperationException($"OnDestroy has no body: {method.FullName}."); - - var processor = method.Body.GetILProcessor(); - var instructions = BuildDisposeInstructions(processor, bindingField, disposeMethod); - var first = method.Body.Instructions[0]; - foreach (var instruction in instructions) - processor.InsertBefore(first, instruction); - } - - private static void EmitDispose(ILProcessor il, FieldDefinition bindingField, - MethodReference disposeMethod) - { - foreach (var instruction in BuildDisposeInstructions(il, bindingField, disposeMethod)) - il.Append(instruction); - } - - private static IReadOnlyList BuildDisposeInstructions(ILProcessor il, - FieldDefinition bindingField, MethodReference disposeMethod) - { - var disposed = il.Create(OpCodes.Nop); - return new[] - { - il.Create(OpCodes.Ldarg_0), - il.Create(OpCodes.Ldfld, bindingField), - il.Create(OpCodes.Brfalse_S, disposed), - il.Create(OpCodes.Ldarg_0), - il.Create(OpCodes.Ldfld, bindingField), - il.Create(OpCodes.Callvirt, disposeMethod), - il.Create(OpCodes.Ldarg_0), - il.Create(OpCodes.Ldnull), - il.Create(OpCodes.Stfld, bindingField), - disposed - }; - } - - private static MethodReference? FindBaseMethodReference(TypeDefinition type, - string methodName, ModuleDefinition module) - { - try - { - var baseTypeReference = type.BaseType; - while (baseTypeReference != null) - { - var baseType = baseTypeReference.Resolve(); - if (baseType == null) - break; - - var method = baseType.Methods.FirstOrDefault(candidate => - candidate.Name == methodName && !candidate.IsStatic && candidate.IsVirtual && - candidate.Parameters.Count == 0); - if (method != null) - { - if (baseTypeReference is GenericInstanceType genericBase) - { - var methodReference = new MethodReference(method.Name, - module.ImportReference(method.ReturnType), module.ImportReference(genericBase)) - { - HasThis = method.HasThis, - ExplicitThis = method.ExplicitThis, - CallingConvention = method.CallingConvention - }; - return methodReference; - } - - return module.ImportReference(method); - } - - baseTypeReference = baseType.BaseType; - } - } - catch - { - } - - return null; - } - - private static bool InheritsFrom(TypeDefinition type, string expectedFullName) - { - var current = type.BaseType; - while (current != null) - { - if (current.FullName == expectedFullName) - return true; - try - { - current = current.Resolve()?.BaseType; - } - catch - { - return false; - } - } - - return false; - } - - private static void EmitGeneratedSubscription(ILProcessor il, ModuleDefinition module, - TypeDefinition ownerType, MethodDefinition handler, CustomAttribute attribute, - string classDefaultBus, VariableDefinition bindingLocal, MethodReference bindingAdd, - MethodReference syncSubscribe, MethodReference asyncSubscribe, - MethodReference legacyAsyncSubscribe, TypeReference asyncHandlerType) - { - if (handler.Parameters.Count is < 1 or > 2) - throw new InvalidOperationException( - $"[ShrinkSubscribe] method {handler.FullName} must have one event parameter and an optional CancellationToken."); - - var eventType = module.ImportReference(handler.Parameters[0].ParameterType); - var eventInterface = FindType(module, "ShrinkEventBus.IShrinkEvent", RuntimeAssemblyName) - ?? throw new InvalidOperationException("IShrinkEvent was not found."); - if (!ImplementsInterface(eventType, eventInterface.FullName)) - throw new InvalidOperationException( - $"[ShrinkSubscribe] method {handler.FullName} event parameter must implement IShrinkEvent."); - - var bus = ReadStringProperty(attribute, "Bus"); - if (string.IsNullOrWhiteSpace(bus)) - bus = classDefaultBus; - var priority = ReadIntProperty(attribute, "Priority", 2); - var numericPriority = ReadIntProperty(attribute, "NumericPriority", 0); - var receiveCanceled = ReadBoolProperty(attribute, "ReceiveCanceled", false); - - MethodReference openSubscribe; - TypeReference delegateType; - if (handler.ReturnType.FullName == module.TypeSystem.Void.FullName && handler.Parameters.Count == 1) - { - openSubscribe = syncSubscribe; - delegateType = MakeGenericType(module, typeof(Action<>), eventType); - } - else if (handler.ReturnType.FullName == "Cysharp.Threading.Tasks.UniTask" && - handler.Parameters.Count == 1) - { - openSubscribe = legacyAsyncSubscribe; - var uniTaskType = FindType(module, "Cysharp.Threading.Tasks.UniTask", "UniTask") - ?? module.ImportReference(handler.ReturnType); - delegateType = MakeGenericType(module, typeof(Func<,>), eventType, uniTaskType); - } - else if (handler.ReturnType.FullName == "Cysharp.Threading.Tasks.UniTask" && - handler.Parameters.Count == 2 && - handler.Parameters[1].ParameterType.FullName == typeof(System.Threading.CancellationToken).FullName) - { - openSubscribe = asyncSubscribe; - var closedAsyncHandler = new GenericInstanceType(asyncHandlerType); - closedAsyncHandler.GenericArguments.Add(eventType); - delegateType = closedAsyncHandler; - } - else - { - throw new InvalidOperationException( - $"Unsupported [ShrinkSubscribe] signature: {handler.FullName}. Use void(T), UniTask(T), or UniTask(T, CancellationToken)."); - } - - var closedSubscribe = new GenericInstanceMethod(openSubscribe); - closedSubscribe.GenericArguments.Add(eventType); - var delegateCtor = MakeDelegateConstructor(module, delegateType); - - il.Emit(OpCodes.Ldloc, bindingLocal); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Ldstr, bus ?? string.Empty); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldftn, module.ImportReference(handler)); - il.Emit(OpCodes.Newobj, delegateCtor); - il.Emit(OpCodes.Ldc_I4, priority); - il.Emit(OpCodes.Ldc_I4, numericPriority); - il.Emit(receiveCanceled ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Call, closedSubscribe); - il.Emit(OpCodes.Callvirt, bindingAdd); - } - - private static void InjectStaticBootstrap(ModuleDefinition module, - IReadOnlyList subscriberTypes, TypeReference subscribeAttributeType) - { - var registryType = FindType(module, "ShrinkEventBus.ShrinkStaticBindingRegistry", RuntimeAssemblyName) - ?? throw new InvalidOperationException("ShrinkStaticBindingRegistry was not found."); - var asyncHandlerType = FindType(module, "ShrinkEventBus.ShrinkAsyncEventHandler`1", RuntimeAssemblyName) - ?? throw new InvalidOperationException("ShrinkAsyncEventHandler was not found."); - var registryDefinition = registryType.Resolve() - ?? throw new InvalidOperationException("ShrinkStaticBindingRegistry could not be resolved."); - var syncRegister = module.ImportReference(registryDefinition.Methods.Single(method => - method.Name == "Register" && method.HasGenericParameters)); - var asyncRegister = module.ImportReference(registryDefinition.Methods.Single(method => - method.Name == "RegisterAsync" && method.HasGenericParameters)); - var legacyAsyncRegister = module.ImportReference(registryDefinition.Methods.Single(method => - method.Name == "RegisterAsyncLegacy" && method.HasGenericParameters)); - - var bootstrap = new TypeDefinition("ShrinkEventBus.Generated", - "ShrinkGeneratedStaticBindings", - TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.NotPublic, - module.TypeSystem.Object); - module.Types.Add(bootstrap); - - var register = new MethodDefinition("Register", - MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, - module.TypeSystem.Void); - bootstrap.Methods.Add(register); - var il = register.Body.GetILProcessor(); - - foreach (var type in subscriberTypes) - { - var classDefaultBus = ReadStringProperty( - type.CustomAttributes.First(attribute => - attribute.AttributeType.FullName == "ShrinkEventBus.ShrinkEventSubscriberAttribute"), - "DefaultBus"); - foreach (var handler in type.Methods.Where(method => - method.IsStatic && HasAttribute(method, subscribeAttributeType)).ToArray()) - { - EmitStaticGeneratedSubscription(il, module, handler, - handler.CustomAttributes.First(attribute => - attribute.AttributeType.FullName == subscribeAttributeType.FullName), - classDefaultBus, syncRegister, asyncRegister, - legacyAsyncRegister, asyncHandlerType); - } - } - - il.Emit(OpCodes.Ret); - InjectModuleInitializer(module, register); - } - - private static void EmitStaticGeneratedSubscription(ILProcessor il, ModuleDefinition module, - MethodDefinition handler, CustomAttribute attribute, string classDefaultBus, - MethodReference syncRegister, MethodReference asyncRegister, - MethodReference legacyAsyncRegister, TypeReference asyncHandlerType) - { - if (handler.Parameters.Count is < 1 or > 2) - throw new InvalidOperationException( - $"[ShrinkSubscribe] method {handler.FullName} must have one event parameter and an optional CancellationToken."); - - var eventType = module.ImportReference(handler.Parameters[0].ParameterType); - var eventInterface = FindType(module, "ShrinkEventBus.IShrinkEvent", RuntimeAssemblyName) - ?? throw new InvalidOperationException("IShrinkEvent was not found."); - if (!ImplementsInterface(eventType, eventInterface.FullName)) - throw new InvalidOperationException( - $"[ShrinkSubscribe] method {handler.FullName} event parameter must implement IShrinkEvent."); - - var bus = ReadStringProperty(attribute, "Bus"); - if (string.IsNullOrWhiteSpace(bus)) - bus = classDefaultBus; - var priority = ReadIntProperty(attribute, "Priority", 2); - var numericPriority = ReadIntProperty(attribute, "NumericPriority", 0); - var receiveCanceled = ReadBoolProperty(attribute, "ReceiveCanceled", false); - - MethodReference openRegister; - TypeReference delegateType; - if (handler.ReturnType.FullName == module.TypeSystem.Void.FullName && handler.Parameters.Count == 1) - { - openRegister = syncRegister; - delegateType = MakeGenericType(module, typeof(Action<>), eventType); - } - else if (handler.ReturnType.FullName == "Cysharp.Threading.Tasks.UniTask" && - handler.Parameters.Count == 1) - { - openRegister = legacyAsyncRegister; - var uniTaskType = FindType(module, "Cysharp.Threading.Tasks.UniTask", "UniTask") - ?? module.ImportReference(handler.ReturnType); - delegateType = MakeGenericType(module, typeof(Func<,>), eventType, uniTaskType); - } - else if (handler.ReturnType.FullName == "Cysharp.Threading.Tasks.UniTask" && - handler.Parameters.Count == 2 && - handler.Parameters[1].ParameterType.FullName == typeof(System.Threading.CancellationToken).FullName) - { - openRegister = asyncRegister; - var closedAsyncHandler = new GenericInstanceType(asyncHandlerType); - closedAsyncHandler.GenericArguments.Add(eventType); - delegateType = closedAsyncHandler; - } - else - { - throw new InvalidOperationException( - $"Unsupported static [ShrinkSubscribe] signature: {handler.FullName}."); - } - - var closedRegister = new GenericInstanceMethod(openRegister); - closedRegister.GenericArguments.Add(eventType); - var delegateCtor = MakeDelegateConstructor(module, delegateType); - var handlerBridge = CreateStaticHandlerBridge(module, handler); - - il.Emit(OpCodes.Ldstr, bus ?? string.Empty); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Ldftn, module.ImportReference(handlerBridge)); - il.Emit(OpCodes.Newobj, delegateCtor); - il.Emit(OpCodes.Ldc_I4, priority); - il.Emit(OpCodes.Ldc_I4, numericPriority); - il.Emit(receiveCanceled ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Call, closedRegister); - } - - private static MethodDefinition CreateStaticHandlerBridge(ModuleDefinition module, - MethodDefinition handler) - { - var bridge = new MethodDefinition( - $"ShrinkEventBus.GeneratedStaticHandler_{handler.MetadataToken.ToInt32():X8}", - MethodAttributes.Assembly | MethodAttributes.Static | MethodAttributes.HideBySig, - module.ImportReference(handler.ReturnType)); - for (var i = 0; i < handler.Parameters.Count; i++) - { - var parameter = handler.Parameters[i]; - bridge.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, - module.ImportReference(parameter.ParameterType))); - } - - var il = bridge.Body.GetILProcessor(); - for (var i = 0; i < bridge.Parameters.Count; i++) - il.Emit(OpCodes.Ldarg, bridge.Parameters[i]); - il.Emit(OpCodes.Call, module.ImportReference(handler)); - il.Emit(OpCodes.Ret); - handler.DeclaringType.Methods.Add(bridge); - return bridge; - } - - private static void InjectModuleInitializer(ModuleDefinition module, MethodReference register) - { - var moduleType = module.Types.First(type => type.Name == ""); - var initializer = moduleType.Methods.FirstOrDefault(method => method.Name == ".cctor"); - if (initializer == null) - { - initializer = new MethodDefinition(".cctor", - MethodAttributes.Private | MethodAttributes.Static | - MethodAttributes.HideBySig | MethodAttributes.SpecialName | - MethodAttributes.RTSpecialName, - module.TypeSystem.Void); - initializer.Body.GetILProcessor().Emit(OpCodes.Ret); - moduleType.Methods.Add(initializer); - } - - var processor = initializer.Body.GetILProcessor(); - processor.InsertBefore(initializer.Body.Instructions[0], - processor.Create(OpCodes.Call, register)); - } - - private static GenericInstanceType MakeGenericType(ModuleDefinition module, Type openType, - params TypeReference[] arguments) - { - var result = new GenericInstanceType(module.ImportReference(openType)); - foreach (var argument in arguments) - result.GenericArguments.Add(argument); - return result; - } - - private static MethodReference MakeDelegateConstructor(ModuleDefinition module, TypeReference delegateType) - { - var ctor = new MethodReference(".ctor", module.TypeSystem.Void, delegateType) - { - HasThis = true, - CallingConvention = MethodCallingConvention.Default - }; - ctor.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object)); - ctor.Parameters.Add(new ParameterDefinition(module.TypeSystem.IntPtr)); - return ctor; - } - - private static bool ImplementsInterface(TypeReference type, string interfaceFullName) - { - TypeDefinition? current; - try - { - current = type.Resolve(); - } - catch - { - return false; - } - - while (current != null) - { - if (current.Interfaces.Any(item => item.InterfaceType.FullName == interfaceFullName)) - return true; - try - { - current = current.BaseType?.Resolve(); - } - catch - { - return false; - } - } - return false; - } - - private static string ReadStringProperty(CustomAttribute attribute, string name) - { - foreach (var property in attribute.Properties) - { - if (property.Name == name) - return property.Argument.Value as string ?? string.Empty; - } - return string.Empty; - } - - private static int ReadIntProperty(CustomAttribute attribute, string name, int defaultValue) - { - foreach (var property in attribute.Properties) - { - if (property.Name == name) - return Convert.ToInt32(property.Argument.Value); - } - return defaultValue; - } - - private static bool ReadBoolProperty(CustomAttribute attribute, string name, bool defaultValue) - { - foreach (var property in attribute.Properties) - { - if (property.Name == name) - return Convert.ToBoolean(property.Argument.Value); - } - return defaultValue; - } - - private static TypeReference? FindType(ModuleDefinition module, string fullName, string assemblyName) - { - var resolved = Type.GetType($"{fullName}, {assemblyName}", false); - return resolved == null ? null : module.ImportReference(resolved); - } - - private static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly) - { - var assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly); - var readerParameters = new ReaderParameters - { - SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()), - SymbolReaderProvider = new PdbReaderProvider(), - AssemblyResolver = assemblyResolver, - ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(), - ReadingMode = ReadingMode.Immediate - }; - - var assemblyDefinition = AssemblyDefinition.ReadAssembly( - new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray()), - readerParameters); - assemblyResolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition); - return assemblyDefinition; - } - - private static ILPostProcessResult GetResult(AssemblyDefinition assemblyDefinition, List diagnostics) - { - var pe = new MemoryStream(); - var pdb = new MemoryStream(); - assemblyDefinition.Write(pe, new WriterParameters - { - SymbolWriterProvider = new PdbWriterProvider(), - SymbolStream = pdb, - WriteSymbols = true - }); - return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), diagnostics); - } + public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) => + WillProcess(compiledAssembly) + ? UnityShrinkCodeGenAdapter.Process(compiledAssembly, "ShrinkEventBus.CodeGen") + : new ILPostProcessResult(compiledAssembly.InMemoryAssembly, new List()); } } diff --git a/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef b/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef index ffe61e9..0bd9538 100644 --- a/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef +++ b/CodeGen/Editor/Unity.ShrinkEventBus.CodeGen.asmdef @@ -2,7 +2,8 @@ "name": "Unity.ShrinkEventBus.CodeGen", "rootNamespace": "ShrinkEventBus.CodeGen", "references": [ - "ShrinkEventBus.Runtime" + "ShrinkEventBus.Runtime", + "Unity.ShrinkShared.CodeGen" ], "includePlatforms": [ "Editor" diff --git a/Runtime/EventBus.cs b/Runtime/EventBus.cs index b323338..5e611ff 100644 --- a/Runtime/EventBus.cs +++ b/Runtime/EventBus.cs @@ -189,7 +189,11 @@ namespace ShrinkEventBus } catch (Exception exception) { +#if UNITY_5_3_OR_NEWER UnityEngine.Debug.LogException(exception); +#else + System.Diagnostics.Trace.TraceError(exception.ToString()); +#endif } } } diff --git a/Runtime/ShrinkBusSchedulers.cs b/Runtime/ShrinkBusSchedulers.cs index d2954c6..adff912 100644 --- a/Runtime/ShrinkBusSchedulers.cs +++ b/Runtime/ShrinkBusSchedulers.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; +using System.Threading.Tasks; using Cysharp.Threading.Tasks; +using ShrinkSDK.Runtime; namespace ShrinkEventBus { @@ -206,15 +208,17 @@ namespace ShrinkEventBus internal sealed class ShrinkMainThreadScheduler : IShrinkBusScheduler { private readonly ShrinkSchedulerQueue _queue; + private readonly IShrinkMainThreadDispatcher _dispatcher; private int _pumpScheduled; private int _disposed; public ShrinkMainThreadScheduler(string name, ShrinkBusOptions options) { _queue = new ShrinkSchedulerQueue(name, options); + _dispatcher = ShrinkEventBusRuntime.MainThreadDispatcher; } - public bool IsOnSchedulerThread => PlayerLoopHelper.IsMainThread; + public bool IsOnSchedulerThread => _dispatcher.IsMainThread; public bool TryPost(Action action) { @@ -276,12 +280,15 @@ namespace ShrinkEventBus { if (Interlocked.Exchange(ref _pumpScheduled, 1) != 0) return; - UniTask.Void(PumpAsync); + if (_dispatcher.TryPost(() => PumpAsync().Forget())) + return; + Interlocked.Exchange(ref _pumpScheduled, 0); + ShrinkEventDiagnostics.LogException(new InvalidOperationException( + "The configured main-thread dispatcher rejected an EventBus pump.")); } - private async UniTaskVoid PumpAsync() + private async UniTask PumpAsync() { - await UniTask.SwitchToMainThread(); try { while (_queue.TryDequeue(out var item)) @@ -453,7 +460,8 @@ namespace ShrinkEventBus var waitMs = timeout == Timeout.InfiniteTimeSpan ? Timeout.Infinite : Math.Max(0, (int)Math.Min(int.MaxValue, timeout.TotalMilliseconds)); - var stopped = await UniTask.RunOnThreadPool(() => _stopped.Wait(waitMs)); + var stopped = await Task.Run(() => _stopped.Wait(waitMs)) + .AsUniTask(useCurrentSynchronizationContext: false); if (!stopped) { _queue.DropPending(); @@ -538,7 +546,7 @@ namespace ShrinkEventBus queue.DropPending(); return; } - await UniTask.Delay(1, ignoreTimeScale: true); + await Task.Delay(1).AsUniTask(useCurrentSynchronizationContext: false); } } } diff --git a/Runtime/ShrinkEventBus.Runtime.asmdef b/Runtime/ShrinkEventBus.Runtime.asmdef index 9b2a8d4..b80a785 100644 --- a/Runtime/ShrinkEventBus.Runtime.asmdef +++ b/Runtime/ShrinkEventBus.Runtime.asmdef @@ -2,11 +2,12 @@ "name": "ShrinkEventBus.Runtime", "rootNamespace": "ShrinkEventBus", "references": [ - "UniTask" + "UniTask", + "ShrinkRuntime.Abstractions" ], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, "autoReferenced": true -} \ No newline at end of file +} diff --git a/Runtime/ShrinkEventBusRuntime.cs b/Runtime/ShrinkEventBusRuntime.cs new file mode 100644 index 0000000..ad7e89b --- /dev/null +++ b/Runtime/ShrinkEventBusRuntime.cs @@ -0,0 +1,80 @@ +#nullable enable + +using System; +using System.Threading; +using ShrinkSDK.Runtime; + +namespace ShrinkEventBus +{ + public static class ShrinkEventBusRuntime + { + private static IShrinkMainThreadDispatcher _mainThreadDispatcher = CreateDefaultDispatcher(); + + public static IShrinkMainThreadDispatcher MainThreadDispatcher => + Volatile.Read(ref _mainThreadDispatcher); + + public static void ConfigureMainThreadDispatcher(IShrinkMainThreadDispatcher dispatcher) + { + if (dispatcher == null) + throw new ArgumentNullException(nameof(dispatcher)); + Volatile.Write(ref _mainThreadDispatcher, dispatcher); + } + + private static IShrinkMainThreadDispatcher CreateDefaultDispatcher() + { +#if UNITY_5_3_OR_NEWER + return new ShrinkUnityMainThreadDispatcher(); +#else + return new ShrinkSynchronizationContextDispatcher( + SynchronizationContext.Current, + Thread.CurrentThread.ManagedThreadId); +#endif + } + } + +#if UNITY_5_3_OR_NEWER + internal sealed class ShrinkUnityMainThreadDispatcher : IShrinkMainThreadDispatcher + { + public bool IsMainThread => Cysharp.Threading.Tasks.PlayerLoopHelper.IsMainThread; + + public bool TryPost(Action action) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + Cysharp.Threading.Tasks.PlayerLoopHelper.AddContinuation( + Cysharp.Threading.Tasks.PlayerLoopTiming.Update, + action); + return true; + } + } +#else + internal sealed class ShrinkSynchronizationContextDispatcher : IShrinkMainThreadDispatcher + { + private readonly SynchronizationContext? _context; + private readonly int _threadId; + + public ShrinkSynchronizationContextDispatcher(SynchronizationContext? context, int threadId) + { + _context = context; + _threadId = threadId; + } + + public bool IsMainThread => Thread.CurrentThread.ManagedThreadId == _threadId; + + public bool TryPost(Action action) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + if (IsMainThread) + { + action(); + return true; + } + if (_context == null) + return false; + _context.Post(static state => ((Action)state!).Invoke(), action); + return true; + } + } +#endif +} diff --git a/Runtime/ShrinkEventBusRuntime.cs.meta b/Runtime/ShrinkEventBusRuntime.cs.meta new file mode 100644 index 0000000..2c06ba3 --- /dev/null +++ b/Runtime/ShrinkEventBusRuntime.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3badb120248f61b4e9af7b571431a681 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json index 3f53350..8160af6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.cneicy.shrink-eventbus", - "version": "2.0.1", + "version": "2.1.0", "displayName": "ShrinkEventBus", "description": "多 Bus、特性强类型注册、UniTask 调度与低分配发布的 Unity 事件总线。", "unity": "2022.3", @@ -9,7 +9,9 @@ "licensesUrl": "https://git.crash.work/ShrinkSDK/ShrinkEventBus/src/branch/main/LICENSE", "dependencies": { "com.cysharp.unitask": "2.5.10", - "com.unity.nuget.mono-cecil": "1.11.4" + "com.unity.nuget.mono-cecil": "1.11.4", + "com.cneicy.shrink-shared-codegen": "0.1.0", + "com.cneicy.shrink-runtime-abstractions": "0.1.0" }, "keywords": [ "eventbus",