using System; using System.Collections.Generic; using System.Reflection; using System.Text; using UnityEditor; using UnityEngine; namespace AlicizaX.Editor { internal sealed class EventMonitorWindow : EditorWindow { private const string MenuPath = "AlicizaX/Event 监视器"; private const double RepaintIntervalSeconds = 0.25d; private const float DefaultLeftPanelWidth = 360f; private const float MinLeftPanelWidth = 260f; private const float MinRightPanelWidth = 360f; private const float SplitterWidth = 5f; private static readonly List s_KnownEventTypes = new(); private static readonly Dictionary s_InitialCapacityCache = new(); private Vector2 _eventListScroll; private Vector2 _subscriberScroll; private Vector2 _historyScroll; private string _searchText = string.Empty; private bool _onlyInitialized; private bool _autoRefresh = true; private Type _selectedEventType; private double _lastRepaintTime; private float _leftPanelWidth = DefaultLeftPanelWidth; private bool _isDraggingSplitter; private DateTime _snapshotTimeUtc; private readonly Dictionary _snapshotEntries = new(); private readonly struct EventRow { internal readonly Type EventType; internal readonly bool Initialized; internal readonly EventDebugSummary Summary; internal readonly int InitialCapacity; internal EventRow(Type eventType, bool initialized, EventDebugSummary summary, int initialCapacity) { EventType = eventType; Initialized = initialized; Summary = summary; InitialCapacity = initialCapacity; } } private readonly struct EventSnapshotEntry { internal readonly EventDebugSummary Summary; internal readonly EventDebugSubscriberInfo[] Subscribers; internal EventSnapshotEntry(EventDebugSummary summary, EventDebugSubscriberInfo[] subscribers) { Summary = summary; Subscribers = subscribers; } } private readonly struct EventAlert { internal readonly MessageType Type; internal readonly string Message; internal EventAlert(MessageType type, string message) { Type = type; Message = message; } } [MenuItem(MenuPath, priority = 310)] private static void Open() { EventMonitorWindow window = GetWindow(); window.titleContent = new GUIContent("事件监视器"); window.minSize = new Vector2(1080f, 640f); window.Show(); } private void OnEnable() { RefreshKnownEventTypes(); EditorApplication.update += HandleEditorUpdate; } private void OnDisable() { EditorApplication.update -= HandleEditorUpdate; } private void OnFocus() { RefreshKnownEventTypes(); Repaint(); } private void HandleEditorUpdate() { if (!_autoRefresh) { return; } double time = EditorApplication.timeSinceStartup; if (time - _lastRepaintTime < RepaintIntervalSeconds) { return; } _lastRepaintTime = time; Repaint(); } private void OnGUI() { DrawToolbar(); Dictionary summaries = BuildSummaryMap(); List rows = BuildRows(summaries); EnsureSelection(rows); DrawSplitLayout(rows, summaries); } private void DrawToolbar() { EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); GUILayout.Label("搜索", GUILayout.Width(44f)); _searchText = GUILayout.TextField( _searchText, GUI.skin.FindStyle("ToolbarSearchTextField") ?? GUI.skin.FindStyle("ToolbarSeachTextField") ?? EditorStyles.toolbarTextField, GUILayout.MinWidth(220f), GUILayout.ExpandWidth(true)); _onlyInitialized = GUILayout.Toggle(_onlyInitialized, "仅已初始化", EditorStyles.toolbarButton, GUILayout.Width(110f)); _autoRefresh = GUILayout.Toggle(_autoRefresh, "自动刷新", EditorStyles.toolbarButton, GUILayout.Width(92f)); if (GUILayout.Button("刷新", EditorStyles.toolbarButton, GUILayout.Width(70f))) { RefreshKnownEventTypes(); Repaint(); } if (GUILayout.Button("重置统计", EditorStyles.toolbarButton, GUILayout.Width(84f))) { EventDebugRegistry.ResetStats(); Repaint(); } if (GUILayout.Button("拍摄快照", EditorStyles.toolbarButton, GUILayout.Width(108f))) { CaptureSnapshot(); Repaint(); } using (new EditorGUI.DisabledScope(_snapshotEntries.Count == 0)) { if (GUILayout.Button("清空快照", EditorStyles.toolbarButton, GUILayout.Width(96f))) { _snapshotEntries.Clear(); _snapshotTimeUtc = default; Repaint(); } } EditorGUILayout.EndHorizontal(); EditorGUILayout.HelpBox( "这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会抛出 InvalidOperationException,Player 中也同样生效。", MessageType.Info); if (_snapshotEntries.Count > 0) { EditorGUILayout.HelpBox( $"快照拍摄时间:{_snapshotTimeUtc.ToLocalTime():HH:mm:ss},发生变化的事件数量:{CountChangedEvents()}。", MessageType.None); } } private void DrawSplitLayout(List rows, Dictionary summaries) { float viewWidth = EditorGUIUtility.currentViewWidth; float maxLeftWidth = Mathf.Max(MinLeftPanelWidth, viewWidth - MinRightPanelWidth - SplitterWidth - 24f); _leftPanelWidth = Mathf.Clamp(_leftPanelWidth, MinLeftPanelWidth, maxLeftWidth); EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); EditorGUILayout.BeginVertical(GUILayout.Width(_leftPanelWidth), GUILayout.ExpandHeight(true)); DrawEventList(rows); EditorGUILayout.EndVertical(); Rect splitterRect = GUILayoutUtility.GetRect( SplitterWidth, SplitterWidth, GUILayout.Width(SplitterWidth), GUILayout.ExpandHeight(true)); GUI.Box(splitterRect, GUIContent.none, EditorStyles.helpBox); EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeHorizontal); UpdateSplitter(splitterRect, maxLeftWidth); EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); DrawDetailPanel(summaries); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); } private void UpdateSplitter(Rect splitterRect, float maxLeftWidth) { Event currentEvent = Event.current; switch (currentEvent.type) { case EventType.MouseDown: if (currentEvent.button == 0 && splitterRect.Contains(currentEvent.mousePosition)) { _isDraggingSplitter = true; currentEvent.Use(); } break; case EventType.MouseDrag: if (_isDraggingSplitter) { _leftPanelWidth = Mathf.Clamp(currentEvent.mousePosition.x, MinLeftPanelWidth, maxLeftWidth); Repaint(); currentEvent.Use(); } break; case EventType.MouseUp: if (_isDraggingSplitter) { _isDraggingSplitter = false; currentEvent.Use(); } break; } } private void DrawEventList(List rows) { EditorGUILayout.BeginVertical(); GUILayout.Label($"事件列表({rows.Count})", EditorStyles.boldLabel); _eventListScroll = EditorGUILayout.BeginScrollView(_eventListScroll); for (int i = 0; i < rows.Count; i++) { DrawEventRow(rows[i]); } EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); } private void DrawEventRow(EventRow row) { bool selected = _selectedEventType == row.EventType; EditorGUILayout.BeginVertical(EditorStyles.helpBox); GUIStyle buttonStyle = selected ? EditorStyles.miniButtonMid : EditorStyles.miniButton; if (GUILayout.Button(row.EventType.FullName ?? row.EventType.Name, buttonStyle)) { _selectedEventType = row.EventType; } string capacityText = row.Initialized ? row.Summary.Capacity.ToString() : row.InitialCapacity.ToString(); string status = row.Initialized ? "已初始化" : "未初始化"; EditorGUILayout.LabelField( $"订阅 {row.Summary.SubscriberCount} | in {row.Summary.InSubscriberCount} / 值 {row.Summary.ValueSubscriberCount} | 容量 {capacityText} | 发布 {row.Summary.PublishCount}", EditorStyles.miniLabel); EditorGUILayout.LabelField($"{status} | 初始容量 {row.InitialCapacity}", EditorStyles.miniLabel); EditorGUILayout.EndVertical(); } private void DrawDetailPanel(Dictionary summaries) { EditorGUILayout.BeginVertical(); if (_selectedEventType == null) { EditorGUILayout.HelpBox("请先从左侧选择一个事件类型。", MessageType.Info); EditorGUILayout.EndVertical(); return; } GUILayout.Label(_selectedEventType.FullName ?? _selectedEventType.Name, EditorStyles.boldLabel); if (!summaries.TryGetValue(_selectedEventType, out EventDebugSummary summary)) { int initialCapacity = GetInitialCapacity(_selectedEventType); EditorGUILayout.HelpBox("这个事件类型在当前域中还没有被初始化。", MessageType.Info); EditorGUILayout.LabelField("初始容量", initialCapacity.ToString()); EditorGUILayout.LabelField("当前订阅数", "0"); EditorGUILayout.LabelField("发布次数", "0"); EditorGUILayout.EndVertical(); return; } EventDebugRegistry.TryGetDetails(_selectedEventType, out _, out EventDebugSubscriberInfo[] subscribers); DrawSummary(summary, GetInitialCapacity(_selectedEventType)); DrawAlerts(summary, subscribers); DrawSnapshotDiff(_selectedEventType, summary, subscribers); DrawSubscribers(subscribers); DrawHistory(); EditorGUILayout.EndVertical(); } private static void DrawSummary(EventDebugSummary summary, int initialCapacity) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); GUILayout.Label("摘要", EditorStyles.boldLabel); EditorGUILayout.LabelField("初始容量", initialCapacity.ToString()); EditorGUILayout.LabelField("当前订阅数", summary.SubscriberCount.ToString()); EditorGUILayout.LabelField("当前派发模式", $"in {summary.InSubscriberCount} | 值 {summary.ValueSubscriberCount}"); EditorGUILayout.LabelField("峰值订阅数", summary.PeakSubscriberCount.ToString()); EditorGUILayout.LabelField("当前容量", summary.Capacity.ToString()); EditorGUILayout.LabelField("容量利用率", FormatRatio(summary.SubscriberCount, summary.Capacity)); EditorGUILayout.LabelField("发布次数", summary.PublishCount.ToString()); EditorGUILayout.LabelField("订阅次数", summary.SubscribeCount.ToString()); EditorGUILayout.LabelField("取消订阅次数", summary.UnsubscribeCount.ToString()); EditorGUILayout.LabelField("扩容次数", summary.ResizeCount.ToString()); EditorGUILayout.LabelField("清空次数", summary.ClearCount.ToString()); EditorGUILayout.LabelField("发布期非法变更", summary.MutationRejectedCount.ToString()); EditorGUILayout.LabelField("最后操作帧", summary.LastOperationFrame.ToString()); EditorGUILayout.LabelField("最后操作时间", FormatTicks(summary.LastOperationTicksUtc)); EditorGUILayout.EndVertical(); } private static void DrawAlerts(EventDebugSummary summary, EventDebugSubscriberInfo[] subscribers) { List alerts = BuildAlerts(summary, subscribers); if (alerts.Count == 0) { return; } EditorGUILayout.Space(4f); GUILayout.Label("告警", EditorStyles.boldLabel); for (int i = 0; i < alerts.Count; i++) { EditorGUILayout.HelpBox(alerts[i].Message, alerts[i].Type); } } private void DrawSnapshotDiff(Type eventType, EventDebugSummary currentSummary, EventDebugSubscriberInfo[] currentSubscribers) { if (_snapshotEntries.Count == 0) { return; } EditorGUILayout.Space(4f); GUILayout.Label("快照对比", EditorStyles.boldLabel); if (!_snapshotEntries.TryGetValue(eventType, out EventSnapshotEntry snapshot)) { EditorGUILayout.HelpBox("这个事件在拍摄快照时还不存在。", MessageType.Info); return; } int subscriberDelta = currentSummary.SubscriberCount - snapshot.Summary.SubscriberCount; long publishDelta = currentSummary.PublishCount - snapshot.Summary.PublishCount; long subscribeDelta = currentSummary.SubscribeCount - snapshot.Summary.SubscribeCount; long unsubscribeDelta = currentSummary.UnsubscribeCount - snapshot.Summary.UnsubscribeCount; int resizeDelta = currentSummary.ResizeCount - snapshot.Summary.ResizeCount; int capacityDelta = currentSummary.Capacity - snapshot.Summary.Capacity; int valueSubscriberDelta = currentSummary.ValueSubscriberCount - snapshot.Summary.ValueSubscriberCount; int inSubscriberDelta = currentSummary.InSubscriberCount - snapshot.Summary.InSubscriberCount; long mutationRejectedDelta = currentSummary.MutationRejectedCount - snapshot.Summary.MutationRejectedCount; EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("订阅数变化", FormatSigned(subscriberDelta)); EditorGUILayout.LabelField("in 订阅变化", FormatSigned(inSubscriberDelta)); EditorGUILayout.LabelField("值传参订阅变化", FormatSigned(valueSubscriberDelta)); EditorGUILayout.LabelField("发布次数变化", FormatSigned(publishDelta)); EditorGUILayout.LabelField("订阅次数变化", FormatSigned(subscribeDelta)); EditorGUILayout.LabelField("取消订阅次数变化", FormatSigned(unsubscribeDelta)); EditorGUILayout.LabelField("扩容次数变化", FormatSigned(resizeDelta)); EditorGUILayout.LabelField("容量变化", FormatSigned(capacityDelta)); EditorGUILayout.LabelField("非法变更变化", FormatSigned(mutationRejectedDelta)); List addedSubscribers = GetSubscriberDiff(currentSubscribers, snapshot.Subscribers); List removedSubscribers = GetSubscriberDiff(snapshot.Subscribers, currentSubscribers); if (addedSubscribers.Count == 0 && removedSubscribers.Count == 0) { EditorGUILayout.LabelField("订阅者集合", "无变化"); } else { if (addedSubscribers.Count > 0) { EditorGUILayout.LabelField("新增", string.Join(" | ", addedSubscribers)); } if (removedSubscribers.Count > 0) { EditorGUILayout.LabelField("移除", string.Join(" | ", removedSubscribers)); } } EditorGUILayout.EndVertical(); } private void DrawSubscribers(EventDebugSubscriberInfo[] subscribers) { EditorGUILayout.Space(4f); GUILayout.Label("订阅者", EditorStyles.boldLabel); if (subscribers.Length == 0) { EditorGUILayout.HelpBox("当前没有活跃订阅者。", MessageType.None); return; } _subscriberScroll = EditorGUILayout.BeginScrollView(_subscriberScroll, GUILayout.Height(position.height * 0.45f)); for (int i = 0; i < subscribers.Length; i++) { DrawSubscriberRow(subscribers[i]); } EditorGUILayout.EndScrollView(); } private void CaptureSnapshot() { _snapshotEntries.Clear(); _snapshotTimeUtc = DateTime.UtcNow; EventDebugSummary[] summaries = EventDebugRegistry.GetSummaries(); for (int i = 0; i < summaries.Length; i++) { EventDebugSummary summary = summaries[i]; EventDebugRegistry.TryGetDetails(summary.EventType, out _, out EventDebugSubscriberInfo[] subscribers); _snapshotEntries[summary.EventType] = new EventSnapshotEntry(summary, subscribers); } } private int CountChangedEvents() { int changedCount = 0; EventDebugSummary[] summaries = EventDebugRegistry.GetSummaries(); for (int i = 0; i < summaries.Length; i++) { EventDebugSummary summary = summaries[i]; if (!_snapshotEntries.TryGetValue(summary.EventType, out EventSnapshotEntry snapshot)) { changedCount++; continue; } if (summary.SubscriberCount != snapshot.Summary.SubscriberCount || summary.ValueSubscriberCount != snapshot.Summary.ValueSubscriberCount || summary.InSubscriberCount != snapshot.Summary.InSubscriberCount || summary.PublishCount != snapshot.Summary.PublishCount || summary.SubscribeCount != snapshot.Summary.SubscribeCount || summary.UnsubscribeCount != snapshot.Summary.UnsubscribeCount || summary.ResizeCount != snapshot.Summary.ResizeCount || summary.MutationRejectedCount != snapshot.Summary.MutationRejectedCount || summary.Capacity != snapshot.Summary.Capacity) { changedCount++; } } return changedCount; } private static void DrawSubscriberRow(EventDebugSubscriberInfo subscriber) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField($"{subscriber.DeclaringTypeName}.{subscriber.MethodName}", EditorStyles.boldLabel); EditorGUILayout.LabelField("槽位", subscriber.HandlerIndex.ToString()); EditorGUILayout.LabelField("版本", subscriber.Version.ToString()); EditorGUILayout.LabelField("目标", subscriber.TargetTypeName); EditorGUILayout.LabelField("类型", subscriber.IsStatic ? "静态方法" : "实例方法"); EditorGUILayout.LabelField("派发方式", subscriber.UsesInParameter ? "in" : "按值复制"); if (subscriber.IsUnityObjectDestroyed) { EditorGUILayout.HelpBox("Unity 目标对象已经被销毁,但委托仍然存在。", MessageType.Warning); } if (subscriber.IsCompilerGeneratedTarget || subscriber.IsCompilerGeneratedMethod) { EditorGUILayout.HelpBox("该订阅者看起来来自 lambda 或闭包,可能带来额外分配或生命周期问题。", MessageType.Info); } if (subscriber.UnityTarget != null && !subscriber.IsUnityObjectDestroyed) { if (GUILayout.Button("定位目标", GUILayout.Width(90f))) { EditorGUIUtility.PingObject(subscriber.UnityTarget); } } EditorGUILayout.EndVertical(); } private void DrawHistory() { EditorGUILayout.Space(4f); GUILayout.Label("最近操作", EditorStyles.boldLabel); EventDebugOperationRecord[] history = EventDebugRegistry.GetRecentOperations(); if (history.Length == 0) { EditorGUILayout.HelpBox("当前域里还没有记录到任何操作。", MessageType.None); return; } _historyScroll = EditorGUILayout.BeginScrollView(_historyScroll); for (int i = 0; i < history.Length; i++) { EventDebugOperationRecord record = history[i]; EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); GUILayout.Label(GetOperationKindText(record.OperationKind), GUILayout.Width(84f)); GUILayout.Label(record.EventType.FullName ?? record.EventType.Name); GUILayout.FlexibleSpace(); GUILayout.Label($"订阅 {record.SubscriberCount}", GUILayout.Width(72f)); GUILayout.Label($"容量 {record.Capacity}", GUILayout.Width(68f)); GUILayout.Label(FormatTicks(record.TicksUtc), GUILayout.Width(92f)); EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } private static Dictionary BuildSummaryMap() { EventDebugSummary[] summaries = EventDebugRegistry.GetSummaries(); Dictionary map = new(summaries.Length); for (int i = 0; i < summaries.Length; i++) { map[summaries[i].EventType] = summaries[i]; } return map; } private List BuildRows(Dictionary summaries) { List rows = new(s_KnownEventTypes.Count); for (int i = 0; i < s_KnownEventTypes.Count; i++) { Type eventType = s_KnownEventTypes[i]; bool initialized = summaries.TryGetValue(eventType, out EventDebugSummary summary); EventDebugSummary rowSummary = initialized ? summary : new EventDebugSummary(eventType, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); EventRow row = new EventRow(eventType, initialized, rowSummary, GetInitialCapacity(eventType)); if (MatchesFilter(row)) { rows.Add(row); } } rows.Sort(CompareRows); return rows; } private bool MatchesFilter(EventRow row) { if (_onlyInitialized && !row.Initialized) { return false; } if (string.IsNullOrEmpty(_searchText)) { return true; } string fullName = row.EventType.FullName ?? row.EventType.Name; return fullName.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0; } private void EnsureSelection(List rows) { if (_selectedEventType != null) { for (int i = 0; i < rows.Count; i++) { if (rows[i].EventType == _selectedEventType) { return; } } } if (rows.Count > 0) { _selectedEventType = rows[0].EventType; } else { _selectedEventType = null; } } private static int CompareRows(EventRow x, EventRow y) { int initializedCompare = y.Initialized.CompareTo(x.Initialized); if (initializedCompare != 0) { return initializedCompare; } int activeCompare = y.Summary.SubscriberCount.CompareTo(x.Summary.SubscriberCount); if (activeCompare != 0) { return activeCompare; } int publishCompare = y.Summary.PublishCount.CompareTo(x.Summary.PublishCount); if (publishCompare != 0) { return publishCompare; } return string.CompareOrdinal(x.EventType.FullName, y.EventType.FullName); } private static void RefreshKnownEventTypes() { s_KnownEventTypes.Clear(); s_InitialCapacityCache.Clear(); foreach (Type eventType in TypeCache.GetTypesDerivedFrom()) { if (!eventType.IsValueType || eventType.IsAbstract) { continue; } s_KnownEventTypes.Add(eventType); } s_KnownEventTypes.Sort((x, y) => string.CompareOrdinal(x.FullName, y.FullName)); } private static int GetInitialCapacity(Type eventType) { if (s_InitialCapacityCache.TryGetValue(eventType, out int cachedCapacity)) { return cachedCapacity; } try { Type initialSizeType = typeof(EventInitialSize<>).MakeGenericType(eventType); FieldInfo sizeField = initialSizeType.GetField("Size", BindingFlags.Public | BindingFlags.Static); if (sizeField != null && sizeField.GetValue(null) is int size) { s_InitialCapacityCache[eventType] = size; return size; } } catch { } s_InitialCapacityCache[eventType] = 0; return 0; } private static string FormatTicks(long ticksUtc) { if (ticksUtc <= 0) { return "-"; } return new DateTime(ticksUtc, DateTimeKind.Utc).ToLocalTime().ToString("HH:mm:ss"); } private static string FormatRatio(int value, int capacity) { if (capacity <= 0) { return "-"; } float percent = value / (float)capacity * 100f; return $"{percent:F1}% ({value}/{capacity})"; } private static string FormatSigned(int value) { if (value > 0) { return $"+{value}"; } return value.ToString(); } private static string FormatSigned(long value) { if (value > 0) { return $"+{value}"; } return value.ToString(); } private static List BuildAlerts(EventDebugSummary summary, EventDebugSubscriberInfo[] subscribers) { List alerts = new(); int destroyedTargetCount = 0; int compilerGeneratedCount = 0; for (int i = 0; i < subscribers.Length; i++) { if (subscribers[i].IsUnityObjectDestroyed) { destroyedTargetCount++; } if (subscribers[i].IsCompilerGeneratedMethod || subscribers[i].IsCompilerGeneratedTarget) { compilerGeneratedCount++; } } if (destroyedTargetCount > 0) { alerts.Add(new EventAlert(MessageType.Warning, $"发现 {destroyedTargetCount} 个订阅者的 Unity 目标对象已经被销毁。")); } if (summary.MutationRejectedCount > 0) { alerts.Add(new EventAlert(MessageType.Warning, $"派发期间发生了 {summary.MutationRejectedCount} 次非法变更尝试,运行时会直接抛异常。")); } if (summary.ResizeCount > 0) { alerts.Add(new EventAlert(MessageType.Warning, $"容器已经扩容 {summary.ResizeCount} 次,当前事件的 Prewarm 可能偏小。")); } if (summary.PeakSubscriberCount > 0 && summary.Capacity >= summary.PeakSubscriberCount * 4) { alerts.Add(new EventAlert(MessageType.Info, $"当前容量 {summary.Capacity} 远大于峰值订阅数 {summary.PeakSubscriberCount},Prewarm 可能偏大。")); } if (summary.ValueSubscriberCount > 0) { alerts.Add(new EventAlert(MessageType.Info, $"当前仍有 {summary.ValueSubscriberCount} 个订阅者使用按值传参;事件体较大时建议改成 in 处理器。")); } if (compilerGeneratedCount > 0) { alerts.Add(new EventAlert(MessageType.Info, $"检测到 {compilerGeneratedCount} 个编译器生成的订阅者,通常意味着 lambda 或闭包。")); } long churn = summary.SubscribeCount + summary.UnsubscribeCount; if (churn > 0 && summary.PublishCount == 0) { alerts.Add(new EventAlert(MessageType.Info, $"这个事件在当前域中发生了 {churn} 次订阅/取消订阅操作,但从未被发布。")); } else if (summary.PublishCount > 0 && churn > summary.PublishCount * 4) { alerts.Add(new EventAlert(MessageType.Info, $"检测到较高的订阅抖动:订阅变更 {churn} 次,发布次数 {summary.PublishCount} 次。")); } if (summary.SubscriberCount > 0 && summary.PublishCount == 0 && summary.LastOperationFrame > 0) { alerts.Add(new EventAlert(MessageType.Info, "这个事件当前已有订阅者,但在当前域中还没有被发布过。")); } return alerts; } private static List GetSubscriberDiff(EventDebugSubscriberInfo[] source, EventDebugSubscriberInfo[] baseline) { Dictionary counts = BuildSubscriberCounts(source); Dictionary baselineCounts = BuildSubscriberCounts(baseline); List result = new(); foreach (KeyValuePair pair in counts) { baselineCounts.TryGetValue(pair.Key, out int baselineCount); int delta = pair.Value - baselineCount; if (delta <= 0) { continue; } result.Add(delta == 1 ? pair.Key : $"{pair.Key} x{delta}"); } return result; } private static Dictionary BuildSubscriberCounts(EventDebugSubscriberInfo[] subscribers) { Dictionary counts = new(); for (int i = 0; i < subscribers.Length; i++) { string key = BuildSubscriberKey(subscribers[i]); counts.TryGetValue(key, out int count); counts[key] = count + 1; } return counts; } private static string BuildSubscriberKey(EventDebugSubscriberInfo subscriber) { StringBuilder builder = new(); builder.Append(subscriber.DeclaringTypeName); builder.Append('.'); builder.Append(subscriber.MethodName); builder.Append(" -> "); builder.Append(subscriber.TargetTypeName); if (subscriber.IsStatic) { builder.Append(" [static]"); } builder.Append(subscriber.UsesInParameter ? " [in]" : " [value]"); return builder.ToString(); } private static string GetOperationKindText(EventDebugOperationKind kind) { return kind switch { EventDebugOperationKind.Subscribe => "订阅", EventDebugOperationKind.Unsubscribe => "取消订阅", EventDebugOperationKind.Publish => "发布", EventDebugOperationKind.Resize => "扩容", EventDebugOperationKind.Clear => "清空", EventDebugOperationKind.MutationRejected => "非法变更", _ => kind.ToString() }; } } }