Files
ShrinkEventBus/Editor/EventBusViewerWindow.cs
T
cneicy 1c921f5aec
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:50:18 +08:00

907 lines
36 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace ShrinkEventBus.Editor
{
public sealed class EventBusViewerWindow : EditorWindow
{
private sealed class BusRow
{
public string Key = string.Empty;
public string FilterKey = string.Empty;
public string Scheduler = string.Empty;
public string Mode = string.Empty;
public string Queue = string.Empty;
public string Overflow = string.Empty;
public int Subscribers;
}
private sealed class EventRow
{
public long Sequence;
public DateTime TimestampUtc;
public string Bus = string.Empty;
public string EventType = string.Empty;
public string ShortEventType = string.Empty;
public string Scheduler = string.Empty;
public string Mode = string.Empty;
public int ThreadId;
public double DurationMicroseconds;
public bool IsAsync;
public bool Accepted;
public bool Handled;
public bool Canceled;
public ShrinkPostFailure Failure;
public string Status => Failure != ShrinkPostFailure.None
? FormatFailure(Failure)
: Canceled
? "已取消"
: Handled
? "已处理"
: "无处理器";
}
private const int MaxEvents = 5000;
private const int MaxPendingEvents = 20000;
private static readonly Color SuccessColor = new(0.32f, 0.72f, 0.48f);
private static readonly Color WarningColor = new(0.94f, 0.68f, 0.24f);
private static readonly Color ErrorColor = new(0.95f, 0.36f, 0.34f);
private static readonly Color MutedColor = new(0.58f, 0.61f, 0.66f);
private readonly object _eventGate = new();
private readonly Queue<EventRow> _pendingEvents = new();
private readonly List<EventRow> _events = new();
private readonly List<EventRow> _filteredEvents = new();
private readonly List<BusRow> _buses = new();
private ListView? _busList;
private ListView? _eventList;
private Label? _eventEmptyState;
private ToolbarSearchField? _search;
private ToolbarToggle? _captureToggle;
private ToolbarToggle? _problemOnlyToggle;
private ToolbarToggle? _autoScrollToggle;
private Label? _captureState;
private Label? _status;
private Label? _busDescription;
private Label? _busCountMetric;
private Label? _handlerCountMetric;
private Label? _capturedMetric;
private Label? _rateMetric;
private Label? _latencyMetric;
private Label? _problemMetric;
private Label? _detailTitle;
private Label? _detailTime;
private Label? _detailBus;
private Label? _detailExecution;
private Label? _detailThread;
private Label? _detailDuration;
private Label? _detailResult;
private string _selectedBus = string.Empty;
private long _selectedEventSequence;
private long _nextSequence;
private long _droppedCaptureCount;
private double _nextRefresh;
private bool _captureRequested = true;
private bool _diagnosticsSubscribed;
[MenuItem("ShrinkSDK/事件总线/事件查看器")]
private static void Open()
{
var window = GetWindow<EventBusViewerWindow>();
window.titleContent = new GUIContent("事件总线 2.0");
window.minSize = new Vector2(980f, 560f);
window.Show();
}
private void OnEnable()
{
SetDiagnosticsSubscription(_captureRequested);
EditorApplication.update += Tick;
}
private void OnDisable()
{
SetDiagnosticsSubscription(false);
EditorApplication.update -= Tick;
}
public void CreateGUI()
{
rootVisualElement.Clear();
rootVisualElement.style.flexDirection = FlexDirection.Column;
rootVisualElement.style.backgroundColor = EditorGUIUtility.isProSkin
? new Color(0.105f, 0.112f, 0.125f)
: new Color(0.88f, 0.89f, 0.91f);
rootVisualElement.Add(BuildToolbar());
rootVisualElement.Add(BuildMetricsBand());
var mainSplit = new TwoPaneSplitView(0, 330f, TwoPaneSplitViewOrientation.Horizontal);
mainSplit.style.flexGrow = 1f;
mainSplit.Add(BuildBusPane());
mainSplit.Add(BuildEventWorkspace());
rootVisualElement.Add(mainSplit);
_status = new Label();
_status.style.height = 24f;
_status.style.paddingLeft = 10f;
_status.style.paddingTop = 4f;
_status.style.borderTopWidth = 1f;
_status.style.borderTopColor = BorderColor();
_status.style.color = MutedColor;
rootVisualElement.Add(_status);
RefreshAll();
}
private VisualElement BuildToolbar()
{
var toolbar = new Toolbar();
toolbar.style.height = 34f;
var title = new Label("ShrinkEventBus");
title.style.unityFontStyleAndWeight = FontStyle.Bold;
title.style.fontSize = 13f;
title.style.marginLeft = 4f;
title.style.marginRight = 8f;
toolbar.Add(title);
_captureState = new Label();
_captureState.style.minWidth = 48f;
_captureState.style.height = 18f;
_captureState.style.marginRight = 10f;
_captureState.style.paddingLeft = 6f;
_captureState.style.paddingRight = 6f;
_captureState.style.unityTextAlign = TextAnchor.MiddleCenter;
_captureState.style.unityFontStyleAndWeight = FontStyle.Bold;
_captureState.style.fontSize = 9f;
toolbar.Add(_captureState);
_search = new ToolbarSearchField { tooltip = "按总线标识或事件类型筛选" };
_search.style.width = 260f;
_search.RegisterValueChangedCallback(_ => ApplyFilter());
toolbar.Add(_search);
_problemOnlyToggle = new ToolbarToggle { text = "仅问题" };
_problemOnlyToggle.tooltip = "只显示已取消、已拒绝或失败的发布";
_problemOnlyToggle.RegisterValueChangedCallback(_ => ApplyFilter());
toolbar.Add(_problemOnlyToggle);
_autoScrollToggle = new ToolbarToggle { text = "跟随", value = true };
_autoScrollToggle.tooltip = "自动保持最新事件可见";
toolbar.Add(_autoScrollToggle);
_captureToggle = new ToolbarToggle { text = "捕获", value = _captureRequested };
_captureToggle.tooltip = "启用详细分发采样";
_captureToggle.RegisterValueChangedCallback(evt =>
{
_captureRequested = evt.newValue;
SetDiagnosticsSubscription(evt.newValue);
UpdateCaptureState();
});
toolbar.Add(_captureToggle);
var spacer = new VisualElement();
spacer.style.flexGrow = 1f;
toolbar.Add(spacer);
toolbar.Add(new ToolbarButton(RefreshAll) { text = "刷新", tooltip = "刷新总线状态" });
toolbar.Add(new ToolbarButton(ExportCsv) { text = "导出", tooltip = "将当前可见事件导出为 CSV" });
toolbar.Add(new ToolbarButton(ClearEvents) { text = "清空", tooltip = "清空已捕获事件" });
UpdateCaptureState();
return toolbar;
}
private VisualElement BuildMetricsBand()
{
var band = new VisualElement();
band.style.height = 58f;
band.style.flexDirection = FlexDirection.Row;
band.style.borderBottomWidth = 1f;
band.style.borderBottomColor = BorderColor();
band.style.backgroundColor = PanelColor();
_busCountMetric = AddMetric(band, "总线");
_handlerCountMetric = AddMetric(band, "处理器");
_capturedMetric = AddMetric(band, "已捕获");
_rateMetric = AddMetric(band, "事件 / 秒");
_latencyMetric = AddMetric(band, "平均分发耗时");
_problemMetric = AddMetric(band, "问题");
return band;
}
private static Label AddMetric(VisualElement parent, string caption)
{
var block = new VisualElement();
block.style.flexGrow = 1f;
block.style.flexBasis = 0f;
block.style.paddingLeft = 12f;
block.style.paddingTop = 6f;
block.style.borderRightWidth = 1f;
block.style.borderRightColor = BorderColor();
var captionLabel = new Label(caption);
captionLabel.style.fontSize = 9f;
captionLabel.style.color = MutedColor;
block.Add(captionLabel);
var value = new Label("0");
value.style.fontSize = 17f;
value.style.unityFontStyleAndWeight = FontStyle.Bold;
value.style.marginTop = 1f;
block.Add(value);
parent.Add(block);
return value;
}
private VisualElement BuildBusPane()
{
var pane = new VisualElement();
pane.style.flexGrow = 1f;
pane.style.backgroundColor = PanelColor();
pane.Add(BuildBusHeader());
_busList = new ListView(_buses, 30f, MakeBusRow, BindBusRow)
{
selectionType = SelectionType.Single,
showAlternatingRowBackgrounds = AlternatingRowBackground.ContentOnly,
virtualizationMethod = CollectionVirtualizationMethod.FixedHeight
};
_busList.style.flexGrow = 1f;
_busList.selectionChanged += selection =>
{
var selected = selection.OfType<BusRow>().FirstOrDefault();
if (selected == null)
return;
_selectedBus = selected.FilterKey;
UpdateBusDescription(selected);
ApplyFilter();
};
pane.Add(_busList);
_busDescription = new Label();
_busDescription.style.minHeight = 48f;
_busDescription.style.paddingLeft = 10f;
_busDescription.style.paddingRight = 8f;
_busDescription.style.paddingTop = 7f;
_busDescription.style.paddingBottom = 6f;
_busDescription.style.borderTopWidth = 1f;
_busDescription.style.borderTopColor = BorderColor();
_busDescription.style.color = MutedColor;
_busDescription.style.whiteSpace = WhiteSpace.Normal;
pane.Add(_busDescription);
return pane;
}
private VisualElement BuildEventWorkspace()
{
var split = new TwoPaneSplitView(1, 185f, TwoPaneSplitViewOrientation.Vertical);
split.style.flexGrow = 1f;
split.Add(BuildEventPane());
split.Add(BuildDetailPane());
return split;
}
private VisualElement BuildEventPane()
{
var pane = new VisualElement();
pane.style.flexGrow = 1f;
pane.Add(BuildEventHeader());
_eventList = new ListView(_filteredEvents, 28f, MakeEventRow, BindEventRow)
{
selectionType = SelectionType.Single,
showAlternatingRowBackgrounds = AlternatingRowBackground.ContentOnly,
virtualizationMethod = CollectionVirtualizationMethod.FixedHeight
};
_eventList.style.flexGrow = 1f;
_eventList.selectionChanged += selection =>
{
var selected = selection.OfType<EventRow>().FirstOrDefault();
if (selected == null)
return;
_selectedEventSequence = selected.Sequence;
ShowEventDetails(selected);
};
pane.Add(_eventList);
_eventEmptyState = new Label("暂无事件");
_eventEmptyState.style.flexGrow = 1f;
_eventEmptyState.style.unityTextAlign = TextAnchor.MiddleCenter;
_eventEmptyState.style.color = MutedColor;
pane.Add(_eventEmptyState);
return pane;
}
private VisualElement BuildDetailPane()
{
var pane = new VisualElement();
pane.style.flexGrow = 1f;
pane.style.backgroundColor = PanelColor();
pane.style.borderTopWidth = 1f;
pane.style.borderTopColor = BorderColor();
_detailTitle = new Label("未选择事件");
_detailTitle.style.fontSize = 13f;
_detailTitle.style.unityFontStyleAndWeight = FontStyle.Bold;
_detailTitle.style.marginLeft = 12f;
_detailTitle.style.marginTop = 9f;
_detailTitle.style.marginBottom = 7f;
pane.Add(_detailTitle);
var grid = new VisualElement();
grid.style.flexDirection = FlexDirection.Row;
grid.style.flexWrap = Wrap.Wrap;
grid.style.paddingLeft = 8f;
grid.style.paddingRight = 8f;
_detailTime = AddDetailField(grid, "时间");
_detailBus = AddDetailField(grid, "总线");
_detailExecution = AddDetailField(grid, "执行方式");
_detailThread = AddDetailField(grid, "线程");
_detailDuration = AddDetailField(grid, "分发耗时");
_detailResult = AddDetailField(grid, "结果");
pane.Add(grid);
return pane;
}
private static Label AddDetailField(VisualElement grid, string caption)
{
var field = new VisualElement();
field.style.width = Length.Percent(33.333f);
field.style.minWidth = 190f;
field.style.paddingLeft = 5f;
field.style.paddingRight = 5f;
field.style.paddingBottom = 8f;
var captionLabel = new Label(caption);
captionLabel.style.fontSize = 9f;
captionLabel.style.color = MutedColor;
field.Add(captionLabel);
var value = new Label("-");
value.style.marginTop = 2f;
value.style.overflow = Overflow.Hidden;
value.style.whiteSpace = WhiteSpace.Normal;
field.Add(value);
grid.Add(field);
return value;
}
private static VisualElement BuildBusHeader()
{
var header = BuildHeaderContainer("总线");
var columns = BuildColumnRow();
columns.Add(MakeHeaderCell("标识", 1f));
columns.Add(MakeHeaderCell("调度器", 0f, 92f));
columns.Add(MakeHeaderCell("处理器", 0f, 58f, TextAnchor.MiddleRight));
header.Add(columns);
return header;
}
private static VisualElement BuildEventHeader()
{
var header = BuildHeaderContainer("事件流");
var columns = BuildColumnRow();
columns.Add(MakeHeaderCell("时间", 0f, 92f));
columns.Add(MakeHeaderCell("总线", 0f, 132f));
columns.Add(MakeHeaderCell("事件", 1f));
columns.Add(MakeHeaderCell("方式", 0f, 54f));
columns.Add(MakeHeaderCell("线程", 0f, 56f, TextAnchor.MiddleRight));
columns.Add(MakeHeaderCell("耗时", 0f, 82f, TextAnchor.MiddleRight));
columns.Add(MakeHeaderCell("结果", 0f, 90f));
header.Add(columns);
return header;
}
private static VisualElement BuildHeaderContainer(string title)
{
var header = new VisualElement();
header.style.height = 52f;
header.style.borderBottomWidth = 1f;
header.style.borderBottomColor = BorderColor();
header.style.backgroundColor = HeaderColor();
var titleLabel = new Label(title);
titleLabel.style.height = 27f;
titleLabel.style.paddingLeft = 10f;
titleLabel.style.paddingTop = 7f;
titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
titleLabel.style.fontSize = 10f;
header.Add(titleLabel);
return header;
}
private static VisualElement BuildColumnRow()
{
var row = new VisualElement();
row.style.height = 24f;
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
return row;
}
private static Label MakeHeaderCell(string text, float grow, float width = 0f,
TextAnchor alignment = TextAnchor.MiddleLeft)
{
var label = MakeCell(string.Empty, grow, width, alignment);
label.text = text;
label.style.fontSize = 9f;
label.style.color = MutedColor;
return label;
}
private static VisualElement MakeBusRow()
{
var row = new VisualElement { name = "bus-row" };
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.Add(MakeCell("key", 1f));
row.Add(MakeCell("scheduler", 0f, 92f));
row.Add(MakeCell("handlers", 0f, 58f, TextAnchor.MiddleRight));
return row;
}
private void BindBusRow(VisualElement element, int index)
{
if (index < 0 || index >= _buses.Count)
return;
var item = _buses[index];
element.Q<Label>("key").text = item.Key;
element.Q<Label>("scheduler").text = item.Scheduler;
element.Q<Label>("handlers").text = item.Subscribers.ToString(CultureInfo.InvariantCulture);
element.tooltip = item.FilterKey.Length == 0
? "汇总视图"
: $"{item.Mode} | 队列 {item.Queue} | 溢出策略 {item.Overflow}";
}
private static VisualElement MakeEventRow()
{
var row = new VisualElement { name = "event-row" };
row.style.flexDirection = FlexDirection.Row;
row.style.alignItems = Align.Center;
row.Add(MakeCell("time", 0f, 92f));
row.Add(MakeCell("bus", 0f, 132f));
row.Add(MakeCell("type", 1f));
row.Add(MakeCell("kind", 0f, 54f));
row.Add(MakeCell("thread", 0f, 56f, TextAnchor.MiddleRight));
row.Add(MakeCell("latency", 0f, 82f, TextAnchor.MiddleRight));
row.Add(MakeCell("status", 0f, 90f));
return row;
}
private void BindEventRow(VisualElement element, int index)
{
if (index < 0 || index >= _filteredEvents.Count)
return;
var item = _filteredEvents[index];
element.Q<Label>("time").text = item.TimestampUtc.ToLocalTime().ToString("HH:mm:ss.fff");
element.Q<Label>("bus").text = item.Bus;
element.Q<Label>("type").text = item.ShortEventType;
element.Q<Label>("kind").text = item.IsAsync ? "异步" : "同步";
element.Q<Label>("thread").text = "线程 " + item.ThreadId;
element.Q<Label>("latency").text = FormatDuration(item.DurationMicroseconds);
var status = element.Q<Label>("status");
status.text = item.Status;
status.style.color = item.Failure != ShrinkPostFailure.None
? ErrorColor
: item.Canceled
? WarningColor
: item.Handled
? SuccessColor
: MutedColor;
element.tooltip = $"{item.EventType}\n{item.Scheduler} / {item.Mode} / 线程 {item.ThreadId}";
}
private static Label MakeCell(string name, float grow, float width = 0f,
TextAnchor alignment = TextAnchor.MiddleLeft)
{
var label = new Label { name = name };
label.style.flexGrow = grow;
label.style.minWidth = 0f;
label.style.paddingLeft = 6f;
label.style.paddingRight = 4f;
label.style.unityTextAlign = alignment;
label.style.overflow = Overflow.Hidden;
label.style.whiteSpace = WhiteSpace.NoWrap;
if (width > 0f)
{
label.style.width = width;
label.style.flexShrink = 0f;
}
else
{
label.style.flexShrink = 1f;
}
return label;
}
private void RecordEvent(ShrinkEventTrace trace)
{
var row = new EventRow
{
Sequence = Interlocked.Increment(ref _nextSequence),
TimestampUtc = trace.TimestampUtc,
Bus = trace.BusKey.Id,
EventType = trace.EventType.FullName ?? trace.EventType.Name,
ShortEventType = trace.EventType.Name,
Scheduler = FormatScheduler(trace.Scheduler),
Mode = FormatDispatchMode(trace.DispatchMode),
ThreadId = trace.ThreadId,
DurationMicroseconds = trace.ElapsedTimestampTicks * (1_000_000d / Stopwatch.Frequency),
IsAsync = trace.IsAsync,
Accepted = trace.Result.Accepted,
Handled = trace.Result.Handled,
Canceled = trace.Result.Canceled,
Failure = trace.Result.Failure
};
lock (_eventGate)
{
if (_pendingEvents.Count >= MaxPendingEvents)
{
Interlocked.Increment(ref _droppedCaptureCount);
return;
}
_pendingEvents.Enqueue(row);
}
}
private void Tick()
{
if (EditorApplication.timeSinceStartup < _nextRefresh)
return;
_nextRefresh = EditorApplication.timeSinceStartup + 0.15;
DrainEvents();
RefreshBuses();
ApplyFilter();
}
private void RefreshAll()
{
DrainEvents();
RefreshBuses();
ApplyFilter();
}
private void RefreshBuses()
{
var snapshot = EventBus.Snapshot()
.OrderBy(item => item.Key.Id, StringComparer.Ordinal)
.ToArray();
var totalSubscribers = snapshot.Sum(item => item.Subscribers);
_buses.Clear();
_buses.Add(new BusRow
{
Key = "全部总线",
FilterKey = string.Empty,
Scheduler = "混合",
Mode = "混合",
Queue = "-",
Overflow = "-",
Subscribers = totalSubscribers
});
foreach (var bus in snapshot)
{
_buses.Add(new BusRow
{
Key = bus.Key.Id,
FilterKey = bus.Key.Id,
Scheduler = FormatScheduler(bus.Options.Scheduler),
Mode = FormatDispatchMode(bus.Options.DispatchMode),
Queue = bus.Options.QueueCapacity.ToString(CultureInfo.InvariantCulture),
Overflow = FormatOverflowPolicy(bus.Options.OverflowPolicy),
Subscribers = bus.Subscribers
});
}
_busList?.RefreshItems();
var selectedIndex = Math.Max(0, _buses.FindIndex(item => item.FilterKey == _selectedBus));
if (_busList != null && _busList.selectedIndex != selectedIndex)
_busList.selectedIndex = selectedIndex;
if (selectedIndex < _buses.Count)
UpdateBusDescription(_buses[selectedIndex]);
if (_busCountMetric != null)
_busCountMetric.text = snapshot.Length.ToString(CultureInfo.InvariantCulture);
if (_handlerCountMetric != null)
_handlerCountMetric.text = totalSubscribers.ToString("N0", CultureInfo.InvariantCulture);
}
private void DrainEvents()
{
lock (_eventGate)
{
while (_pendingEvents.Count > 0)
_events.Add(_pendingEvents.Dequeue());
}
if (_events.Count > MaxEvents)
_events.RemoveRange(0, _events.Count - MaxEvents);
}
private void ApplyFilter()
{
var query = _search?.value?.Trim() ?? string.Empty;
var problemOnly = _problemOnlyToggle?.value == true;
_filteredEvents.Clear();
for (var i = _events.Count - 1; i >= 0; i--)
{
var item = _events[i];
if (_selectedBus.Length > 0 && !string.Equals(item.Bus, _selectedBus, StringComparison.Ordinal))
continue;
if (problemOnly && item.Failure == ShrinkPostFailure.None && !item.Canceled && item.Accepted)
continue;
if (query.Length > 0 &&
item.Bus.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0 &&
item.EventType.IndexOf(query, StringComparison.OrdinalIgnoreCase) < 0)
continue;
_filteredEvents.Add(item);
}
if (_eventList != null)
{
var hasVisibleEvents = _filteredEvents.Count > 0;
_eventList.style.display = hasVisibleEvents ? DisplayStyle.Flex : DisplayStyle.None;
if (hasVisibleEvents)
_eventList.RefreshItems();
}
if (_eventEmptyState != null)
_eventEmptyState.style.display = _filteredEvents.Count == 0
? DisplayStyle.Flex
: DisplayStyle.None;
RestoreEventSelection();
UpdateMetrics();
UpdateStatus();
if (_autoScrollToggle?.value == true && _filteredEvents.Count > 0 && _selectedEventSequence == 0)
_eventList?.ScrollToItem(0);
}
private void RestoreEventSelection()
{
if (_eventList == null || _selectedEventSequence == 0)
return;
var index = _filteredEvents.FindIndex(item => item.Sequence == _selectedEventSequence);
if (index < 0)
{
_selectedEventSequence = 0;
_eventList.ClearSelection();
ClearEventDetails();
return;
}
if (_eventList.selectedIndex != index)
_eventList.selectedIndex = index;
}
private void UpdateMetrics()
{
var now = DateTime.UtcNow;
var recent = 0;
var problems = 0;
var duration = 0d;
for (var i = 0; i < _filteredEvents.Count; i++)
{
var item = _filteredEvents[i];
if ((now - item.TimestampUtc).TotalSeconds <= 1d)
recent++;
if (item.Failure != ShrinkPostFailure.None || item.Canceled || !item.Accepted)
problems++;
duration += item.DurationMicroseconds;
}
if (_capturedMetric != null)
_capturedMetric.text = _events.Count.ToString("N0", CultureInfo.InvariantCulture);
if (_rateMetric != null)
_rateMetric.text = recent.ToString("N0", CultureInfo.InvariantCulture);
if (_latencyMetric != null)
_latencyMetric.text = _filteredEvents.Count == 0
? "0 微秒"
: FormatDuration(duration / _filteredEvents.Count);
if (_problemMetric != null)
{
_problemMetric.text = problems.ToString("N0", CultureInfo.InvariantCulture);
_problemMetric.style.color = problems > 0 ? ErrorColor : SuccessColor;
}
}
private void UpdateStatus()
{
if (_status == null)
return;
var dropped = Interlocked.Read(ref _droppedCaptureCount);
var scope = _selectedBus.Length == 0 ? "全部总线" : _selectedBus;
_status.text = $"可见 {_filteredEvents.Count:N0} 条 / 保留 {_events.Count:N0} 条 | {scope} | 上限 {MaxEvents:N0} 条" +
(dropped > 0 ? $" | 捕获溢出 {dropped:N0} 条" : string.Empty);
}
private void UpdateBusDescription(BusRow row)
{
if (_busDescription == null)
return;
_busDescription.text = row.FilterKey.Length == 0
? $"汇总 | {row.Subscribers:N0} 个处理器"
: $"{row.Scheduler} / {row.Mode}\n队列 {row.Queue} | 溢出策略 {row.Overflow} | {row.Subscribers:N0} 个处理器";
}
private void ShowEventDetails(EventRow item)
{
if (_detailTitle == null)
return;
_detailTitle.text = item.EventType;
_detailTitle.tooltip = item.EventType;
if (_detailTime != null)
_detailTime.text = item.TimestampUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss.fff");
if (_detailBus != null)
_detailBus.text = item.Bus;
if (_detailExecution != null)
_detailExecution.text = $"{(item.IsAsync ? "异步" : "同步")} | {item.Scheduler} / {item.Mode}";
if (_detailThread != null)
_detailThread.text = "托管线程 " + item.ThreadId;
if (_detailDuration != null)
_detailDuration.text = item.DurationMicroseconds.ToString("N3", CultureInfo.InvariantCulture) + " 微秒";
if (_detailResult != null)
{
_detailResult.text = $"{item.Status} | 已接收={FormatBoolean(item.Accepted)} 已处理={FormatBoolean(item.Handled)} 已取消={FormatBoolean(item.Canceled)}";
_detailResult.style.color = item.Failure != ShrinkPostFailure.None
? ErrorColor
: item.Canceled
? WarningColor
: SuccessColor;
}
}
private void ClearEventDetails()
{
if (_detailTitle != null)
_detailTitle.text = "未选择事件";
foreach (var label in new[]
{
_detailTime, _detailBus, _detailExecution, _detailThread, _detailDuration, _detailResult
})
{
if (label != null)
{
label.text = "-";
label.style.color = StyleKeyword.Null;
}
}
}
private void SetDiagnosticsSubscription(bool enabled)
{
if (enabled == _diagnosticsSubscribed)
return;
if (enabled)
EventBus.DetailedPosted += RecordEvent;
else
EventBus.DetailedPosted -= RecordEvent;
_diagnosticsSubscribed = enabled;
}
private void UpdateCaptureState()
{
if (_captureState == null)
return;
_captureState.text = _diagnosticsSubscribed ? "实时" : "已暂停";
_captureState.style.color = _diagnosticsSubscribed ? SuccessColor : WarningColor;
_captureState.style.backgroundColor = _diagnosticsSubscribed
? new Color(0.12f, 0.28f, 0.19f)
: new Color(0.32f, 0.25f, 0.11f);
}
private void ClearEvents()
{
lock (_eventGate)
_pendingEvents.Clear();
_events.Clear();
_filteredEvents.Clear();
_selectedEventSequence = 0;
Interlocked.Exchange(ref _droppedCaptureCount, 0);
_eventList?.ClearSelection();
ClearEventDetails();
ApplyFilter();
}
private void ExportCsv()
{
var path = EditorUtility.SaveFilePanel("导出 EventBus 跟踪记录", string.Empty,
$"ShrinkEventBus-{DateTime.Now:yyyyMMdd-HHmmss}.csv", "csv");
if (string.IsNullOrWhiteSpace(path))
return;
var builder = new StringBuilder(Math.Max(256, _filteredEvents.Count * 128));
builder.AppendLine("序号,UTC时间,总线,事件类型,调度器,分发模式,执行方式,线程ID,耗时微秒,已接收,已处理,已取消,失败原因");
for (var i = _filteredEvents.Count - 1; i >= 0; i--)
{
var item = _filteredEvents[i];
builder.Append(item.Sequence).Append(',')
.Append(item.TimestampUtc.ToString("O")).Append(',')
.Append(Csv(item.Bus)).Append(',')
.Append(Csv(item.EventType)).Append(',')
.Append(item.Scheduler).Append(',')
.Append(item.Mode).Append(',')
.Append(item.IsAsync ? "异步" : "同步").Append(',')
.Append(item.ThreadId).Append(',')
.Append(item.DurationMicroseconds.ToString("F3", CultureInfo.InvariantCulture)).Append(',')
.Append(FormatBoolean(item.Accepted)).Append(',')
.Append(FormatBoolean(item.Handled)).Append(',')
.Append(FormatBoolean(item.Canceled)).Append(',')
.Append(FormatFailure(item.Failure)).AppendLine();
}
File.WriteAllText(path, builder.ToString(), new UTF8Encoding(false));
EditorUtility.RevealInFinder(path);
}
private static string Csv(string value) =>
'"' + value.Replace("\"", "\"\"") + '"';
private static string FormatDuration(double microseconds) => microseconds < 1000d
? microseconds.ToString("N2", CultureInfo.InvariantCulture) + " 微秒"
: (microseconds / 1000d).ToString("N2", CultureInfo.InvariantCulture) + " 毫秒";
private static string FormatBoolean(bool value) => value ? "是" : "否";
private static string FormatScheduler(ShrinkBusSchedulerKind scheduler) => scheduler switch
{
ShrinkBusSchedulerKind.Inline => "当前线程",
ShrinkBusSchedulerKind.MainThread => "主线程",
ShrinkBusSchedulerKind.DedicatedThread => "专属线程",
ShrinkBusSchedulerKind.TaskPool => "任务池",
_ => scheduler.ToString()
};
private static string FormatDispatchMode(ShrinkDispatchMode mode) => mode switch
{
ShrinkDispatchMode.Ordered => "有序",
ShrinkDispatchMode.Parallel => "并行",
_ => mode.ToString()
};
private static string FormatOverflowPolicy(ShrinkQueueOverflowPolicy policy) => policy switch
{
ShrinkQueueOverflowPolicy.Reject => "拒绝",
ShrinkQueueOverflowPolicy.DropNewest => "丢弃最新",
ShrinkQueueOverflowPolicy.DropOldest => "丢弃最旧",
ShrinkQueueOverflowPolicy.Wait => "等待",
_ => policy.ToString()
};
private static string FormatFailure(ShrinkPostFailure failure) => failure switch
{
ShrinkPostFailure.None => "无",
ShrinkPostFailure.BusStopped => "总线已停止",
ShrinkPostFailure.QueueFull => "队列已满",
ShrinkPostFailure.Canceled => "已取消",
ShrinkPostFailure.HandlerException => "处理器异常",
ShrinkPostFailure.InvalidEvent => "无效事件",
_ => failure.ToString()
};
private static Color PanelColor() => EditorGUIUtility.isProSkin
? new Color(0.13f, 0.137f, 0.15f)
: new Color(0.94f, 0.95f, 0.96f);
private static Color HeaderColor() => EditorGUIUtility.isProSkin
? new Color(0.16f, 0.168f, 0.183f)
: new Color(0.82f, 0.84f, 0.87f);
private static Color BorderColor() => EditorGUIUtility.isProSkin
? new Color(0.24f, 0.25f, 0.28f)
: new Color(0.66f, 0.68f, 0.72f);
}
}