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:
cneicy committed 2026-06-18 17:05:16 +08:00
1 parent 2cea8a7ff5
commit 8acccdcc4b
41 files changed
+3168 -888

No files matched your search

+54 -15
View File
@@ -1,5 +1,6 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.Serialization;
@@ -7,7 +8,16 @@ namespace ShrinkEventBus
{
public abstract class EventBase : IDisposable
{
private readonly ListenerList _listenerList = new();
private sealed class EventMetadata
{
public bool IsCancelable;
public bool HasResult;
}
private static readonly ConcurrentDictionary<Type, EventMetadata> MetadataCache = new();
private EventHandlerInfo[]? _dispatchSnapshot;
private Guid? _eventId;
private bool _isCanceled;
private EventResult _result = EventResult.DEFAULT;
@@ -18,7 +28,7 @@ namespace ShrinkEventBus
public DateTime EventTime { get; private set; } = DateTime.UtcNow;
[IgnoreDataMember]
public Guid EventId { get; private set; } = Guid.NewGuid();
public Guid EventId => _eventId ??= Guid.NewGuid();
[IgnoreDataMember]
public bool IsCancelable { get; }
@@ -34,13 +44,14 @@ namespace ShrinkEventBus
protected EventBase()
{
IsCancelable = GetType().GetCustomAttribute<CancelableAttribute>() != null;
HasResult = GetType().GetCustomAttribute<HasResultAttribute>() != null;
var metadata = GetOrCreateMetadata(GetType());
IsCancelable = metadata.IsCancelable;
HasResult = metadata.HasResult;
Setup();
}
protected virtual void Setup() { }
protected virtual void OnReset() { }
internal void ResetInternal()
@@ -50,8 +61,8 @@ namespace ShrinkEventBus
CurrentHandler = null;
Phase = null;
EventTime = DateTime.UtcNow;
EventId = Guid.NewGuid();
_listenerList.Clear();
_eventId = null;
_dispatchSnapshot = null;
OnReset();
}
@@ -60,8 +71,13 @@ namespace ShrinkEventBus
CurrentHandler = null;
Phase = null;
EventTime = DateTime.UtcNow;
EventId = Guid.NewGuid();
_listenerList.Clear();
_eventId = null;
_dispatchSnapshot = null;
}
internal void SetListenerSnapshot(EventHandlerInfo[]? handlers)
{
_dispatchSnapshot = handlers is { Length: > 0 } ? handlers : null;
}
[IgnoreDataMember]
@@ -70,7 +86,9 @@ namespace ShrinkEventBus
get => _isCanceled;
set
{
if (!IsCancelable) throw new UnsupportedOperationException();
if (!IsCancelable)
throw new UnsupportedOperationException(
$"Event {GetType().Name} is not cancelable. Mark it with [Cancelable] to allow cancellation.");
_isCanceled = value;
}
}
@@ -81,7 +99,9 @@ namespace ShrinkEventBus
get => _result;
set
{
if (!HasResult) throw new InvalidOperationException();
if (!HasResult)
throw new InvalidOperationException(
$"Event {GetType().Name} does not support results. Mark it with [HasResult] to allow setting a result.");
_result = value;
}
}
@@ -90,18 +110,37 @@ namespace ShrinkEventBus
{
if (Phase == value) return;
if (Phase != null && Phase.Value.CompareTo(value) > 0)
throw new ArgumentException();
throw new ArgumentException(
$"Event phase cannot move backwards from {Phase.Value} to {value}.", nameof(value));
Phase = value;
}
public void SetCanceled(bool canceled) => IsCanceled = canceled;
public void SetResult(EventResult result) => Result = result;
public ListenerList GetListenerList() => _listenerList;
public EventHandlerInfo[] GetSubscribers() => _listenerList.GetHandlers();
public EventHandlerInfo[] GetSubscribers()
{
var snapshot = _dispatchSnapshot;
if (snapshot == null || snapshot.Length == 0)
return Array.Empty<EventHandlerInfo>();
var copy = new EventHandlerInfo[snapshot.Length];
Array.Copy(snapshot, copy, snapshot.Length);
return copy;
}
public void Dispose()
{
ReleaseAction?.Invoke(this);
}
private static EventMetadata GetOrCreateMetadata(Type eventType)
{
return MetadataCache.GetOrAdd(eventType, static type => new EventMetadata
{
IsCancelable = type.GetCustomAttribute<CancelableAttribute>() != null,
HasResult = type.GetCustomAttribute<HasResultAttribute>() != null
});
}
}
}
+105 -374
View File
@@ -1,398 +1,129 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace ShrinkEventBus
{
public static class EventBus
{
public static event Action<EventBase, Type> OnEventTriggered;
private static readonly IShrinkEventBus DefaultBus = CreateDefaultBus();
public static IShrinkEventBus Default => DefaultBus;
public static event Action<EventBase, Type> OnEventTriggered
{
add => DefaultBus.OnEventTriggered += value;
remove => DefaultBus.OnEventTriggered -= value;
}
#if UNITY_EDITOR
public static bool EnableDebugRecord { get; set; }
public static event Action<EventBase, string, string> OnEventTriggeredForEditor;
public static bool EnableDebugRecord
{
get => DefaultBus.EnableDebugRecord;
set => DefaultBus.EnableDebugRecord = value;
}
public static event Action<EventBase, string, string, EventHandlerInfo[]> OnEventTriggeredForEditor
{
add => DefaultBus.OnEventTriggeredForEditor += value;
remove => DefaultBus.OnEventTriggeredForEditor -= value;
}
#endif
private static readonly Dictionary<Type, ListenerList> EventHandlers = new();
private static readonly HashSet<object> RegisteredInstances = new();
private static readonly object InstanceLock = new();
public static ShrinkEventBusBuilder Builder() => new();
static EventBus()
public static IShrinkEventBus CreateBus(Action<ShrinkEventBusBuilder>? configure = null)
{
EventBusRegHelper.RegStaticEventHandler();
var builder = new ShrinkEventBusBuilder();
configure?.Invoke(builder);
return builder.Build();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureAutoManagerInitialized() => EventAutoRegHelper.EnsureInitialized();
public static void Start() => DefaultBus.Start();
public static void AutoRegister(object target) => DefaultBus.AutoRegister(target);
public static void Register(object target) => DefaultBus.Register(target);
public static void Unregister(object target) => DefaultBus.Unregister(target);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler, int priority = 0)
where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
RegisterEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Async Handler (Priority: {priority})");
}
public static void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler, int priority)
where TEvent : EventBase => DefaultBus.RegisterEvent(handler, priority);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Action<TEvent> handler, EventPriority priority = EventPriority.NORMAL,
bool receiveCanceled = false) where TEvent : EventBase
{
RegisterEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Sync Handler (Priority: {priority})", handler.Method);
}
public static void RegisterEvent<TEvent>(Action<TEvent> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false)
where TEvent : EventBase => DefaultBus.RegisterEvent(handler, priority, receiveCanceled);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Action<TEvent> handler, int priority = 0) where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
RegisterEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Sync Handler (Priority: {priority})", handler.Method);
}
public static void RegisterEvent<TEvent>(Action<TEvent> handler, int priority)
where TEvent : EventBase => DefaultBus.RegisterEvent(handler, priority);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false)
where TEvent : EventBase => DefaultBus.RegisterEvent(handler, priority, receiveCanceled);
public static IShrinkEventSubscription SubscribeEvent<TEvent>(Action<TEvent> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false)
where TEvent : EventBase => DefaultBus.SubscribeEvent(handler, priority, receiveCanceled);
public static IShrinkEventSubscription SubscribeEvent<TEvent>(Action<TEvent> handler, int priority)
where TEvent : EventBase => DefaultBus.SubscribeEvent(handler, priority);
public static IShrinkEventSubscription SubscribeEvent<TEvent>(Func<TEvent, UniTask> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false)
where TEvent : EventBase => DefaultBus.SubscribeEvent(handler, priority, receiveCanceled);
public static IShrinkEventSubscription SubscribeEvent<TEvent>(Func<TEvent, UniTask> handler, int priority)
where TEvent : EventBase => DefaultBus.SubscribeEvent(handler, priority);
public static void UnregisterEvent<TEvent>(Func<TEvent, UniTask> handler) where TEvent : EventBase =>
DefaultBus.UnregisterEvent(handler);
public static void UnregisterEvent<TEvent>(Action<TEvent> handler) where TEvent : EventBase =>
DefaultBus.UnregisterEvent(handler);
public static void ClearAllSubscribersForEvent<TEvent>() where TEvent : EventBase =>
DefaultBus.ClearAllSubscribersForEvent<TEvent>();
public static void UnregisterAllEventsForObject(object targetObject) =>
DefaultBus.UnregisterAllEventsForObject(targetObject);
public static void UnregisterInstance(object targetObject) => DefaultBus.Unregister(targetObject);
public static void UnregisterAllEvents() => DefaultBus.UnregisterAllEvents();
public static UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase =>
DefaultBus.TriggerEventAsync(eventArgs);
public static UniTask<bool> TriggerEventAsync<TEvent>(EventPriority phase, TEvent eventArgs)
where TEvent : EventBase => DefaultBus.TriggerEventAsync(phase, eventArgs);
public static bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase =>
DefaultBus.TriggerEvent(eventArgs);
public static bool TriggerEvent<TEvent>(EventPriority phase, TEvent eventArgs) where TEvent : EventBase =>
DefaultBus.TriggerEvent(phase, eventArgs);
public static EventHandlerInfo[] GetEventSubscribers<TEvent>() where TEvent : EventBase =>
DefaultBus.GetEventSubscribers<TEvent>();
public static ListenerList GetListenerList<TEvent>() where TEvent : EventBase =>
DefaultBus.GetListenerList<TEvent>();
public static IReadOnlyDictionary<Type, EventHandlerInfo[]> GetAllSubscribersSnapshot() =>
DefaultBus.GetAllSubscribersSnapshot();
public static IReadOnlyList<ShrinkEventSubscriptionSnapshot> GetActiveSubscriptionsSnapshot() =>
DefaultBus.GetActiveSubscriptionsSnapshot();
public static bool IsInstanceRegistered(object target) => DefaultBus.IsInstanceRegistered(target);
public static int GetRegisteredInstanceCount() => DefaultBus.GetRegisteredInstanceCount();
public static int GetRegisteredEventTypeCount() => DefaultBus.GetRegisteredEventTypeCount();
private static IShrinkEventBus CreateDefaultBus()
{
RegisterEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Async Handler (Priority: {priority})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void RegisterEventInternal(Type eventType, Delegate handler, EventPriority priority,
int numericPriority, bool receiveCanceled, string debugInfo = "", MethodInfo originalMethod = null)
{
lock (EventHandlers)
{
if (!EventHandlers.TryGetValue(eventType, out var collection))
{
collection = new ListenerList();
EventHandlers[eventType] = collection;
var cacheType = typeof(EventCache<>).MakeGenericType(eventType);
cacheType.GetField("List", BindingFlags.Static | BindingFlags.Public)?.SetValue(null, collection);
}
collection.Add(handler, priority, numericPriority, receiveCanceled, debugInfo, originalMethod);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterEvent<TEvent>(Func<TEvent, UniTask> handler) where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
if (!EventHandlers.TryGetValue(eventType, out var collection)) return;
collection.Remove(handler);
if (collection.Count == 0)
{
EventHandlers.Remove(eventType);
EventCache<TEvent>.List = null;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterEvent<TEvent>(Action<TEvent> handler) where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
if (!EventHandlers.TryGetValue(eventType, out var collection)) return;
collection.Remove(handler);
if (collection.Count == 0)
{
EventHandlers.Remove(eventType);
EventCache<TEvent>.List = null;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ClearAllSubscribersForEvent<TEvent>() where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
if (EventHandlers.Remove(eventType))
EventCache<TEvent>.List = null;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterAllEventsForObject(object targetObject)
{
if (targetObject is null) return;
lock (EventHandlers)
{
var eventTypesToRemove = new List<Type>();
foreach (var kvp in EventHandlers)
{
kvp.Value.RemoveTarget(targetObject);
if (kvp.Value.Count == 0) eventTypesToRemove.Add(kvp.Key);
}
foreach (var eventType in eventTypesToRemove)
{
EventHandlers.Remove(eventType);
var cacheType = typeof(EventCache<>).MakeGenericType(eventType);
cacheType.GetField("List", BindingFlags.Static | BindingFlags.Public)?.SetValue(null, null);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterInstance(object targetObject)
{
if (targetObject is null) return;
lock (InstanceLock)
{
RegisteredInstances.Remove(targetObject);
}
UnregisterAllEventsForObject(targetObject);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnregisterAllEvents()
{
lock (EventHandlers)
{
foreach (var type in EventHandlers.Keys)
{
var cacheType = typeof(EventCache<>).MakeGenericType(type);
cacheType.GetField("List", BindingFlags.Static | BindingFlags.Public).SetValue(null, null);
}
EventHandlers.Clear();
}
lock (InstanceLock)
{
RegisteredInstances.Clear();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryPrepareHandler<TEvent>(TEvent eventArgs, EventHandlerInfo handlerInfo)
where TEvent : EventBase
{
eventArgs.CurrentHandler = handlerInfo;
eventArgs.SetPhase(handlerInfo.Priority);
if (eventArgs.IsCancelable && eventArgs.IsCanceled && !handlerInfo.ReceiveCanceled)
return false;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
EnsureAutoManagerInitialized();
eventArgs.PrepareForDispatch();
var collection = EventCache<TEvent>.List;
var handlers = collection?.GetHandlers() ?? Array.Empty<EventHandlerInfo>();
var wasHandled = false;
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
if (!TryPrepareHandler(eventArgs, handlerInfo)) continue;
try
{
if (handlerInfo.Handler is Action<TEvent> syncHandler)
{
syncHandler(eventArgs);
wasHandled = true;
}
else if (handlerInfo.Handler is Func<TEvent, UniTask> asyncHandler)
{
await asyncHandler(eventArgs);
wasHandled = true;
}
}
catch (Exception ex)
{
Debug.LogException(ex);
Debug.LogError($"[EventBus] Async Handler Exception: {ex.Message}");
}
}
eventArgs.CurrentHandler = null;
CompleteEventDispatch(eventArgs, typeof(TEvent), handlers);
return wasHandled;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
EnsureAutoManagerInitialized();
eventArgs.PrepareForDispatch();
var collection = EventCache<TEvent>.List;
var handlers = collection?.GetHandlers() ?? Array.Empty<EventHandlerInfo>();
var wasHandled = false;
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
if (!TryPrepareHandler(eventArgs, handlerInfo)) continue;
try
{
if (handlerInfo.Handler is Action<TEvent> syncHandler)
{
syncHandler(eventArgs);
wasHandled = true;
}
else if (handlerInfo.Handler is Func<TEvent, UniTask> asyncHandler)
{
var detachedEvent = EventCloneUtility.CloneForDetachedDispatch(eventArgs);
FireAndForgetSafe(() => asyncHandler(detachedEvent),
$"{handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName}");
wasHandled = true;
}
}
catch (Exception ex)
{
Debug.LogException(ex);
Debug.LogError($"[EventBus] Sync Handler Exception: {ex.Message}");
}
}
eventArgs.CurrentHandler = null;
CompleteEventDispatch(eventArgs, typeof(TEvent), handlers);
return wasHandled;
}
private static void FireAndForgetSafe(Func<UniTask> action, string handlerName)
{
action().Forget(e =>
{
Debug.LogException(e);
Debug.LogError($"[EventBus] Async Handler {handlerName} threw exception: {e.Message}");
});
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CompleteEventDispatch(EventBase eventArgs, Type eventType, EventHandlerInfo[] handlers)
{
OnEventTriggered?.Invoke(eventArgs, eventType);
#if UNITY_EDITOR
if (!EnableDebugRecord) return;
PrepareEditorTrace(handlers, eventArgs);
OnEventTriggeredForEditor?.Invoke(eventArgs, eventType.Name, GetSenderInfo());
#endif
}
#if UNITY_EDITOR
private static void PrepareEditorTrace(EventHandlerInfo[] handlers, EventBase eventArgs)
{
if (!EnableDebugRecord) return;
var listenerList = eventArgs.GetListenerList();
listenerList.Clear();
for (var i = 0; i < handlers.Length; i++)
listenerList.Add(handlers[i]);
}
private static string GetSenderInfo()
{
var senderInfo = "Unknown";
try
{
var trace = new System.Diagnostics.StackTrace(0, false);
for (var i = 0; i < trace.FrameCount; i++)
{
var method = trace.GetFrame(i)?.GetMethod();
var declaringType = method?.DeclaringType;
if (declaringType == null) continue;
if (declaringType == typeof(EventBus) || declaringType.DeclaringType == typeof(EventBus)) continue;
var ns = declaringType.Namespace ?? "";
if (ns.StartsWith("System") || ns.StartsWith("Cysharp") || ns.StartsWith("UnityEngine")) continue;
var className = declaringType.Name;
var methodName = method.Name;
if (declaringType.DeclaringType != null && className.StartsWith("<") && className.Contains(">"))
{
className = declaringType.DeclaringType.Name;
var startIndex = declaringType.Name.IndexOf('<') + 1;
var endIndex = declaringType.Name.IndexOf('>');
if (startIndex > 0 && endIndex > startIndex)
methodName = declaringType.Name.Substring(startIndex, endIndex - startIndex);
}
return $"{className}.{methodName}()";
}
}
catch
{
}
return senderInfo;
}
#endif
public static EventHandlerInfo[] GetEventSubscribers<TEvent>() where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
return EventHandlers.TryGetValue(eventType, out var collection)
? collection.GetHandlers()
: Array.Empty<EventHandlerInfo>();
}
}
public static ListenerList GetListenerList<TEvent>() where TEvent : EventBase
{
var eventType = typeof(TEvent);
lock (EventHandlers)
{
EventHandlers.TryGetValue(eventType, out var collection);
return collection;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AutoRegister(object target)
{
if (target == null) return;
if (IsInstanceRegistered(target)) return;
EventBusRegHelper.RegisterEventHandlers(target);
lock (InstanceLock)
{
if (!RegisteredInstances.Add(target)) return;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInstanceRegistered(object target)
{
if (target == null) return false;
lock (InstanceLock)
{
return RegisteredInstances.Contains(target);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetRegisteredInstanceCount()
{
lock (InstanceLock)
{
return RegisteredInstances.Count;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetRegisteredEventTypeCount()
{
lock (EventHandlers)
{
return EventHandlers.Count;
}
var bus = (ShrinkEventBusInstance)new ShrinkEventBusBuilder()
.SetExceptionHandlingMode(ShrinkEventExceptionHandlingMode.LogAndContinue)
.AllowPerPhaseDispatch()
.Build();
EventBusRegHelper.RegStaticEventHandler(bus);
return bus;
}
}
}
+274 -75
View File
@@ -1,5 +1,6 @@
using System;
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Cysharp.Threading.Tasks;
using UnityEngine;
@@ -8,39 +9,98 @@ using Debug = UnityEngine.Debug;
namespace ShrinkEventBus.Runtime
{
/// <summary>
/// ShrinkEventBus 性能测试
/// ShrinkEventBus 性能测试。
/// 挂在任意 GameObject 上,运行后在 Console 查看结果。
/// 在进行测试时不要打开事件查看器大的追踪实时事件功能
/// 在进行测试时不要打开事件查看器的大量实时追踪。
/// </summary>
public class EventBusBenchmark : MonoBehaviour
public sealed class EventBusBenchmark : MonoBehaviour
{
[Header("同步触发测试")] public int iterations = 1000000;
[Header("同步触发测试")] public int iterations = 1_000_000;
[Header("大批量订阅者测试")] public int subscriberCount = 30;
public int massIterations = 1000;
public int massIterations = 10_000;
[Header("异步触发测试")] public int asyncIterations = 1000;
[Header("异步触发测试")] public int asyncIterations = 1_000;
[Header("注册 / 注销测试")] public int registerIterations = 5000;
[Header("取消事件测试")] public int cancelIterations = 100000;
[Header("注册 / 注销测试")] public int registerIterations = 5_000;
public int scannedRegisterIterations = 1_000;
[Header("取消事件测试")] public int cancelIterations = 100_000;
public class BenchmarkEvent : EventBase
{
public int Value { get; set; }
}
public sealed class DerivedBenchmarkEvent : BenchmarkEvent
{
public bool IsDerived { get; set; }
}
[Cancelable]
public class CancelableBenchmarkEvent : EventBase
{
public int Value { get; set; }
}
private sealed class BenchmarkInstanceSubscriber
{
public int Recorded;
[EventSubscribe(EventPriority.NORMAL)]
private void OnBenchmark(BenchmarkEvent evt)
{
Recorded = evt.Value;
}
}
private static class BenchmarkStaticSubscriber
{
public static int Recorded;
[EventSubscribe(EventPriority.NORMAL)]
private static void OnBenchmark(BenchmarkEvent evt)
{
Recorded = evt.Value;
}
}
private static class BenchmarkMethodSubscriber
{
public static int Recorded;
[EventSubscribe(EventPriority.NORMAL)]
private static void OnBenchmark(BenchmarkEvent evt)
{
Recorded = evt.Value;
}
}
private sealed class PhaseSubscriber
{
public int Sum;
[EventSubscribe(EventPriority.HIGH)]
private void OnHigh(BenchmarkEvent evt)
{
Sum += evt.Value + 1;
}
[EventSubscribe(EventPriority.NORMAL)]
private void OnNormal(BenchmarkEvent evt)
{
Sum += evt.Value + 10;
}
[EventSubscribe(EventPriority.LOW)]
private void OnLow(BenchmarkEvent evt)
{
Sum += evt.Value + 100;
}
}
private readonly StringBuilder _report = new();
private void Start()
{
RunAllBenchmarks().Forget();
@@ -52,8 +112,9 @@ namespace ShrinkEventBus.Runtime
_report.AppendLine("╔══════════════════════════════════════════════════╗");
_report.AppendLine("║ ShrinkEventBus Benchmark Report ║");
_report.AppendLine(
$"║ iter={iterations,8} mass={massIterations,5} reg={registerIterations,5} cancel={cancelIterations,6} ║");
$"║ iter={iterations,8} mass={massIterations,6} reg={registerIterations,5} scan={scannedRegisterIterations,4} ║");
_report.AppendLine("╚══════════════════════════════════════════════════╝");
_report.AppendLine(" 基准对象:独立实例 bus(AllowPerPhaseDispatch 已开启)");
Log("开始:无订阅者触发");
Bench_TriggerEvent_NoSubscriber();
@@ -63,16 +124,36 @@ namespace ShrinkEventBus.Runtime
Bench_TriggerEvent_SingleSubscriber();
await UniTask.Yield();
Log("开始:父事件监听子事件");
Bench_TriggerEvent_InheritedSubscriber();
await UniTask.Yield();
Log($"开始:{subscriberCount} 订阅者同步触发");
Bench_TriggerEvent_MassSubscribers();
await UniTask.Yield();
Log("开始:按 phase 分发");
Bench_TriggerEvent_PerPhase();
await UniTask.Yield();
Log("开始:异步触发");
await Bench_TriggerEventAsync();
await UniTask.Yield();
Log("开始:注册/注销");
Bench_RegisterUnregister();
Log("开始:手工 delegate 注册/注销");
Bench_RegisterUnregister_ManualDelegate();
await UniTask.Yield();
Log("开始:实例扫描注册/注销");
Bench_RegisterUnregister_InstanceScan();
await UniTask.Yield();
Log("开始:静态类型扫描注册/注销");
Bench_RegisterUnregister_StaticScan();
await UniTask.Yield();
Log("开始:MethodInfo 注册/注销");
Bench_RegisterUnregister_MethodScan();
await UniTask.Yield();
Log("开始:EventPool vs new");
@@ -87,95 +168,141 @@ namespace ShrinkEventBus.Runtime
Debug.Log(_report.ToString());
}
private void Bench_TriggerEvent_NoSubscriber()
{
EventBus.ClearAllSubscribersForEvent<BenchmarkEvent>();
var bus = CreateBenchmarkBus();
var evt = new BenchmarkEvent { Value = 1 };
for (var i = 0; i < 500; i++) EventBus.TriggerEvent(evt);
Warmup(() => bus.TriggerEvent(evt), 500);
var sw = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++) EventBus.TriggerEvent(evt);
for (var i = 0; i < iterations; i++)
bus.TriggerEvent(evt);
sw.Stop();
Record("TriggerEvent × 无订阅者(基线)", sw.Elapsed.TotalMilliseconds, iterations);
Log("完成:无订阅者触发");
}
private void Bench_TriggerEvent_SingleSubscriber()
{
EventBus.ClearAllSubscribersForEvent<BenchmarkEvent>();
var bus = CreateBenchmarkBus();
var dummy = 0;
void Handler(BenchmarkEvent e)
void Handler(BenchmarkEvent evt)
{
dummy = e.Value;
dummy = evt.Value;
}
EventBus.RegisterEvent<BenchmarkEvent>(Handler, EventPriority.NORMAL);
bus.RegisterEvent<BenchmarkEvent>(Handler, EventPriority.NORMAL);
var evt = new BenchmarkEvent { Value = 1 };
for (var i = 0; i < 500; i++) EventBus.TriggerEvent(evt);
Warmup(() => bus.TriggerEvent(evt), 500);
var sw = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++) EventBus.TriggerEvent(evt);
for (var i = 0; i < iterations; i++)
bus.TriggerEvent(evt);
sw.Stop();
Record("TriggerEvent × 单订阅者同步", sw.Elapsed.TotalMilliseconds, iterations);
Log("完成:单订阅者同步触发");
EventBus.UnregisterEvent<BenchmarkEvent>(Handler);
bus.UnregisterEvent<BenchmarkEvent>(Handler);
GC.KeepAlive(dummy);
}
private void Bench_TriggerEvent_InheritedSubscriber()
{
var bus = CreateBenchmarkBus();
var dummy = 0;
void Handler(BenchmarkEvent evt)
{
dummy = evt.Value;
}
bus.RegisterEvent<BenchmarkEvent>(Handler, EventPriority.NORMAL);
var evt = new DerivedBenchmarkEvent { Value = 1, IsDerived = true };
Warmup(() => bus.TriggerEvent(evt), 500);
var sw = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
bus.TriggerEvent(evt);
sw.Stop();
Record("TriggerEvent × 父事件监听子事件", sw.Elapsed.TotalMilliseconds, iterations);
Log("完成:父事件监听子事件");
bus.UnregisterEvent<BenchmarkEvent>(Handler);
GC.KeepAlive(dummy);
}
private void Bench_TriggerEvent_MassSubscribers()
{
EventBus.ClearAllSubscribersForEvent<BenchmarkEvent>();
var bus = CreateBenchmarkBus();
var dummy = 0;
var handlers = new Action<BenchmarkEvent>[subscriberCount];
for (var i = 0; i < subscriberCount; i++)
{
var captured = i;
handlers[i] = e => { dummy = captured + e.Value; };
EventBus.RegisterEvent<BenchmarkEvent>(handlers[i], EventPriority.NORMAL);
handlers[i] = evt => { dummy = captured + evt.Value; };
bus.RegisterEvent<BenchmarkEvent>(handlers[i], EventPriority.NORMAL);
}
var evt = new BenchmarkEvent { Value = 1 };
for (var i = 0; i < 100; i++) EventBus.TriggerEvent(evt);
Warmup(() => bus.TriggerEvent(evt), 100);
var sw = Stopwatch.StartNew();
for (var i = 0; i < massIterations; i++) EventBus.TriggerEvent(evt);
for (var i = 0; i < massIterations; i++)
bus.TriggerEvent(evt);
sw.Stop();
Record($"TriggerEvent × {subscriberCount} 订阅者同步", sw.Elapsed.TotalMilliseconds, massIterations);
Log($"完成:{subscriberCount} 订阅者同步触发");
foreach (var h in handlers) EventBus.UnregisterEvent<BenchmarkEvent>(h);
foreach (var handler in handlers)
bus.UnregisterEvent<BenchmarkEvent>(handler);
GC.KeepAlive(dummy);
}
private void Bench_TriggerEvent_PerPhase()
{
var bus = CreateBenchmarkBus();
var subscriber = new PhaseSubscriber();
bus.Register(subscriber);
var evt = new BenchmarkEvent { Value = 1 };
Warmup(() => bus.TriggerEvent(EventPriority.HIGH, evt), 500);
var sw = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
bus.TriggerEvent(EventPriority.HIGH, evt);
sw.Stop();
Record("TriggerEvent(Phase=HIGH) × 3 phase 监听", sw.Elapsed.TotalMilliseconds, iterations);
Log("完成:按 phase 分发");
bus.Unregister(subscriber);
GC.KeepAlive(subscriber.Sum);
}
private async UniTask Bench_TriggerEventAsync()
{
EventBus.ClearAllSubscribersForEvent<BenchmarkEvent>();
var bus = CreateBenchmarkBus();
var dummy = 0;
async UniTask Handler(BenchmarkEvent e)
async UniTask Handler(BenchmarkEvent evt)
{
dummy = e.Value;
dummy = evt.Value;
await UniTask.CompletedTask;
}
EventBus.RegisterEvent<BenchmarkEvent>(Handler, EventPriority.NORMAL);
bus.RegisterEvent<BenchmarkEvent>(Handler, EventPriority.NORMAL);
var evt = new BenchmarkEvent { Value = 1 };
for (var i = 0; i < 10; i++) await EventBus.TriggerEventAsync(evt);
await WarmupAsync(() => bus.TriggerEventAsync(evt), 10);
const int perFrame = 10;
var totalMs = 0.0;
@@ -186,7 +313,8 @@ namespace ShrinkEventBus.Runtime
await UniTask.Yield();
var count = Math.Min(perFrame, asyncIterations - ran);
var sw = Stopwatch.StartNew();
for (var i = 0; i < count; i++) await EventBus.TriggerEventAsync(evt);
for (var i = 0; i < count; i++)
await bus.TriggerEventAsync(evt);
sw.Stop();
totalMs += sw.Elapsed.TotalMilliseconds;
ran += count;
@@ -195,48 +323,102 @@ namespace ShrinkEventBus.Runtime
Record("TriggerEventAsync × 单订阅者异步", totalMs, asyncIterations);
Log("完成:异步触发");
EventBus.UnregisterEvent<BenchmarkEvent>(Handler);
bus.UnregisterEvent<BenchmarkEvent>(Handler);
GC.KeepAlive(dummy);
}
private void Bench_RegisterUnregister()
private void Bench_RegisterUnregister_ManualDelegate()
{
var bus = CreateBenchmarkBus();
var dummy = 0;
var handlers = new Action<BenchmarkEvent>[registerIterations];
for (var i = 0; i < registerIterations; i++)
{
var captured = i;
handlers[i] = e => { dummy = captured; };
handlers[i] = evt => { dummy = captured; };
}
var swReg = Stopwatch.StartNew();
for (var i = 0; i < registerIterations; i++)
EventBus.RegisterEvent<BenchmarkEvent>(handlers[i], EventPriority.NORMAL);
bus.RegisterEvent<BenchmarkEvent>(handlers[i], EventPriority.NORMAL);
swReg.Stop();
Record("RegisterEvent", swReg.Elapsed.TotalMilliseconds, registerIterations);
Log("完成:RegisterEvent");
Record("RegisterEvent(delegate)", swReg.Elapsed.TotalMilliseconds, registerIterations);
var swUnreg = Stopwatch.StartNew();
for (var i = 0; i < registerIterations; i++)
EventBus.UnregisterEvent<BenchmarkEvent>(handlers[i]);
bus.UnregisterEvent<BenchmarkEvent>(handlers[i]);
swUnreg.Stop();
Record("UnregisterEvent", swUnreg.Elapsed.TotalMilliseconds, registerIterations);
Log("完成:UnregisterEvent");
Record("UnregisterEvent(delegate)", swUnreg.Elapsed.TotalMilliseconds, registerIterations);
Log("完成:手工 delegate 注册/注销");
GC.KeepAlive(dummy);
}
private void Bench_RegisterUnregister_InstanceScan()
{
var bus = CreateBenchmarkBus();
var subscribers = new BenchmarkInstanceSubscriber[scannedRegisterIterations];
for (var i = 0; i < subscribers.Length; i++)
subscribers[i] = new BenchmarkInstanceSubscriber();
var sw = Stopwatch.StartNew();
for (var i = 0; i < subscribers.Length; i++)
{
var subscriber = subscribers[i];
bus.Register(subscriber);
bus.Unregister(subscriber);
}
sw.Stop();
Record("Register/Unregister(instance scan)", sw.Elapsed.TotalMilliseconds, subscribers.Length);
Log("完成:实例扫描注册/注销");
}
private void Bench_RegisterUnregister_StaticScan()
{
var bus = CreateBenchmarkBus();
var sw = Stopwatch.StartNew();
for (var i = 0; i < scannedRegisterIterations; i++)
{
bus.Register(typeof(BenchmarkStaticSubscriber));
bus.Unregister(typeof(BenchmarkStaticSubscriber));
}
sw.Stop();
Record("Register/Unregister(static type scan)", sw.Elapsed.TotalMilliseconds, scannedRegisterIterations);
Log("完成:静态类型扫描注册/注销");
}
private void Bench_RegisterUnregister_MethodScan()
{
var bus = CreateBenchmarkBus();
var method = typeof(BenchmarkMethodSubscriber).GetMethod("OnBenchmark",
BindingFlags.Static | BindingFlags.NonPublic);
if (method == null)
throw new MissingMethodException(typeof(BenchmarkMethodSubscriber).FullName, "OnBenchmark");
var sw = Stopwatch.StartNew();
for (var i = 0; i < scannedRegisterIterations; i++)
{
bus.Register(method);
bus.Unregister(method);
}
sw.Stop();
Record("Register/Unregister(MethodInfo)", sw.Elapsed.TotalMilliseconds, scannedRegisterIterations);
Log("完成:MethodInfo 注册/注销");
}
private void Bench_EventPool_vs_New()
{
EventBus.ClearAllSubscribersForEvent<BenchmarkEvent>();
var bus = CreateBenchmarkBus();
void Handler(BenchmarkEvent e)
void Handler(BenchmarkEvent evt)
{
}
EventBus.RegisterEvent<BenchmarkEvent>(Handler, EventPriority.NORMAL);
bus.RegisterEvent<BenchmarkEvent>(Handler, EventPriority.NORMAL);
for (var i = 0; i < 64; i++)
EventPool<BenchmarkEvent>.Release(EventPool<BenchmarkEvent>.Get());
@@ -246,75 +428,92 @@ namespace ShrinkEventBus.Runtime
var swNew = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
{
var e = new BenchmarkEvent { Value = i };
EventBus.TriggerEvent(e);
var evt = new BenchmarkEvent { Value = i };
bus.TriggerEvent(evt);
}
swNew.Stop();
var gcNew = GC.CollectionCount(0) - gcBefore;
Record($"TriggerEvent × new() [GC Gen0={gcNew,3}]", swNew.Elapsed.TotalMilliseconds, iterations);
Log("完成:new() 分配");
GC.Collect();
gcBefore = GC.CollectionCount(0);
var swPool = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
{
using var e = EventPool<BenchmarkEvent>.Get();
e.Value = i;
EventBus.TriggerEvent(e);
using var evt = EventPool<BenchmarkEvent>.Get();
evt.Value = i;
bus.TriggerEvent(evt);
}
swPool.Stop();
var gcPool = GC.CollectionCount(0) - gcBefore;
Record($"TriggerEvent × Pool.Get() [GC Gen0={gcPool,3}]", swPool.Elapsed.TotalMilliseconds, iterations);
Log("完成:Pool.Get()");
Log("完成:EventPool vs new");
EventBus.UnregisterEvent<BenchmarkEvent>(Handler);
bus.UnregisterEvent<BenchmarkEvent>(Handler);
}
private void Bench_CanceledEvent_Skip()
{
EventBus.ClearAllSubscribersForEvent<CancelableBenchmarkEvent>();
var bus = CreateBenchmarkBus();
var dummy = 0;
var handlers = new Action<CancelableBenchmarkEvent>[10];
for (var i = 0; i < 10; i++)
for (var i = 0; i < handlers.Length; i++)
{
handlers[i] = e => { dummy = e.Value; };
EventBus.RegisterEvent<CancelableBenchmarkEvent>(handlers[i], EventPriority.NORMAL);
handlers[i] = evt => { dummy = evt.Value; };
bus.RegisterEvent<CancelableBenchmarkEvent>(handlers[i], EventPriority.NORMAL);
}
var evt = new CancelableBenchmarkEvent { Value = 1 };
evt.SetCanceled(true);
for (var i = 0; i < 500; i++) EventBus.TriggerEvent(evt);
Warmup(() => bus.TriggerEvent(evt), 500);
var sw = Stopwatch.StartNew();
for (var i = 0; i < cancelIterations; i++) EventBus.TriggerEvent(evt);
for (var i = 0; i < cancelIterations; i++)
bus.TriggerEvent(evt);
sw.Stop();
Record("TriggerEvent × 已取消事件跳过(10 订阅者)", sw.Elapsed.TotalMilliseconds, cancelIterations);
Log("完成:已取消事件跳过");
foreach (var h in handlers)
EventBus.UnregisterEvent<CancelableBenchmarkEvent>(h);
foreach (var handler in handlers)
bus.UnregisterEvent<CancelableBenchmarkEvent>(handler);
GC.KeepAlive(dummy);
}
private static IShrinkEventBus CreateBenchmarkBus()
{
return EventBus.CreateBus(builder => builder.AllowPerPhaseDispatch());
}
private static void Log(string msg) =>
private static void Warmup(Action action, int count)
{
for (var i = 0; i < count; i++)
action();
}
private static async UniTask WarmupAsync(Func<UniTask<bool>> action, int count)
{
for (var i = 0; i < count; i++)
await action();
}
private static void Log(string msg)
{
Debug.Log($"[Benchmark] {msg}");
}
private void Record(string label, double totalMs, int count)
{
var perOp = totalMs / count * 1000.0;
var throughput = count / (totalMs / 1000.0);
var throughput = totalMs <= 0.0001 ? 0 : count / (totalMs / 1000.0);
_report.AppendLine($"\n ▶ {label}");
_report.AppendLine($" 总耗时 : {totalMs,10:F3} ms");
_report.AppendLine($" 单次 : {perOp,10:F4} μs/op");
_report.AppendLine($" 吞吐量 : {throughput,10:F0} ops/s");
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
namespace ShrinkEventBus
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class EventBusStaticRegistryAttribute : Attribute
{
public EventBusStaticRegistryAttribute(params Type[] subscriberTypes)
{
SubscriberTypes = subscriberTypes ?? Array.Empty<Type>();
}
public Type[] SubscriberTypes { get; }
}
internal static class EventBusGeneratedRegistry
{
public static IReadOnlyList<Type> GetStaticSubscriberTypes()
{
var types = new List<Type>();
var seen = new HashSet<Type>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
object[] attributes;
try
{
attributes = assembly.GetCustomAttributes(typeof(EventBusStaticRegistryAttribute), false);
}
catch
{
continue;
}
foreach (var attribute in attributes.OfType<EventBusStaticRegistryAttribute>())
{
foreach (var subscriberType in attribute.SubscriberTypes ?? Array.Empty<Type>())
{
if (subscriberType == null || !seen.Add(subscriberType))
continue;
types.Add(subscriberType);
}
}
}
return types;
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 04809187aa0ce3b43a4df12b048a1c98
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+161 -78
View File
@@ -1,4 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
@@ -6,104 +8,185 @@ using UnityEngine;
namespace ShrinkEventBus
{
public static class EventBusRegHelper
internal static class EventBusRegHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegStaticEventHandler() => RegisterEventHandlersInternal(null);
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 RegisterEventHandlers(object target) => RegisterEventHandlersInternal(target);
private static void RegisterEventHandlersInternal(object target)
public static void RegisterTarget(ShrinkEventBusInstance bus, object target, bool requireSubscriberAttribute,
bool lenientWhenNoMethods = false)
{
var isStatic = target == null;
if (bus == null)
throw new ArgumentNullException(nameof(bus));
if (target == null)
throw new ArgumentNullException(nameof(target));
if (isStatic)
switch (target)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
foreach (var type in assembly.GetTypes())
{
if (type.GetCustomAttributes(typeof(EventBusSubscriberAttribute), false).Length == 0)
continue;
ScanMethodsAndRegister(null,
type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
}
}
catch
{
// ignored
}
}
}
else
{
var type = target.GetType();
if (type.GetCustomAttributes(typeof(EventBusSubscriberAttribute), false).Length == 0) return;
ScanMethodsAndRegister(target,
type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
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 ScanMethodsAndRegister(object target, MethodInfo[] methods)
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)
{
if (target != null && method.IsStatic) continue;
var attributes = method.GetCustomAttributes(typeof(EventSubscribeAttribute), false);
if (attributes.Length == 0) continue;
var subscribeAttr = (EventSubscribeAttribute)attributes[0];
var parameters = method.GetParameters();
if (parameters.Length != 1) continue;
var parameterType = parameters[0].ParameterType;
if (!typeof(EventBase).IsAssignableFrom(parameterType)) continue;
ProcessMethodRegistration(target, method, subscribeAttr, parameterType);
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 ProcessMethodRegistration(object target, MethodInfo method,
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)
{
try
{
string scope = target == null ? "Static" : "Instance";
string typeName = target == null ? method.DeclaringType?.Name : target.GetType().Name;
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);
EventBus.RegisterEventInternal(parameterType, handlerDelegate, subscribeAttr.Priority,
subscribeAttr.NumericPriority, subscribeAttr.ReceiveCanceled,
$"{scope} {typeName}.{method.Name} (UniTask)", method);
}
else if (method.ReturnType == typeof(void))
{
var actionType = typeof(Action<>).MakeGenericType(parameterType);
var actionDelegate = target == null
? Delegate.CreateDelegate(actionType, method)
: Delegate.CreateDelegate(actionType, target, method);
EventBus.RegisterEventInternal(parameterType, actionDelegate, subscribeAttr.Priority,
subscribeAttr.NumericPriority, subscribeAttr.ReceiveCanceled,
$"{scope} {typeName}.{method.Name} (Sync)", method);
}
else
{
Debug.LogWarning(
$"[EventBus] Registration failed: {typeName}.{method.Name} return type must be void or UniTask.");
}
}
catch (Exception ex)
if (method.ReturnType == typeof(UniTask))
{
#if UNITY_EDITOR
Debug.LogWarning($"[EventBus] Registration failed for {method.Name}: {ex.Message}");
#endif
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.");
}
}
}
}
-7
View File
@@ -1,7 +0,0 @@
namespace ShrinkEventBus
{
internal static class EventCache<TEvent> where TEvent : EventBase
{
public static ListenerList List;
}
}
-3
View File
@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 0c41a516998341adbddce082cec09938
timeCreated: 1772889485
+3 -13
View File
@@ -18,8 +18,9 @@ namespace ShrinkEventBus
throw new InvalidOperationException(
$"Cannot clone event type {source.GetType().FullName}. A public parameterless constructor is required.");
// 先固化 EventId,让克隆与原事件共享同一个派发标识
_ = source.EventId;
CopyFields(source, clone);
CopyListenerSnapshot(source, clone);
clone.ReleaseAction = null;
clone.IsInPool = false;
return clone;
@@ -32,16 +33,6 @@ namespace ShrinkEventBus
fields[i].SetValue(target, fields[i].GetValue(source));
}
private static void CopyListenerSnapshot(EventBase source, EventBase target)
{
var targetListeners = target.GetListenerList();
targetListeners.Clear();
var sourceHandlers = source.GetListenerList().GetHandlers();
for (var i = 0; i < sourceHandlers.Length; i++)
targetListeners.Add(sourceHandlers[i]);
}
private static FieldInfo[] GetCopyableFields(Type type)
{
lock (CacheLock)
@@ -77,8 +68,7 @@ namespace ShrinkEventBus
private static bool ShouldSkipField(FieldInfo field)
{
return field.Name is "_listenerList"
or "<ReleaseAction>k__BackingField"
return field.Name is "<ReleaseAction>k__BackingField"
or "<IsInPool>k__BackingField";
}
}
+76 -3
View File
@@ -1,5 +1,6 @@
using System;
using System.Reflection;
using Cysharp.Threading.Tasks;
#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。
@@ -12,7 +13,10 @@ namespace ShrinkEventBus
public class EventHandlerInfo
{
public long SubscriptionId { get; }
public Delegate Handler { get; }
internal Action<EventBase>? SyncInvoker { get; }
internal Func<EventBase, UniTask>? AsyncInvoker { get; }
public EventPriority Priority { get; }
public int NumericPriority { get; }
public bool ReceiveCanceled { get; }
@@ -24,11 +28,17 @@ namespace ShrinkEventBus
public MethodInfo? OriginalMethod { get; }
public string OriginalMethodName { get; }
public Type? OriginalDeclaringType { get; }
public DateTime RegisteredAtUtc { get; }
public EventHandlerInfo(Delegate handler, EventPriority priority, int numericPriority,
bool receiveCanceled, string debugInfo = "", MethodInfo? originalMethod = null)
private EventHandlerInfo(long subscriptionId, Delegate handler, Action<EventBase>? syncInvoker,
Func<EventBase, UniTask>? asyncInvoker,
EventPriority priority, int numericPriority, bool receiveCanceled, string debugInfo = "",
MethodInfo? originalMethod = null)
{
SubscriptionId = subscriptionId;
Handler = handler;
SyncInvoker = syncInvoker;
AsyncInvoker = asyncInvoker;
Priority = priority;
NumericPriority = numericPriority;
ReceiveCanceled = receiveCanceled;
@@ -50,6 +60,8 @@ namespace ShrinkEventBus
OriginalMethodName = MethodName;
OriginalDeclaringType = DeclaringType;
}
RegisteredAtUtc = DateTime.UtcNow;
}
private static MethodInfo? ExtractOriginalMethodFromWrapper(Delegate handler)
@@ -64,5 +76,66 @@ namespace ShrinkEventBus
public string DisplayMethodName => OriginalMethodName;
public Type DisplayDeclaringType => OriginalDeclaringType ?? DeclaringType;
public bool MatchesMethod(MethodInfo method)
{
return method != null && (ReferenceEquals(Method, method) || ReferenceEquals(OriginalMethod, method));
}
public bool MatchesDeclaringType(Type declaringType)
{
return declaringType != null &&
(DeclaringType == declaringType || OriginalDeclaringType == declaringType);
}
public static EventHandlerInfo Create(long subscriptionId, Delegate handler, Type eventType,
EventPriority priority, int numericPriority, bool receiveCanceled, string debugInfo = "",
MethodInfo? originalMethod = null)
{
if (handler == null)
throw new System.ArgumentNullException(nameof(handler));
if (eventType == null)
throw new System.ArgumentNullException(nameof(eventType));
Action<EventBase>? syncInvoker = null;
Func<EventBase, UniTask>? asyncInvoker = null;
if (handler.Method.ReturnType == typeof(void))
syncInvoker = CreateSyncInvoker(eventType, handler);
else if (handler.Method.ReturnType == typeof(UniTask))
asyncInvoker = CreateAsyncInvoker(eventType, handler);
else
throw new System.ArgumentException(
$"Unsupported event handler return type {handler.Method.ReturnType.FullName} for {handler.Method}.");
return new EventHandlerInfo(subscriptionId, handler, syncInvoker, asyncInvoker, priority, numericPriority,
receiveCanceled, debugInfo, originalMethod);
}
private static Action<EventBase> CreateSyncInvoker(Type eventType, Delegate handler)
{
var factory = typeof(EventHandlerInfo).GetMethod(nameof(CreateSyncInvokerGeneric),
BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(eventType);
return (Action<EventBase>)factory.Invoke(null, new object[] { handler })!;
}
private static Func<EventBase, UniTask> CreateAsyncInvoker(Type eventType, Delegate handler)
{
var factory = typeof(EventHandlerInfo).GetMethod(nameof(CreateAsyncInvokerGeneric),
BindingFlags.NonPublic | BindingFlags.Static)!.MakeGenericMethod(eventType);
return (Func<EventBase, UniTask>)factory.Invoke(null, new object[] { handler })!;
}
private static Action<EventBase> CreateSyncInvokerGeneric<TEvent>(Delegate handler) where TEvent : EventBase
{
var typedHandler = (Action<TEvent>)handler;
return eventArgs => typedHandler((TEvent)eventArgs);
}
private static Func<EventBase, UniTask> CreateAsyncInvokerGeneric<TEvent>(Delegate handler)
where TEvent : EventBase
{
var typedHandler = (Func<TEvent, UniTask>)handler;
return eventArgs => typedHandler((TEvent)eventArgs);
}
}
}
}
+11 -6
View File
@@ -1,10 +1,12 @@
using System;
using System;
using System.Collections.Generic;
namespace ShrinkEventBus
{
public static class EventPool<T> where T : EventBase, new()
{
private const int MaxPoolSize = 128;
private static readonly Stack<T> Pool = new(32);
private static readonly object Lock = new();
private static readonly Action<EventBase> CachedReleaseAction = e => Release((T)e);
@@ -28,15 +30,18 @@ namespace ShrinkEventBus
public static void Release(T evt)
{
if (evt == null || evt.IsInPool) return;
evt.ResetInternal();
evt.IsInPool = true;
if (evt == null)
return;
lock (Lock)
{
if (evt.IsInPool || Pool.Count >= MaxPoolSize)
return;
evt.ResetInternal();
evt.IsInPool = true;
Pool.Push(evt);
}
}
}
}
}
+1 -1
View File
@@ -18,7 +18,7 @@
{
>= 100 => EventPriority.HIGHEST,
>= 50 => EventPriority.HIGH,
> 0 => EventPriority.NORMAL,
>= 0 => EventPriority.NORMAL,
>= -50 => EventPriority.LOW,
_ => EventPriority.LOWEST
};
+109
View File
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Cysharp.Threading.Tasks;
namespace ShrinkEventBus
{
public interface IShrinkEventSubscription : IDisposable
{
long SubscriptionId { get; }
bool IsDisposed { get; }
}
public readonly struct ShrinkEventSubscriptionSnapshot
{
public ShrinkEventSubscriptionSnapshot(long subscriptionId, Type eventType, EventPriority priority,
int numericPriority, bool receiveCanceled, object target, string targetTypeName, string methodName,
string debugInfo, DateTime registeredAtUtc)
{
SubscriptionId = subscriptionId;
EventType = eventType ?? throw new ArgumentNullException(nameof(eventType));
Priority = priority;
NumericPriority = numericPriority;
ReceiveCanceled = receiveCanceled;
Target = target;
TargetTypeName = targetTypeName ?? string.Empty;
MethodName = methodName ?? string.Empty;
DebugInfo = debugInfo ?? string.Empty;
RegisteredAtUtc = registeredAtUtc;
}
public long SubscriptionId { get; }
public Type EventType { get; }
public EventPriority Priority { get; }
public int NumericPriority { get; }
public bool ReceiveCanceled { get; }
public object Target { get; }
public string TargetTypeName { get; }
public string MethodName { get; }
public string DebugInfo { get; }
public DateTime RegisteredAtUtc { get; }
public bool IsStaticHandler => Target == null;
}
public interface IShrinkEventBus
{
event Action<EventBase, Type> OnEventTriggered;
#if UNITY_EDITOR
event Action<EventBase, string, string, EventHandlerInfo[]> OnEventTriggeredForEditor;
bool EnableDebugRecord { get; set; }
#endif
bool IsStarted { get; }
void Start();
void AutoRegister(object target);
void Register(object target);
void Unregister(object target);
void RegisterEvent<TEvent>(Action<TEvent> handler, EventPriority priority = EventPriority.NORMAL,
bool receiveCanceled = false) where TEvent : EventBase;
void RegisterEvent<TEvent>(Action<TEvent> handler, int priority) where TEvent : EventBase;
void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler, EventPriority priority = EventPriority.NORMAL,
bool receiveCanceled = false) where TEvent : EventBase;
void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler, int priority) where TEvent : EventBase;
IShrinkEventSubscription SubscribeEvent<TEvent>(Action<TEvent> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase;
IShrinkEventSubscription SubscribeEvent<TEvent>(Action<TEvent> handler, int priority)
where TEvent : EventBase;
IShrinkEventSubscription SubscribeEvent<TEvent>(Func<TEvent, UniTask> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase;
IShrinkEventSubscription SubscribeEvent<TEvent>(Func<TEvent, UniTask> handler, int priority)
where TEvent : EventBase;
void UnregisterEvent<TEvent>(Action<TEvent> handler) where TEvent : EventBase;
void UnregisterEvent<TEvent>(Func<TEvent, UniTask> handler) where TEvent : EventBase;
void ClearAllSubscribersForEvent<TEvent>() where TEvent : EventBase;
void UnregisterAllEventsForObject(object targetObject);
void UnregisterAllEvents();
bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase;
bool TriggerEvent<TEvent>(EventPriority phase, TEvent eventArgs) where TEvent : EventBase;
UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase;
UniTask<bool> TriggerEventAsync<TEvent>(EventPriority phase, TEvent eventArgs) where TEvent : EventBase;
EventHandlerInfo[] GetEventSubscribers<TEvent>() where TEvent : EventBase;
ListenerList GetListenerList<TEvent>() where TEvent : EventBase;
IReadOnlyDictionary<Type, EventHandlerInfo[]> GetAllSubscribersSnapshot();
IReadOnlyList<ShrinkEventSubscriptionSnapshot> GetActiveSubscriptionsSnapshot();
bool IsInstanceRegistered(object target);
int GetRegisteredInstanceCount();
int GetRegisteredEventTypeCount();
}
public interface IShrinkEventExceptionHandler
{
void HandleException(IShrinkEventBus bus, EventBase eventArgs, EventHandlerInfo[] listeners, int index,
Exception exception);
}
public enum ShrinkEventExceptionHandlingMode
{
LogAndContinue,
Throw,
LogAndThrow
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cb69f308f5c727542ac602709b02c628
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+179 -46
View File
@@ -1,50 +1,84 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
namespace ShrinkEventBus
{
public class ListenerList
{
private readonly object _lock = new();
private readonly List<EventHandlerInfo> _list = new();
private static readonly EventPriority[] Priorities = (EventPriority[])Enum.GetValues(typeof(EventPriority));
private readonly object _lock;
private readonly List<EventHandlerInfo>[] _priorityBuckets;
private readonly ListenerList? _parent;
private List<ListenerList>? _children;
private EventHandlerInfo[] _snapshot = Array.Empty<EventHandlerInfo>();
private readonly EventHandlerInfo[]?[] _phaseSnapshots = new EventHandlerInfo[Priorities.Length][];
private bool _dirty;
public void Add(Delegate handler, EventPriority priority, int numericPriority,
bool receiveCanceled, string debugInfo = "", MethodInfo? originalMethod = null)
public ListenerList(ListenerList? parent = null) : this(parent, null)
{
var newInfo = new EventHandlerInfo(handler, priority, numericPriority,
receiveCanceled, debugInfo, originalMethod);
AddInfo(newInfo);
}
// 同一条父子链必须共用一把锁,否则脏标记传播与快照重建会产生竞态
internal ListenerList(ListenerList? parent, object? sharedLock)
{
_lock = sharedLock ?? parent?._lock ?? new object();
_priorityBuckets = new List<EventHandlerInfo>[Priorities.Length];
for (var i = 0; i < _priorityBuckets.Length; i++)
_priorityBuckets[i] = new List<EventHandlerInfo>();
_parent = parent;
_parent?.AddChild(this);
_dirty = true;
}
public int LocalCount
{
get
{
lock (_lock)
{
var count = 0;
foreach (var list in _priorityBuckets)
count += list.Count;
return count;
}
}
}
public int Count => GetHandlers().Length;
public void Add(EventHandlerInfo info)
{
AddInfo(info);
}
if (info == null)
throw new ArgumentNullException(nameof(info));
private void AddInfo(EventHandlerInfo info)
{
lock (_lock)
{
var index = BinarySearchInsertIndex(info.Priority, info.NumericPriority);
_list.Insert(index, info);
_dirty = true;
var list = _priorityBuckets[(int)info.Priority];
var index = BinarySearchInsertIndex(list, info.NumericPriority);
list.Insert(index, info);
MarkDirty();
}
}
public bool Remove(Delegate handler)
{
if (handler == null)
return false;
lock (_lock)
{
for (var i = 0; i < _list.Count; i++)
foreach (var list in _priorityBuckets)
{
if (_list[i].Handler.Equals(handler))
for (var i = 0; i < list.Count; i++)
{
_list.RemoveAt(i);
_dirty = true;
if (!list[i].Handler.Equals(handler))
continue;
list.RemoveAt(i);
MarkDirty();
return true;
}
}
@@ -53,21 +87,55 @@ namespace ShrinkEventBus
}
}
public int RemoveWhere(Predicate<EventHandlerInfo> predicate)
{
if (predicate == null)
return 0;
lock (_lock)
{
var removedCount = 0;
foreach (var list in _priorityBuckets)
{
for (var i = list.Count - 1; i >= 0; i--)
{
if (!predicate(list[i]))
continue;
list.RemoveAt(i);
removedCount++;
}
}
if (removedCount > 0)
MarkDirty();
return removedCount;
}
}
public void RemoveTarget(object target)
{
if (target == null)
return;
lock (_lock)
{
var removed = false;
for (var i = _list.Count - 1; i >= 0; i--)
foreach (var list in _priorityBuckets)
{
if (_list[i].Target == target)
for (var i = list.Count - 1; i >= 0; i--)
{
_list.RemoveAt(i);
if (!ReferenceEquals(list[i].Target, target))
continue;
list.RemoveAt(i);
removed = true;
}
}
if (removed) _dirty = true;
if (removed)
MarkDirty();
}
}
@@ -76,20 +144,20 @@ namespace ShrinkEventBus
lock (_lock)
{
if (_dirty)
{
_snapshot = _list.ToArray();
_dirty = false;
}
RebuildSnapshot();
return _snapshot;
}
}
public int Count
public EventHandlerInfo[] GetHandlers(EventPriority priority)
{
get
lock (_lock)
{
lock (_lock) return _list.Count;
if (_dirty)
RebuildSnapshot();
return _phaseSnapshots[(int)priority] ?? Array.Empty<EventHandlerInfo>();
}
}
@@ -97,33 +165,98 @@ namespace ShrinkEventBus
{
lock (_lock)
{
_list.Clear();
_snapshot = Array.Empty<EventHandlerInfo>();
_dirty = false;
foreach (var list in _priorityBuckets)
list.Clear();
MarkDirty();
}
}
private int BinarySearchInsertIndex(EventPriority priority, int numericPriority)
private void AddChild(ListenerList child)
{
if (child == null)
return;
lock (_lock)
{
_children ??= new List<ListenerList>(2);
_children.Add(child);
}
}
private void MarkDirty()
{
_dirty = true;
_snapshot = Array.Empty<EventHandlerInfo>();
Array.Clear(_phaseSnapshots, 0, _phaseSnapshots.Length);
if (_children == null)
return;
for (var i = 0; i < _children.Count; i++)
_children[i].MarkDirty();
}
private void RebuildSnapshot()
{
var merged = new List<EventHandlerInfo>();
for (var i = 0; i < Priorities.Length; i++)
{
var parentHandlers = _parent != null
? _parent.GetHandlers(Priorities[i])
: Array.Empty<EventHandlerInfo>();
var phaseSnapshot = MergeByNumericPriority(_priorityBuckets[i], parentHandlers);
_phaseSnapshots[i] = phaseSnapshot;
merged.AddRange(phaseSnapshot);
}
_snapshot = merged.ToArray();
_dirty = false;
}
// 两侧均已按 NumericPriority 降序排列;平局时本类型 handler 在前
private static EventHandlerInfo[] MergeByNumericPriority(List<EventHandlerInfo> own,
EventHandlerInfo[] parentHandlers)
{
if (parentHandlers.Length == 0)
return own.ToArray();
if (own.Count == 0)
return parentHandlers;
var result = new EventHandlerInfo[own.Count + parentHandlers.Length];
int i = 0, j = 0, k = 0;
while (i < own.Count && j < parentHandlers.Length)
{
result[k++] = parentHandlers[j].NumericPriority > own[i].NumericPriority
? parentHandlers[j++]
: own[i++];
}
while (i < own.Count)
result[k++] = own[i++];
while (j < parentHandlers.Length)
result[k++] = parentHandlers[j++];
return result;
}
private static int BinarySearchInsertIndex(List<EventHandlerInfo> list, int numericPriority)
{
var lo = 0;
var hi = _list.Count;
var hi = list.Count;
while (lo < hi)
{
var mid = (lo + hi) >> 1;
var existing = _list[mid];
var cmp = existing.Priority.CompareTo(priority);
if (cmp == 0)
cmp = numericPriority.CompareTo(existing.NumericPriority);
if (cmp < 0)
lo = mid + 1;
else
var cmp = numericPriority.CompareTo(list[mid].NumericPriority);
if (cmp > 0)
hi = mid;
else
lo = mid + 1;
}
return lo;
}
}
}
}
+76
View File
@@ -0,0 +1,76 @@
#nullable enable
using System;
namespace ShrinkEventBus
{
public sealed class ShrinkEventBusBuilder
{
internal IShrinkEventExceptionHandler? ExceptionHandler { get; private set; }
internal ShrinkEventExceptionHandlingMode ExceptionHandlingMode { get; private set; } =
ShrinkEventExceptionHandlingMode.LogAndContinue;
internal Action<Type>? EventClassChecker { get; private set; }
internal bool StartShutdownEnabled { get; private set; }
internal bool CheckTypesOnDispatchEnabled { get; private set; }
internal bool AllowPerPhaseDispatchEnabled { get; private set; }
public ShrinkEventBusBuilder SetExceptionHandler(IShrinkEventExceptionHandler handler)
{
ExceptionHandler = handler ?? throw new ArgumentNullException(nameof(handler));
return this;
}
public ShrinkEventBusBuilder SetExceptionHandlingMode(ShrinkEventExceptionHandlingMode mode)
{
ExceptionHandlingMode = mode;
return this;
}
public ShrinkEventBusBuilder StartShutdown()
{
StartShutdownEnabled = true;
return this;
}
public ShrinkEventBusBuilder CheckTypesOnDispatch()
{
CheckTypesOnDispatchEnabled = true;
return this;
}
public ShrinkEventBusBuilder AllowPerPhaseDispatch()
{
AllowPerPhaseDispatchEnabled = true;
return this;
}
public ShrinkEventBusBuilder ClassChecker(Action<Type> checker)
{
if (checker == null)
throw new ArgumentNullException(nameof(checker));
EventClassChecker = EventClassChecker == null
? checker
: EventClassChecker + checker;
return this;
}
public ShrinkEventBusBuilder MarkerInterface<TMarker>() where TMarker : class
{
var markerType = typeof(TMarker);
if (!markerType.IsInterface)
throw new InvalidOperationException($"Marker type {markerType.FullName} must be an interface.");
return ClassChecker(eventType =>
{
if (!markerType.IsAssignableFrom(eventType))
throw new ArgumentException(
$"This bus only accepts events assignable to {markerType.FullName}, but got {eventType.FullName}.");
});
}
public IShrinkEventBus Build()
{
return new ShrinkEventBusInstance(this);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aad8b06ecb53b8a4998fd9944673e537
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+708
View File
@@ -0,0 +1,708 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace ShrinkEventBus
{
internal sealed class ShrinkEventBusInstance : IShrinkEventBus
{
private sealed class EventSubscription : IShrinkEventSubscription
{
private readonly ShrinkEventBusInstance _owner;
private bool _disposed;
public EventSubscription(ShrinkEventBusInstance owner, long subscriptionId)
{
_owner = owner ?? throw new ArgumentNullException(nameof(owner));
SubscriptionId = subscriptionId;
}
public long SubscriptionId { get; }
public bool IsDisposed => _disposed;
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
_owner.UnregisterSubscription(SubscriptionId);
}
}
private readonly Dictionary<Type, ListenerList> _eventHandlers = new();
private readonly HashSet<object> _registeredTargets = new();
private readonly object _listenerLock = new();
private readonly object _registrationLock = new();
private long _nextSubscriptionId;
private readonly IShrinkEventExceptionHandler? _exceptionHandler;
private readonly ShrinkEventExceptionHandlingMode _exceptionHandlingMode;
private readonly Action<Type>? _eventClassChecker;
private readonly bool _checkTypesOnDispatch;
private readonly bool _allowPerPhaseDispatch;
public event Action<EventBase, Type>? OnEventTriggered;
#if UNITY_EDITOR
public bool EnableDebugRecord { get; set; }
public event Action<EventBase, string, string, EventHandlerInfo[]>? OnEventTriggeredForEditor;
#endif
public bool IsStarted { get; private set; }
public ShrinkEventBusInstance(ShrinkEventBusBuilder builder)
{
if (builder == null)
throw new ArgumentNullException(nameof(builder));
_exceptionHandler = builder.ExceptionHandler;
_exceptionHandlingMode = builder.ExceptionHandlingMode;
_eventClassChecker = builder.EventClassChecker;
_checkTypesOnDispatch = builder.CheckTypesOnDispatchEnabled;
_allowPerPhaseDispatch = builder.AllowPerPhaseDispatchEnabled;
IsStarted = !builder.StartShutdownEnabled;
}
public void Start()
{
IsStarted = true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AutoRegister(object target)
{
RegisterCore(target, requireSubscriberAttribute: true, lenientWhenNoMethods: true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Register(object target)
{
RegisterCore(target, requireSubscriberAttribute: false, lenientWhenNoMethods: false);
}
public void Unregister(object target)
{
if (target == null)
return;
lock (_registrationLock)
{
_registeredTargets.Remove(target);
}
switch (target)
{
case Delegate handler:
RemoveHandlers(info => Equals(info.Handler, handler));
return;
case MethodInfo method:
RemoveHandlers(info => info.MatchesMethod(method));
return;
case Type declaringType:
RemoveHandlers(info => info.Target == null && info.MatchesDeclaringType(declaringType));
return;
default:
UnregisterAllEventsForObject(target);
return;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler, int priority)
where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
RegisterEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Async Handler (Priority: {priority})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterEvent<TEvent>(Action<TEvent> handler, EventPriority priority = EventPriority.NORMAL,
bool receiveCanceled = false) where TEvent : EventBase
{
RegisterEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Sync Handler (Priority: {priority})", handler.Method);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterEvent<TEvent>(Action<TEvent> handler, int priority) where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
RegisterEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Sync Handler (Priority: {priority})", handler.Method);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterEvent<TEvent>(Func<TEvent, UniTask> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase
{
RegisterEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Async Handler (Priority: {priority})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IShrinkEventSubscription SubscribeEvent<TEvent>(Action<TEvent> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase
{
return SubscribeEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Sync Subscription (Priority: {priority})", handler.Method);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IShrinkEventSubscription SubscribeEvent<TEvent>(Action<TEvent> handler, int priority)
where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
return SubscribeEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Sync Subscription (Priority: {priority})", handler.Method);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IShrinkEventSubscription SubscribeEvent<TEvent>(Func<TEvent, UniTask> handler,
EventPriority priority = EventPriority.NORMAL, bool receiveCanceled = false) where TEvent : EventBase
{
return SubscribeEventInternal(typeof(TEvent), handler, priority, 0, receiveCanceled,
$"Manual Async Subscription (Priority: {priority})");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public IShrinkEventSubscription SubscribeEvent<TEvent>(Func<TEvent, UniTask> handler, int priority)
where TEvent : EventBase
{
var eventPriority = PriorityHelper.ConvertToEventPriority(priority);
return SubscribeEventInternal(typeof(TEvent), handler, eventPriority, priority, false,
$"Manual Async Subscription (Priority: {priority})");
}
internal void RegisterEventInternal(Type eventType, Delegate handler, EventPriority priority,
int numericPriority, bool receiveCanceled, string debugInfo = "", MethodInfo? originalMethod = null)
{
if (eventType == null)
throw new ArgumentNullException(nameof(eventType));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
ValidateEventType(eventType, "register");
var handlerInfo = EventHandlerInfo.Create(GetNextSubscriptionId(), handler, eventType, priority,
numericPriority, receiveCanceled,
debugInfo, originalMethod);
lock (_listenerLock)
{
GetOrCreateListenerList(eventType).Add(handlerInfo);
}
}
private IShrinkEventSubscription SubscribeEventInternal(Type eventType, Delegate handler, EventPriority priority,
int numericPriority, bool receiveCanceled, string debugInfo = "", MethodInfo? originalMethod = null)
{
if (eventType == null)
throw new ArgumentNullException(nameof(eventType));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
ValidateEventType(eventType, "subscribe");
var subscriptionId = GetNextSubscriptionId();
var handlerInfo = EventHandlerInfo.Create(subscriptionId, handler, eventType, priority, numericPriority,
receiveCanceled, debugInfo, originalMethod);
lock (_listenerLock)
{
GetOrCreateListenerList(eventType).Add(handlerInfo);
}
return new EventSubscription(this, subscriptionId);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UnregisterEvent<TEvent>(Func<TEvent, UniTask> handler) where TEvent : EventBase
{
if (handler == null)
return;
var collection = TryGetListenerList(typeof(TEvent));
collection?.Remove(handler);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UnregisterEvent<TEvent>(Action<TEvent> handler) where TEvent : EventBase
{
if (handler == null)
return;
var collection = TryGetListenerList(typeof(TEvent));
collection?.Remove(handler);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ClearAllSubscribersForEvent<TEvent>() where TEvent : EventBase
{
var collection = TryGetListenerList(typeof(TEvent));
collection?.Clear();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UnregisterAllEventsForObject(object targetObject)
{
if (targetObject is null)
return;
lock (_listenerLock)
{
foreach (var kvp in _eventHandlers)
kvp.Value.RemoveTarget(targetObject);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UnregisterAllEvents()
{
lock (_listenerLock)
{
foreach (var kvp in _eventHandlers)
kvp.Value.Clear();
}
lock (_registrationLock)
{
_registeredTargets.Clear();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UniTask<bool> TriggerEventAsync<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
return TriggerEventAsyncInternal(eventArgs, null);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UniTask<bool> TriggerEventAsync<TEvent>(EventPriority phase, TEvent eventArgs) where TEvent : EventBase
{
EnsurePerPhaseDispatchAllowed();
return TriggerEventAsyncInternal(eventArgs, phase);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TriggerEvent<TEvent>(TEvent eventArgs) where TEvent : EventBase
{
return TriggerEventInternal(eventArgs, null);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TriggerEvent<TEvent>(EventPriority phase, TEvent eventArgs) where TEvent : EventBase
{
EnsurePerPhaseDispatchAllowed();
return TriggerEventInternal(eventArgs, phase);
}
private async UniTask<bool> TriggerEventAsyncInternal(EventBase eventArgs, EventPriority? phase)
{
if (!TryPrepareDispatch(eventArgs, phase, out var eventType, out var handlers))
return false;
var wasHandled = false;
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
if (!TryPrepareHandler(eventArgs, handlerInfo))
continue;
try
{
if (handlerInfo.SyncInvoker != null)
{
handlerInfo.SyncInvoker(eventArgs);
wasHandled = true;
}
else if (handlerInfo.AsyncInvoker != null)
{
await handlerInfo.AsyncInvoker(eventArgs);
wasHandled = true;
}
}
catch (Exception ex)
{
if (HandleListenerException(eventArgs, handlers, i, ex))
throw;
}
}
eventArgs.CurrentHandler = null;
CompleteEventDispatch(eventArgs, eventType, handlers);
return wasHandled;
}
private bool TriggerEventInternal(EventBase eventArgs, EventPriority? phase)
{
if (!TryPrepareDispatch(eventArgs, phase, out var eventType, out var handlers))
return false;
var wasHandled = false;
for (var i = 0; i < handlers.Length; i++)
{
var handlerInfo = handlers[i];
if (!TryPrepareHandler(eventArgs, handlerInfo))
continue;
try
{
if (handlerInfo.SyncInvoker != null)
{
handlerInfo.SyncInvoker(eventArgs);
wasHandled = true;
}
else if (handlerInfo.AsyncInvoker != null)
{
var detachedEvent = EventCloneUtility.CloneForDetachedDispatch(eventArgs);
FireAndForgetSafe(() => handlerInfo.AsyncInvoker(detachedEvent),
$"{handlerInfo.DisplayDeclaringType.Name}.{handlerInfo.DisplayMethodName}");
wasHandled = true;
}
}
catch (Exception ex)
{
if (HandleListenerException(eventArgs, handlers, i, ex))
throw;
}
}
eventArgs.CurrentHandler = null;
CompleteEventDispatch(eventArgs, eventType, handlers);
return wasHandled;
}
private bool TryPrepareDispatch(EventBase eventArgs, EventPriority? phase, out Type eventType,
out EventHandlerInfo[] handlers)
{
if (eventArgs == null)
throw new ArgumentNullException(nameof(eventArgs));
if (!IsStarted)
{
eventType = eventArgs.GetType();
handlers = Array.Empty<EventHandlerInfo>();
return false;
}
eventType = eventArgs.GetType();
ValidateDispatchEventType(eventType);
eventArgs.PrepareForDispatch();
lock (_listenerLock)
{
var listenerList = GetOrCreateListenerList(eventType);
handlers = phase.HasValue ? listenerList.GetHandlers(phase.Value) : listenerList.GetHandlers();
}
eventArgs.SetListenerSnapshot(handlers);
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryPrepareHandler(EventBase eventArgs, EventHandlerInfo handlerInfo)
{
eventArgs.CurrentHandler = handlerInfo;
eventArgs.SetPhase(handlerInfo.Priority);
if (eventArgs.IsCancelable && eventArgs.IsCanceled && !handlerInfo.ReceiveCanceled)
return false;
return true;
}
private bool HandleListenerException(EventBase eventArgs, EventHandlerInfo[] handlers, int index, Exception exception)
{
_exceptionHandler?.HandleException(this, eventArgs, handlers, index, exception);
switch (_exceptionHandlingMode)
{
case ShrinkEventExceptionHandlingMode.Throw:
return true;
case ShrinkEventExceptionHandlingMode.LogAndThrow:
LogHandlerException(exception);
return true;
default:
LogHandlerException(exception);
return false;
}
}
private static void LogHandlerException(Exception exception)
{
Debug.LogException(exception);
Debug.LogError($"[EventBus] Handler exception: {exception.Message}");
}
private static void FireAndForgetSafe(Func<UniTask> action, string handlerName)
{
action().Forget(e =>
{
Debug.LogException(e);
Debug.LogError($"[EventBus] Async Handler {handlerName} threw exception: {e.Message}");
});
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CompleteEventDispatch(EventBase eventArgs, Type eventType, EventHandlerInfo[] handlers)
{
OnEventTriggered?.Invoke(eventArgs, eventType);
#if UNITY_EDITOR
var editorTraceHandler = OnEventTriggeredForEditor;
if (!EnableDebugRecord || editorTraceHandler == null)
return;
editorTraceHandler.Invoke(eventArgs, eventType.Name, GetSenderInfo(), handlers);
#endif
}
#if UNITY_EDITOR
private static string GetSenderInfo()
{
var senderInfo = "Unknown";
try
{
var trace = new System.Diagnostics.StackTrace(0, false);
for (var i = 0; i < trace.FrameCount; i++)
{
var method = trace.GetFrame(i)?.GetMethod();
var declaringType = method?.DeclaringType;
if (declaringType == null)
continue;
if (declaringType == typeof(EventBus) || declaringType.DeclaringType == typeof(EventBus))
continue;
if (declaringType == typeof(ShrinkEventBusInstance) ||
declaringType.DeclaringType == typeof(ShrinkEventBusInstance))
continue;
var ns = declaringType.Namespace ?? string.Empty;
if (ns.StartsWith("System") || ns.StartsWith("Cysharp") || ns.StartsWith("UnityEngine"))
continue;
var className = declaringType.Name;
var methodName = method!.Name;
if (declaringType.DeclaringType != null && className.StartsWith("<") && className.Contains(">"))
{
className = declaringType.DeclaringType.Name;
var startIndex = declaringType.Name.IndexOf('<') + 1;
var endIndex = declaringType.Name.IndexOf('>');
if (startIndex > 0 && endIndex > startIndex)
methodName = declaringType.Name.Substring(startIndex, endIndex - startIndex);
}
return $"{className}.{methodName}()";
}
}
catch
{
}
return senderInfo;
}
#endif
public EventHandlerInfo[] GetEventSubscribers<TEvent>() where TEvent : EventBase
{
var listenerList = TryGetListenerList(typeof(TEvent));
var handlers = listenerList?.GetHandlers();
if (handlers == null || handlers.Length == 0)
return Array.Empty<EventHandlerInfo>();
return (EventHandlerInfo[])handlers.Clone();
}
public ListenerList GetListenerList<TEvent>() where TEvent : EventBase
{
lock (_listenerLock)
{
return GetOrCreateListenerList(typeof(TEvent));
}
}
public IReadOnlyDictionary<Type, EventHandlerInfo[]> GetAllSubscribersSnapshot()
{
lock (_listenerLock)
{
var snapshot = new Dictionary<Type, EventHandlerInfo[]>(_eventHandlers.Count);
foreach (var kvp in _eventHandlers)
{
var handlers = kvp.Value.GetHandlers();
if (handlers.Length == 0)
continue;
snapshot[kvp.Key] = (EventHandlerInfo[])handlers.Clone();
}
return snapshot;
}
}
public IReadOnlyList<ShrinkEventSubscriptionSnapshot> GetActiveSubscriptionsSnapshot()
{
lock (_listenerLock)
{
var snapshot = new List<ShrinkEventSubscriptionSnapshot>();
foreach (var entry in _eventHandlers)
{
var eventType = entry.Key;
var handlers = entry.Value.GetHandlers();
for (var i = 0; i < handlers.Length; i++)
{
var handler = handlers[i];
snapshot.Add(new ShrinkEventSubscriptionSnapshot(
handler.SubscriptionId,
eventType,
handler.Priority,
handler.NumericPriority,
handler.ReceiveCanceled,
handler.Target,
handler.DisplayDeclaringType.FullName ?? handler.DisplayDeclaringType.Name,
handler.DisplayMethodName,
handler.DebugInfo,
handler.RegisteredAtUtc));
}
}
return snapshot;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsInstanceRegistered(object target)
{
if (target == null)
return false;
lock (_registrationLock)
{
return _registeredTargets.Contains(target);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetRegisteredInstanceCount()
{
lock (_registrationLock)
{
return _registeredTargets.Count;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetRegisteredEventTypeCount()
{
lock (_listenerLock)
{
var count = 0;
foreach (var listenerList in _eventHandlers.Values)
{
if (listenerList.LocalCount > 0)
count++;
}
return count;
}
}
private void RegisterCore(object target, bool requireSubscriberAttribute, bool lenientWhenNoMethods)
{
if (target == null)
throw new ArgumentNullException(nameof(target));
lock (_registrationLock)
{
if (_registeredTargets.Contains(target))
return;
EventBusRegHelper.RegisterTarget(this, target, requireSubscriberAttribute, lenientWhenNoMethods);
_registeredTargets.Add(target);
}
}
private void RemoveHandlers(Predicate<EventHandlerInfo> predicate)
{
lock (_listenerLock)
{
foreach (var listenerList in _eventHandlers.Values)
listenerList.RemoveWhere(predicate);
}
}
private void UnregisterSubscription(long subscriptionId)
{
RemoveHandlers(info => info.SubscriptionId == subscriptionId);
}
private long GetNextSubscriptionId() => Interlocked.Increment(ref _nextSubscriptionId);
private ListenerList GetOrCreateListenerList(Type eventType)
{
if (_eventHandlers.TryGetValue(eventType, out var existing))
return existing;
ListenerList? parent = null;
var parentType = GetParentEventType(eventType);
if (parentType != null)
parent = GetOrCreateListenerList(parentType);
var created = new ListenerList(parent, _listenerLock);
_eventHandlers[eventType] = created;
return created;
}
private ListenerList? TryGetListenerList(Type eventType)
{
if (eventType == null)
return null;
lock (_listenerLock)
{
return _eventHandlers.TryGetValue(eventType, out var listenerList) ? listenerList : null;
}
}
private void ValidateEventType(Type eventType, string action)
{
if (!typeof(EventBase).IsAssignableFrom(eventType))
throw new ArgumentException(
$"Cannot {action} listener for {eventType.FullName}: type must inherit from EventBase.");
_eventClassChecker?.Invoke(eventType);
}
private void ValidateDispatchEventType(Type eventType)
{
if (!typeof(EventBase).IsAssignableFrom(eventType))
throw new ArgumentException($"Cannot dispatch event type {eventType.FullName}: not an EventBase.");
if (_checkTypesOnDispatch)
_eventClassChecker?.Invoke(eventType);
}
private static Type? GetParentEventType(Type eventType)
{
var current = eventType.BaseType;
while (current != null && current != typeof(object))
{
if (typeof(EventBase).IsAssignableFrom(current))
return current;
current = current.BaseType;
}
return null;
}
private void EnsurePerPhaseDispatchAllowed()
{
if (!_allowPerPhaseDispatch)
throw new InvalidOperationException(
"Per-phase event dispatch is disabled for this bus. Enable it via ShrinkEventBusBuilder.AllowPerPhaseDispatch().");
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 92c1c7cd96e37c8458600728c7c0c57f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: