增加Event调试编辑器

1.增加Event调试编辑器查看订阅信息
2.调试编辑器增加快照查看泄露风险数据
This commit is contained in:
b4lie 2026-03-21 21:19:07 +08:00
parent 0c0e2243d4
commit 93aba55cb0
7 changed files with 1247 additions and 17 deletions

8
Editor/Event.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c2a7d2e65d45494cbf9c71c37614d89f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,789 @@
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()
};
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9f62c982b4564f0b89a347090eec406c
timeCreated: 1774080001

View File

@ -24,6 +24,10 @@ namespace AlicizaX
private static int _activeCount;
private static int _version;
#if UNITY_EDITOR
private static int _publishDepth;
#endif
#if Event_StrictCheck
private static System.Collections.Generic.HashSet<Action<TPayload>> _activeHandlers;
#endif
@ -47,11 +51,19 @@ namespace AlicizaX
#if Event_StrictCheck
_activeHandlers = new System.Collections.Generic.HashSet<Action<TPayload>>();
#endif
#if UNITY_EDITOR
EventDebugRegistry.RegisterContainer<TPayload>(GetDebugSubscriberCount, GetDebugCapacity, GetDebugSubscribers);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EventRuntimeHandle Subscribe(Action<TPayload> callback)
{
#if UNITY_EDITOR
ThrowIfMutatingDuringPublish("subscribe");
#endif
#if Event_StrictCheck
if (_activeHandlers.Contains(callback))
{
@ -77,6 +89,10 @@ namespace AlicizaX
_versions[handlerIndex] = version;
_activeSlots[handlerIndex] = activeIndex;
#if UNITY_EDITOR
EventDebugRegistry.RecordSubscribe<TPayload>(_activeCount, _callbacks.Length);
#endif
return new EventRuntimeHandle(TypeId, handlerIndex, version);
}
@ -101,12 +117,19 @@ namespace AlicizaX
_freeSlots[_freeCount++] = i;
}
#if UNITY_EDITOR
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length);
#endif
return _freeSlots[--_freeCount];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Unsubscribe(int handlerIndex, int version)
{
#if UNITY_EDITOR
ThrowIfMutatingDuringPublish("unsubscribe");
#endif
if (_versions[handlerIndex] != version) return;
@ -131,13 +154,25 @@ namespace AlicizaX
}
_freeSlots[_freeCount++] = handlerIndex;
#if UNITY_EDITOR
EventDebugRegistry.RecordUnsubscribe<TPayload>(_activeCount, _callbacks.Length);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Publish(in TPayload payload)
{
#if UNITY_EDITOR
_publishDepth++;
try
{
#endif
int count = _activeCount;
#if UNITY_EDITOR
EventDebugRegistry.RecordPublish<TPayload>(count, _callbacks.Length);
#endif
if (count == 0) return;
int[] indices = _activeIndices;
@ -162,13 +197,25 @@ namespace AlicizaX
switch (count - i)
{
case 3:
callbacks[indices[i + 2]](payload);
goto case 2;
case 2:
callbacks[indices[i]](payload);
callbacks[indices[i + 1]](payload);
goto case 1;
case 1: callbacks[indices[i]](payload); break;
callbacks[indices[i + 2]](payload);
break;
case 2:
callbacks[indices[i]](payload);
callbacks[indices[i + 1]](payload);
break;
case 1:
callbacks[indices[i]](payload);
break;
}
#if UNITY_EDITOR
}
finally
{
_publishDepth--;
}
#endif
}
public static int SubscriberCount
@ -179,6 +226,10 @@ namespace AlicizaX
public static void EnsureCapacity(int capacity)
{
#if UNITY_EDITOR
ThrowIfMutatingDuringPublish("ensure capacity");
#endif
if (_callbacks.Length >= capacity) return;
int oldLen = _callbacks.Length;
@ -192,10 +243,18 @@ namespace AlicizaX
{
_freeSlots[_freeCount++] = i;
}
#if UNITY_EDITOR
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length);
#endif
}
public static void Clear()
{
#if UNITY_EDITOR
ThrowIfMutatingDuringPublish("clear");
#endif
for (int i = 0; i < _activeCount; i++)
{
int idx = _activeIndices[i];
@ -206,9 +265,69 @@ namespace AlicizaX
_activeCount = 0;
#if UNITY_EDITOR
EventDebugRegistry.RecordClear<TPayload>(_activeCount, _callbacks.Length);
#endif
#if Event_StrictCheck
_activeHandlers.Clear();
#endif
}
#if UNITY_EDITOR
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ThrowIfMutatingDuringPublish(string operation)
{
if (_publishDepth <= 0) return;
throw new InvalidOperationException(
$"EventContainer<{typeof(TPayload).Name}> cannot {operation} while publishing. " +
"Supporting dispatch-time mutations would require slower publish semantics.");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetDebugSubscriberCount() => _activeCount;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetDebugCapacity() => _callbacks.Length;
private static EventDebugSubscriberInfo[] GetDebugSubscribers()
{
int count = _activeCount;
if (count == 0)
{
return Array.Empty<EventDebugSubscriberInfo>();
}
EventDebugSubscriberInfo[] subscribers = new EventDebugSubscriberInfo[count];
for (int i = 0; i < count; i++)
{
int handlerIndex = _activeIndices[i];
Action<TPayload> callback = _callbacks[handlerIndex];
object target = callback.Target;
bool isStatic = target == null;
bool isUnityObjectDestroyed = false;
UnityEngine.Object unityTarget = null;
if (!isStatic && target is UnityEngine.Object engineObject)
{
unityTarget = engineObject;
isUnityObjectDestroyed = engineObject == null;
}
subscribers[i] = new EventDebugSubscriberInfo(
handlerIndex,
_versions[handlerIndex],
callback.Method.DeclaringType?.FullName ?? "<UnknownType>",
callback.Method.Name,
target?.GetType().FullName ?? "<Static>",
unityTarget,
isStatic,
isUnityObjectDestroyed);
}
return subscribers;
}
#endif
}
}

