feat(packages): 内置 SDK 包并完善 ContextLoader 集成
- 将 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 迁移与旧代码地图
This commit is contained in:
@@ -0,0 +1,718 @@
|
||||
#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().");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user