Files
ShrinkEventBus/Runtime/EventBusRegHelper.cs
T
cneicy 8acccdcc4b 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。
2026-06-18 17:05:16 +08:00

193 lines
8.6 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace ShrinkEventBus
{
internal static class EventBusRegHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegStaticEventHandler(ShrinkEventBusInstance bus)
{
if (bus == null)
throw new ArgumentNullException(nameof(bus));
RegisterStaticSubscriberTypes(bus, EventBusGeneratedRegistry.GetStaticSubscriberTypes());
}
public static void RegisterStaticSubscriberTypes(ShrinkEventBusInstance bus, IEnumerable<Type> subscriberTypes)
{
if (bus == null)
throw new ArgumentNullException(nameof(bus));
if (subscriberTypes == null)
throw new ArgumentNullException(nameof(subscriberTypes));
foreach (var type in subscriberTypes)
{
try
{
if (type == null || type.GetCustomAttributes(typeof(EventBusSubscriberAttribute), false).Length == 0)
continue;
RegisterTarget(bus, type, requireSubscriberAttribute: true, lenientWhenNoMethods: true);
}
catch (Exception ex)
{
#if UNITY_EDITOR
Debug.LogWarning($"[EventBus] Static registration failed for {type?.FullName}: {ex.Message}");
#endif
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterTarget(ShrinkEventBusInstance bus, object target, bool requireSubscriberAttribute,
bool lenientWhenNoMethods = false)
{
if (bus == null)
throw new ArgumentNullException(nameof(bus));
if (target == null)
throw new ArgumentNullException(nameof(target));
switch (target)
{
case MethodInfo method:
RegisterMethod(bus, null, method, requireSubscriberAttribute: false, isStaticRegistration: true);
return;
case Type staticType:
RegisterDeclaredMethods(bus, null, staticType, requireSubscriberAttribute,
isStaticRegistration: true, lenientWhenNoMethods);
return;
default:
RegisterDeclaredMethods(bus, target, target.GetType(), requireSubscriberAttribute,
isStaticRegistration: false, lenientWhenNoMethods);
return;
}
}
private static void RegisterDeclaredMethods(ShrinkEventBusInstance bus, object? target, Type type,
bool requireSubscriberAttribute, bool isStaticRegistration, bool lenientWhenNoMethods)
{
if (requireSubscriberAttribute &&
type.GetCustomAttributes(typeof(EventBusSubscriberAttribute), false).Length == 0)
{
throw new InvalidOperationException(
$"Type {type.FullName} must declare [EventBusSubscriber] before it can be auto-registered.");
}
var flags = (isStaticRegistration ? BindingFlags.Static : BindingFlags.Instance) |
BindingFlags.Public | BindingFlags.NonPublic;
var methods = type.GetMethods(flags);
var foundMethods = 0;
foreach (var method in methods)
{
var hasSubscribeAttribute = method.GetCustomAttributes(typeof(EventSubscribeAttribute), false).Length > 0;
if (!hasSubscribeAttribute)
continue;
if (method.IsStatic != isStaticRegistration)
{
var expected = isStaticRegistration ? "static" : "instance";
throw new InvalidOperationException(
$"Method {method} is annotated with [EventSubscribe] but does not match the expected {expected} registration mode.");
}
RegisterMethod(bus, target, method, requireSubscriberAttribute: false, isStaticRegistration);
foundMethods++;
}
if (foundMethods == 0)
{
var message =
$"Type {type.FullName} has no [EventSubscribe] methods for {(isStaticRegistration ? "static" : "instance")} registration.";
if (lenientWhenNoMethods)
{
Debug.LogWarning($"[EventBus] {message} Auto-registration skipped.");
return;
}
throw new InvalidOperationException(message);
}
}
private static void RegisterMethod(ShrinkEventBusInstance bus, object? target, MethodInfo method,
bool requireSubscriberAttribute, bool isStaticRegistration)
{
if (method == null)
throw new ArgumentNullException(nameof(method));
if (!method.IsDefined(typeof(EventSubscribeAttribute), false))
throw new InvalidOperationException($"Method {method} is not annotated with [EventSubscribe].");
if (method.IsStatic != isStaticRegistration)
{
var expected = isStaticRegistration ? "static" : "instance";
throw new InvalidOperationException(
$"Method {method} is annotated with [EventSubscribe] but does not match the expected {expected} registration mode.");
}
if (requireSubscriberAttribute && method.DeclaringType != null &&
method.DeclaringType.GetCustomAttributes(typeof(EventBusSubscriberAttribute), false).Length == 0)
{
throw new InvalidOperationException(
$"Type {method.DeclaringType.FullName} must declare [EventBusSubscriber] before it can be auto-registered.");
}
var subscribeAttr = (EventSubscribeAttribute)method.GetCustomAttributes(typeof(EventSubscribeAttribute), false)[0];
var parameters = method.GetParameters();
if (parameters.Length != 1)
{
throw new InvalidOperationException(
$"Method {method} has [EventSubscribe] but declares {parameters.Length} parameters. Event handlers must declare exactly one EventBase parameter.");
}
var parameterType = parameters[0].ParameterType;
if (!typeof(EventBase).IsAssignableFrom(parameterType))
{
throw new InvalidOperationException(
$"Method {method} has [EventSubscribe] but parameter {parameterType.FullName} does not inherit from EventBase.");
}
ProcessMethodRegistration(bus, target, method, subscribeAttr, parameterType);
}
private static void ProcessMethodRegistration(ShrinkEventBusInstance bus, object? target, MethodInfo method,
EventSubscribeAttribute subscribeAttr, Type parameterType)
{
string scope = target == null ? "Static" : "Instance";
string typeName = target == null ? method.DeclaringType?.Name ?? "Unknown" : target.GetType().Name;
if (method.ReturnType == typeof(UniTask))
{
var funcType = typeof(Func<,>).MakeGenericType(parameterType, typeof(UniTask));
var handlerDelegate = target == null
? Delegate.CreateDelegate(funcType, method)
: Delegate.CreateDelegate(funcType, target, method);
bus.RegisterEventInternal(parameterType, handlerDelegate, subscribeAttr.Priority,
subscribeAttr.NumericPriority, subscribeAttr.ReceiveCanceled,
$"{scope} {typeName}.{method.Name} (UniTask)", method);
return;
}
if (method.ReturnType == typeof(void))
{
var actionType = typeof(Action<>).MakeGenericType(parameterType);
var actionDelegate = target == null
? Delegate.CreateDelegate(actionType, method)
: Delegate.CreateDelegate(actionType, target, method);
bus.RegisterEventInternal(parameterType, actionDelegate, subscribeAttr.Priority,
subscribeAttr.NumericPriority, subscribeAttr.ReceiveCanceled,
$"{scope} {typeName}.{method.Name} (Sync)", method);
return;
}
throw new InvalidOperationException(
$"Method {method} has [EventSubscribe] but return type {method.ReturnType.FullName} is unsupported. Use void or UniTask.");
}
}
}