增加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 _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
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
[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
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user