- 将 ShrinkEventBus、ShrinkDataSaver 及其 EventBus 集成从 gitlink 转为仓库直接维护的完整 UPM 包,补齐运行时、编辑器工具、测试与文档 - 新增 Command 和 Network 的 App 集成组件,支持 ContextLoader 服务发布、可逆注销及 Network Loopback 生命周期管理 - 更新 Starter 与演示组合逻辑,缺失模块时可注册、已有兼容安装器时可覆盖,并补充宿主启动断言 - 升级内部包依赖与 Shared CodeGen 包定义,放宽 Integration.App 包的 Git 忽略规则 - 将独立服务器生成器改为基于已编译程序集的语义扫描,支持 partial、复杂泛型、命名冲突检测及模板 SHA-256 覆写保护 - 新增 Network 语义扫描、模板保护和 App 组件生命周期测试 - 新增真实 UPM 消费工程验证脚本,校验内部版本一致性、程序集加载及 EditMode 测试 - 重构当前架构文档并归档已完成的 Cordis 迁移与旧代码地图
719 lines
27 KiB
C#
719 lines
27 KiB
C#
#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;
|
|
|
|
// Keep the registration -> listener lock order used by RegisterCore.
|
|
// Removing only handlers is not enough: the target must be allowed to
|
|
// register again after a component-style teardown and reinitialization.
|
|
lock (_registrationLock)
|
|
{
|
|
_registeredTargets.Remove(targetObject);
|
|
|
|
lock (_listenerLock)
|
|
{
|
|
foreach (var kvp in _eventHandlers)
|
|
kvp.Value.RemoveTarget(targetObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void UnregisterAllEvents()
|
|
{
|
|
// Match RegisterCore/UnregisterAllEventsForObject so teardown cannot
|
|
// deadlock with a concurrent registration.
|
|
lock (_registrationLock)
|
|
{
|
|
lock (_listenerLock)
|
|
{
|
|
foreach (var kvp in _eventHandlers)
|
|
kvp.Value.Clear();
|
|
}
|
|
|
|
_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().");
|
|
}
|
|
}
|
|
}
|