增加Event调试编辑器
1.增加Event调试编辑器查看订阅信息 2.调试编辑器增加快照查看泄露风险数据
This commit is contained in:
parent
0c0e2243d4
commit
93aba55cb0
8
Editor/Event.meta
Normal file
8
Editor/Event.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c2a7d2e65d45494cbf9c71c37614d89f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
789
Editor/Event/EventMonitorWindow.cs
Normal file
789
Editor/Event/EventMonitorWindow.cs
Normal 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()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
Editor/Event/EventMonitorWindow.cs.meta
Normal file
3
Editor/Event/EventMonitorWindow.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9f62c982b4564f0b89a347090eec406c
|
||||||
|
timeCreated: 1774080001
|
||||||
@ -24,6 +24,10 @@ namespace AlicizaX
|
|||||||
private static int _activeCount;
|
private static int _activeCount;
|
||||||
private static int _version;
|
private static int _version;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private static int _publishDepth;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if Event_StrictCheck
|
#if Event_StrictCheck
|
||||||
private static System.Collections.Generic.HashSet<Action<TPayload>> _activeHandlers;
|
private static System.Collections.Generic.HashSet<Action<TPayload>> _activeHandlers;
|
||||||
#endif
|
#endif
|
||||||
@ -47,11 +51,19 @@ namespace AlicizaX
|
|||||||
#if Event_StrictCheck
|
#if Event_StrictCheck
|
||||||
_activeHandlers = new System.Collections.Generic.HashSet<Action<TPayload>>();
|
_activeHandlers = new System.Collections.Generic.HashSet<Action<TPayload>>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EventDebugRegistry.RegisterContainer<TPayload>(GetDebugSubscriberCount, GetDebugCapacity, GetDebugSubscribers);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static EventRuntimeHandle Subscribe(Action<TPayload> callback)
|
public static EventRuntimeHandle Subscribe(Action<TPayload> callback)
|
||||||
{
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
ThrowIfMutatingDuringPublish("subscribe");
|
||||||
|
#endif
|
||||||
|
|
||||||
#if Event_StrictCheck
|
#if Event_StrictCheck
|
||||||
if (_activeHandlers.Contains(callback))
|
if (_activeHandlers.Contains(callback))
|
||||||
{
|
{
|
||||||
@ -77,6 +89,10 @@ namespace AlicizaX
|
|||||||
_versions[handlerIndex] = version;
|
_versions[handlerIndex] = version;
|
||||||
_activeSlots[handlerIndex] = activeIndex;
|
_activeSlots[handlerIndex] = activeIndex;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EventDebugRegistry.RecordSubscribe<TPayload>(_activeCount, _callbacks.Length);
|
||||||
|
#endif
|
||||||
|
|
||||||
return new EventRuntimeHandle(TypeId, handlerIndex, version);
|
return new EventRuntimeHandle(TypeId, handlerIndex, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,12 +117,19 @@ namespace AlicizaX
|
|||||||
_freeSlots[_freeCount++] = i;
|
_freeSlots[_freeCount++] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length);
|
||||||
|
#endif
|
||||||
|
|
||||||
return _freeSlots[--_freeCount];
|
return _freeSlots[--_freeCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void Unsubscribe(int handlerIndex, int version)
|
private static void Unsubscribe(int handlerIndex, int version)
|
||||||
{
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
ThrowIfMutatingDuringPublish("unsubscribe");
|
||||||
|
#endif
|
||||||
|
|
||||||
if (_versions[handlerIndex] != version) return;
|
if (_versions[handlerIndex] != version) return;
|
||||||
|
|
||||||
@ -131,13 +154,25 @@ namespace AlicizaX
|
|||||||
}
|
}
|
||||||
|
|
||||||
_freeSlots[_freeCount++] = handlerIndex;
|
_freeSlots[_freeCount++] = handlerIndex;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EventDebugRegistry.RecordUnsubscribe<TPayload>(_activeCount, _callbacks.Length);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Publish(in TPayload payload)
|
public static void Publish(in TPayload payload)
|
||||||
{
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
_publishDepth++;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#endif
|
||||||
int count = _activeCount;
|
int count = _activeCount;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EventDebugRegistry.RecordPublish<TPayload>(count, _callbacks.Length);
|
||||||
|
#endif
|
||||||
if (count == 0) return;
|
if (count == 0) return;
|
||||||
|
|
||||||
int[] indices = _activeIndices;
|
int[] indices = _activeIndices;
|
||||||
@ -162,13 +197,25 @@ namespace AlicizaX
|
|||||||
switch (count - i)
|
switch (count - i)
|
||||||
{
|
{
|
||||||
case 3:
|
case 3:
|
||||||
callbacks[indices[i + 2]](payload);
|
callbacks[indices[i]](payload);
|
||||||
goto case 2;
|
|
||||||
case 2:
|
|
||||||
callbacks[indices[i + 1]](payload);
|
callbacks[indices[i + 1]](payload);
|
||||||
goto case 1;
|
callbacks[indices[i + 2]](payload);
|
||||||
case 1: callbacks[indices[i]](payload); break;
|
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
|
public static int SubscriberCount
|
||||||
@ -179,6 +226,10 @@ namespace AlicizaX
|
|||||||
|
|
||||||
public static void EnsureCapacity(int capacity)
|
public static void EnsureCapacity(int capacity)
|
||||||
{
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
ThrowIfMutatingDuringPublish("ensure capacity");
|
||||||
|
#endif
|
||||||
|
|
||||||
if (_callbacks.Length >= capacity) return;
|
if (_callbacks.Length >= capacity) return;
|
||||||
|
|
||||||
int oldLen = _callbacks.Length;
|
int oldLen = _callbacks.Length;
|
||||||
@ -192,10 +243,18 @@ namespace AlicizaX
|
|||||||
{
|
{
|
||||||
_freeSlots[_freeCount++] = i;
|
_freeSlots[_freeCount++] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EventDebugRegistry.RecordResize<TPayload>(_activeCount, _callbacks.Length);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Clear()
|
public static void Clear()
|
||||||
{
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
ThrowIfMutatingDuringPublish("clear");
|
||||||
|
#endif
|
||||||
|
|
||||||
for (int i = 0; i < _activeCount; i++)
|
for (int i = 0; i < _activeCount; i++)
|
||||||
{
|
{
|
||||||
int idx = _activeIndices[i];
|
int idx = _activeIndices[i];
|
||||||
@ -206,9 +265,69 @@ namespace AlicizaX
|
|||||||
|
|
||||||
_activeCount = 0;
|
_activeCount = 0;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
EventDebugRegistry.RecordClear<TPayload>(_activeCount, _callbacks.Length);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if Event_StrictCheck
|
#if Event_StrictCheck
|
||||||
_activeHandlers.Clear();
|
_activeHandlers.Clear();
|
||||||
#endif
|
#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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
319
Runtime/ABase/Event/EventDebugRegistry.cs
Normal file
319
Runtime/ABase/Event/EventDebugRegistry.cs
Normal 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
|
||||||
3
Runtime/ABase/Event/EventDebugRegistry.cs.meta
Normal file
3
Runtime/ABase/Event/EventDebugRegistry.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e6f1e983f1df4d6cb307f1c0ab0af6b3
|
||||||
|
timeCreated: 1774080000
|
||||||
@ -24,17 +24,6 @@ namespace AlicizaX
|
|||||||
EventContainer<T>.Publish(in evt);
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int GetSubscriberCount<T>() where T : struct, IEventArgs
|
public static int GetSubscriberCount<T>() where T : struct, IEventArgs
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user