View File

@ -0,0 +1,319 @@
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AlicizaX
{
internal enum EventDebugOperationKind : byte
{
Subscribe,
Unsubscribe,
Publish,
Resize,
Clear
}
internal readonly struct EventDebugSummary
{
internal readonly Type EventType;
internal readonly bool Initialized;
internal readonly int SubscriberCount;
internal readonly int PeakSubscriberCount;
internal readonly int Capacity;
internal readonly long PublishCount;
internal readonly long SubscribeCount;
internal readonly long UnsubscribeCount;
internal readonly int ResizeCount;
internal readonly int ClearCount;
internal readonly int LastOperationFrame;
internal readonly long LastOperationTicksUtc;
internal EventDebugSummary(
Type eventType,
bool initialized,
int subscriberCount,
int peakSubscriberCount,
int capacity,
long publishCount,
long subscribeCount,
long unsubscribeCount,
int resizeCount,
int clearCount,
int lastOperationFrame,
long lastOperationTicksUtc)
{
EventType = eventType;
Initialized = initialized;
SubscriberCount = subscriberCount;
PeakSubscriberCount = peakSubscriberCount;
Capacity = capacity;
PublishCount = publishCount;
SubscribeCount = subscribeCount;
UnsubscribeCount = unsubscribeCount;
ResizeCount = resizeCount;
ClearCount = clearCount;
LastOperationFrame = lastOperationFrame;
LastOperationTicksUtc = lastOperationTicksUtc;
}
}
internal readonly struct EventDebugSubscriberInfo
{
internal readonly int HandlerIndex;
internal readonly int Version;
internal readonly string DeclaringTypeName;
internal readonly string MethodName;
internal readonly string TargetTypeName;
internal readonly UnityEngine.Object UnityTarget;
internal readonly bool IsStatic;
internal readonly bool IsUnityObjectDestroyed;
internal EventDebugSubscriberInfo(
int handlerIndex,
int version,
string declaringTypeName,
string methodName,
string targetTypeName,
UnityEngine.Object unityTarget,
bool isStatic,
bool isUnityObjectDestroyed)
{
HandlerIndex = handlerIndex;
Version = version;
DeclaringTypeName = declaringTypeName;
MethodName = methodName;
TargetTypeName = targetTypeName;
UnityTarget = unityTarget;
IsStatic = isStatic;
IsUnityObjectDestroyed = isUnityObjectDestroyed;
}
}
internal readonly struct EventDebugOperationRecord
{
internal readonly Type EventType;
internal readonly EventDebugOperationKind OperationKind;
internal readonly int Frame;
internal readonly long TicksUtc;
internal readonly int SubscriberCount;
internal readonly int Capacity;
internal EventDebugOperationRecord(
Type eventType,
EventDebugOperationKind operationKind,
int frame,
long ticksUtc,
int subscriberCount,
int capacity)
{
EventType = eventType;
OperationKind = operationKind;
Frame = frame;
TicksUtc = ticksUtc;
SubscriberCount = subscriberCount;
Capacity = capacity;
}
}
internal static class EventDebugRegistry
{
private const int HistoryCapacity = 128;
private sealed class State
{
internal readonly Type EventType;
internal Func<int> SubscriberCountProvider;
internal Func<int> CapacityProvider;
internal Func<EventDebugSubscriberInfo[]> SubscribersProvider;
internal int PeakSubscriberCount;
internal long PublishCount;
internal long SubscribeCount;
internal long UnsubscribeCount;
internal int ResizeCount;
internal int ClearCount;
internal int LastOperationFrame;
internal long LastOperationTicksUtc;
internal State(Type eventType)
{
EventType = eventType;
}
}
private static readonly Dictionary<Type, State> _states = new();
private static readonly List<Type> _registrationOrder = new();
private static EventDebugOperationRecord[] _history = new EventDebugOperationRecord[HistoryCapacity];
private static int _historyWriteIndex;
private static int _historyCount;
internal static void RegisterContainer<T>(
Func<int> subscriberCountProvider,
Func<int> capacityProvider,
Func<EventDebugSubscriberInfo[]> subscribersProvider)
where T : struct, IEventArgs
{
Type eventType = typeof(T);
if (!_states.TryGetValue(eventType, out State state))
{
state = new State(eventType);
_states.Add(eventType, state);
_registrationOrder.Add(eventType);
}
state.SubscriberCountProvider = subscriberCountProvider;
state.CapacityProvider = capacityProvider;
state.SubscribersProvider = subscribersProvider;
state.PeakSubscriberCount = Math.Max(state.PeakSubscriberCount, subscriberCountProvider());
}
internal static void RecordSubscribe<T>(int subscriberCount, int capacity) where T : struct, IEventArgs
{
State state = GetState<T>();
state.SubscribeCount++;
state.PeakSubscriberCount = Math.Max(state.PeakSubscriberCount, subscriberCount);
MarkOperation(state, EventDebugOperationKind.Subscribe, subscriberCount, capacity);
}
internal static void RecordUnsubscribe<T>(int subscriberCount, int capacity) where T : struct, IEventArgs
{
State state = GetState<T>();
state.UnsubscribeCount++;
MarkOperation(state, EventDebugOperationKind.Unsubscribe, subscriberCount, capacity);
}
internal static void RecordPublish<T>(int subscriberCount, int capacity) where T : struct, IEventArgs
{
State state = GetState<T>();
state.PublishCount++;
MarkOperation(state, EventDebugOperationKind.Publish, subscriberCount, capacity);
}
internal static void RecordResize<T>(int subscriberCount, int capacity) where T : struct, IEventArgs
{
State state = GetState<T>();
state.ResizeCount++;
MarkOperation(state, EventDebugOperationKind.Resize, subscriberCount, capacity);
}
internal static void RecordClear<T>(int subscriberCount, int capacity) where T : struct, IEventArgs
{
State state = GetState<T>();
state.ClearCount++;
MarkOperation(state, EventDebugOperationKind.Clear, subscriberCount, capacity);
}
internal static EventDebugSummary[] GetSummaries()
{
int count = _registrationOrder.Count;
EventDebugSummary[] summaries = new EventDebugSummary[count];
for (int i = 0; i < count; i++)
{
summaries[i] = BuildSummary(_states[_registrationOrder[i]]);
}
return summaries;
}
internal static bool TryGetDetails(Type eventType, out EventDebugSummary summary, out EventDebugSubscriberInfo[] subscribers)
{
if (_states.TryGetValue(eventType, out State state))
{
summary = BuildSummary(state);
subscribers = state.SubscribersProvider?.Invoke() ?? Array.Empty<EventDebugSubscriberInfo>();
return true;
}
summary = default;
subscribers = Array.Empty<EventDebugSubscriberInfo>();
return false;
}
internal static EventDebugOperationRecord[] GetRecentOperations()
{
EventDebugOperationRecord[] result = new EventDebugOperationRecord[_historyCount];
for (int i = 0; i < _historyCount; i++)
{
int index = (_historyWriteIndex - 1 - i + _history.Length) % _history.Length;
result[i] = _history[index];
}
return result;
}
internal static void ResetStats()
{
foreach (Type eventType in _registrationOrder)
{
State state = _states[eventType];
state.PeakSubscriberCount = state.SubscriberCountProvider?.Invoke() ?? 0;
state.PublishCount = 0;
state.SubscribeCount = 0;
state.UnsubscribeCount = 0;
state.ResizeCount = 0;
state.ClearCount = 0;
state.LastOperationFrame = 0;
state.LastOperationTicksUtc = 0;
}
_historyWriteIndex = 0;
_historyCount = 0;
}
private static State GetState<T>() where T : struct, IEventArgs
{
Type eventType = typeof(T);
if (_states.TryGetValue(eventType, out State state))
{
return state;
}
throw new InvalidOperationException($"Event debug state is not registered for {eventType.FullName}.");
}
private static EventDebugSummary BuildSummary(State state)
{
int subscriberCount = state.SubscriberCountProvider?.Invoke() ?? 0;
int capacity = state.CapacityProvider?.Invoke() ?? 0;
return new EventDebugSummary(
state.EventType,
state.SubscriberCountProvider != null,
subscriberCount,
state.PeakSubscriberCount,
capacity,
state.PublishCount,
state.SubscribeCount,
state.UnsubscribeCount,
state.ResizeCount,
state.ClearCount,
state.LastOperationFrame,
state.LastOperationTicksUtc);
}
private static void MarkOperation(State state, EventDebugOperationKind kind, int subscriberCount, int capacity)
{
int frame = Time.frameCount;
long ticksUtc = DateTime.UtcNow.Ticks;
state.LastOperationFrame = frame;
state.LastOperationTicksUtc = ticksUtc;
_history[_historyWriteIndex] = new EventDebugOperationRecord(
state.EventType,
kind,
frame,
ticksUtc,
subscriberCount,
capacity);
_historyWriteIndex = (_historyWriteIndex + 1) % _history.Length;
if (_historyCount < _history.Length)
{
_historyCount++;
}
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e6f1e983f1df4d6cb307f1c0ab0af6b3
timeCreated: 1774080000

View File

@ -1,4 +1,4 @@
using System;
 using System;
using System.Runtime.CompilerServices;
using Unity.IL2CPP.CompilerServices;
@ -24,17 +24,6 @@ namespace AlicizaX
EventContainer<T>.Publish(in evt);
}
[Il2CppSetOption(Option.NullChecks, false)]
[Il2CppSetOption(Option.DivideByZeroChecks, false)]
[Il2CppSetOption(Option.ArrayBoundsChecks, false)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Publish<T>(Action<T> init) where T : struct, IEventArgs
{
var evt = default(T);
init(evt);
Publish(in evt);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetSubscriberCount<T>() where T : struct, IEventArgs
{