com.alicizax.unity.framework/Editor/Event/EventMonitorWindow.cs

790 lines
30 KiB
C#
Raw Normal View History

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 = "Tools/AlicizaX/事件监视器";
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<Type> s_KnownEventTypes = new();
private static readonly Dictionary<Type, int> 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<Type, EventSnapshotEntry> _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<EventMonitorWindow>();
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<Type, EventDebugSummary> summaries = BuildSummaryMap();
List<EventRow> 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(
"这是仅编辑器可用的事件监视器。事件派发期间如果发生订阅、取消订阅、清空或扩容操作,会在编辑器中立即抛出异常。",
MessageType.Info);
if (_snapshotEntries.Count > 0)
{
EditorGUILayout.HelpBox(
$"快照拍摄时间:{_snapshotTimeUtc.ToLocalTime():HH:mm:ss},发生变化的事件数量:{CountChangedEvents()}。",
MessageType.None);
}
}
private void DrawSplitLayout(List<EventRow> rows, Dictionary<Type, EventDebugSummary> 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<EventRow> 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} | 峰值 {row.Summary.PeakSubscriberCount} | 容量 {capacityText} | 发布 {row.Summary.PublishCount}",
EditorStyles.miniLabel);
EditorGUILayout.LabelField(status, EditorStyles.miniLabel);
EditorGUILayout.EndVertical();
}
private void DrawDetailPanel(Dictionary<Type, EventDebugSummary> 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);
DrawAlerts(summary, subscribers);
DrawSnapshotDiff(_selectedEventType, summary, subscribers);
DrawSubscribers(subscribers);
DrawHistory();
EditorGUILayout.EndVertical();
}
private static void DrawSummary(EventDebugSummary summary)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
GUILayout.Label("摘要", EditorStyles.boldLabel);
EditorGUILayout.LabelField("当前订阅数", summary.SubscriberCount.ToString());
EditorGUILayout.LabelField("峰值订阅数", summary.PeakSubscriberCount.ToString());
EditorGUILayout.LabelField("当前容量", summary.Capacity.ToString());
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.LastOperationFrame.ToString());
EditorGUILayout.LabelField("最后操作时间", FormatTicks(summary.LastOperationTicksUtc));
EditorGUILayout.EndVertical();
}
private static void DrawAlerts(EventDebugSummary summary, EventDebugSubscriberInfo[] subscribers)
{
List<EventAlert> 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;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("订阅数变化", FormatSigned(subscriberDelta));
EditorGUILayout.LabelField("发布次数变化", FormatSigned(publishDelta));
EditorGUILayout.LabelField("订阅次数变化", FormatSigned(subscribeDelta));
EditorGUILayout.LabelField("取消订阅次数变化", FormatSigned(unsubscribeDelta));
EditorGUILayout.LabelField("扩容次数变化", FormatSigned(resizeDelta));
EditorGUILayout.LabelField("容量变化", FormatSigned(capacityDelta));
List<string> addedSubscribers = GetSubscriberDiff(currentSubscribers, snapshot.Subscribers);
List<string> 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.PublishCount != snapshot.Summary.PublishCount ||
summary.SubscribeCount != snapshot.Summary.SubscribeCount ||
summary.UnsubscribeCount != snapshot.Summary.UnsubscribeCount ||
summary.ResizeCount != snapshot.Summary.ResizeCount ||
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 ? "静态方法" : "实例方法");
if (subscriber.IsUnityObjectDestroyed)
{
EditorGUILayout.HelpBox("Unity 目标对象已经被销毁,但委托仍然存在。", MessageType.Warning);
}
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<Type, EventDebugSummary> BuildSummaryMap()
{
EventDebugSummary[] summaries = EventDebugRegistry.GetSummaries();
Dictionary<Type, EventDebugSummary> map = new(summaries.Length);
for (int i = 0; i < summaries.Length; i++)
{
map[summaries[i].EventType] = summaries[i];
}
return map;
}
private List<EventRow> BuildRows(Dictionary<Type, EventDebugSummary> summaries)
{
List<EventRow> 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);
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<EventRow> 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<IEventArgs>())
{
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 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<EventAlert> BuildAlerts(EventDebugSummary summary, EventDebugSubscriberInfo[] subscribers)
{
List<EventAlert> alerts = new();
int destroyedTargetCount = 0;
for (int i = 0; i < subscribers.Length; i++)
{
if (subscribers[i].IsUnityObjectDestroyed)
{
destroyedTargetCount++;
}
}
if (destroyedTargetCount > 0)
{
alerts.Add(new EventAlert(MessageType.Warning, $"发现 {destroyedTargetCount} 个订阅者的 Unity 目标对象已经被销毁。"));
}
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 可能偏大。"));
}
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<string> GetSubscriberDiff(EventDebugSubscriberInfo[] source, EventDebugSubscriberInfo[] baseline)
{
Dictionary<string, int> counts = BuildSubscriberCounts(source);
Dictionary<string, int> baselineCounts = BuildSubscriberCounts(baseline);
List<string> result = new();
foreach (KeyValuePair<string, int> 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<string, int> BuildSubscriberCounts(EventDebugSubscriberInfo[] subscribers)
{
Dictionary<string, int> 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]");
}
return builder.ToString();
}
private static string GetOperationKindText(EventDebugOperationKind kind)
{
return kind switch
{
EventDebugOperationKind.Subscribe => "订阅",
EventDebugOperationKind.Unsubscribe => "取消订阅",
EventDebugOperationKind.Publish => "发布",
EventDebugOperationKind.Resize => "扩容",
EventDebugOperationKind.Clear => "清空",
_ => kind.ToString()
};
}
}
}