com.wallstop-studios.dxmessaging 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +326 -0
- package/Editor/Analyzers/BaseCallLogMessageParser.cs.meta +3 -3
- package/Editor/Analyzers/BaseCallReportAggregator.cs.meta +3 -3
- package/Editor/Analyzers/DxMessagingConsoleHarvester.cs +8 -8
- package/Editor/Analyzers/DxMessagingConsoleHarvester.cs.meta +3 -3
- package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs +171 -13
- package/Editor/CustomEditors/MessageAwareComponentFallbackEditor.cs.meta +3 -3
- package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs +391 -96
- package/Editor/CustomEditors/MessageAwareComponentInspectorOverlay.cs.meta +3 -3
- package/Editor/CustomEditors/MessageAwareComponentInspectorView.cs +279 -0
- package/Editor/CustomEditors/MessageAwareComponentInspectorView.cs.meta +11 -0
- package/Editor/DxMessagingEditorPalette.cs +66 -0
- package/Editor/DxMessagingEditorPalette.cs.meta +11 -0
- package/Editor/DxMessagingEditorTheme.cs +200 -0
- package/Editor/DxMessagingEditorTheme.cs.meta +11 -0
- package/Editor/DxMessagingMenu.cs.meta +3 -3
- package/Editor/DxMessagingSceneBuildProcessor.cs.meta +3 -3
- package/Editor/Icons/dxmessaging-icon-256.png +0 -0
- package/Editor/Icons/dxmessaging-icon-256.png.meta +156 -0
- package/Editor/Icons/dxmessaging-icon-32.png +0 -0
- package/Editor/Icons/dxmessaging-icon-32.png.meta +156 -0
- package/Editor/Icons/dxmessaging-icon-48.png +0 -0
- package/Editor/Icons/dxmessaging-icon-48.png.meta +156 -0
- package/Editor/Icons.meta +8 -0
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs +2 -3
- package/Editor/Settings/DxMessagingBaseCallIgnoreSync.cs.meta +3 -3
- package/Editor/Settings/DxMessagingSettingsProvider.cs +368 -33
- package/Editor/SetupCscRsp.cs +232 -163
- package/Editor/Testing/MessagingComponentEditorHarness.cs +11 -4
- package/Editor/Theme/DxMessagingTheme.uss +255 -0
- package/Editor/Theme/DxMessagingTheme.uss.meta +11 -0
- package/Editor/Theme/DxTokens.uss +90 -0
- package/Editor/Theme/DxTokens.uss.meta +11 -0
- package/Editor/Theme.meta +8 -0
- package/Editor/Windows/DxMessagingFlowGraphWindow.cs +5513 -0
- package/Editor/Windows/DxMessagingFlowGraphWindow.cs.meta +11 -0
- package/Editor/Windows/DxMessagingMessageMonitorWindow.cs +2288 -0
- package/Editor/Windows/DxMessagingMessageMonitorWindow.cs.meta +11 -0
- package/Editor/Windows.meta +8 -0
- package/README.md +84 -35
- package/Runtime/Analyzers.meta +8 -0
- package/Runtime/AssemblyInfo.cs +1 -0
- package/Runtime/Core/Attributes/DxIgnoreMissingBaseCallAttribute.cs.meta +3 -3
- package/Runtime/Core/Diagnostics/MessageEmissionData.cs +29 -1
- package/Runtime/Core/DxMessagingStaticState.cs.meta +3 -3
- package/Runtime/Core/Extensions/MessageBusExtensions.cs.meta +3 -3
- package/Runtime/Core/Extensions/MessageExtensions.cs +4 -5
- package/Runtime/Core/Extensions/MessageExtensions.cs.meta +3 -3
- package/Runtime/Core/Helper/MessageCache.cs +56 -23
- package/Runtime/Core/IMessage.cs.meta +3 -3
- package/Runtime/Core/InstanceId.cs.meta +3 -3
- package/Runtime/Core/Internal/FlatDispatch.cs +32 -0
- package/Runtime/Core/Internal/TypedSlots.cs +45 -7
- package/Runtime/Core/MessageBus/DiagnosticsTarget.cs.meta +3 -3
- package/Runtime/Core/MessageBus/GlobalMessageBusProvider.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageBus.cs +63 -28
- package/Runtime/Core/MessageBus/IMessageBus.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageBusProvider.cs.meta +3 -3
- package/Runtime/Core/MessageBus/IMessageRegistrationBuilder.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessageBus.cs +1135 -626
- package/Runtime/Core/MessageBus/MessageBus.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessageBusRebindMode.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessageBusRegistration.cs +207 -0
- package/Runtime/Core/MessageBus/MessageBusRegistration.cs.meta +11 -0
- package/Runtime/Core/MessageBus/MessageRegistrationBuilder.cs.meta +3 -3
- package/Runtime/Core/MessageBus/MessagingRegistration.cs.meta +3 -3
- package/Runtime/Core/MessageBus/RegistrationLog.cs +25 -12
- package/Runtime/Core/MessageBus/RegistrationLog.cs.meta +3 -3
- package/Runtime/Core/MessageHandler.cs +1886 -1082
- package/Runtime/Core/MessageHandler.cs.meta +3 -3
- package/Runtime/Core/MessageRegistrationHandle.cs +19 -5
- package/Runtime/Core/MessageRegistrationHandle.cs.meta +3 -3
- package/Runtime/Core/MessageRegistrationToken.cs +1932 -895
- package/Runtime/Core/MessageRegistrationToken.cs.meta +3 -3
- package/Runtime/Core/Messages/IBroadcastMessage.cs.meta +3 -3
- package/Runtime/Core/Messages/ITargetedMessage.cs.meta +3 -3
- package/Runtime/Core/Messages/IUntargetedMessage.cs.meta +3 -3
- package/Runtime/Core/Messages/SourcedStringMessage.cs.meta +3 -3
- package/Runtime/Core/MessagingDebug.cs.meta +3 -3
- package/Runtime/Unity/CurrentGlobalMessageBusProvider.cs.meta +3 -3
- package/Runtime/Unity/DxMessagingRuntimeInitializer.cs.meta +3 -3
- package/Runtime/Unity/InitialGlobalMessageBusProvider.cs.meta +3 -3
- package/Runtime/Unity/Integrations/Reflex/ReflexRegistrationInstaller.cs.meta +3 -3
- package/Runtime/Unity/Integrations/VContainer/VContainerRegistrationExtensions.cs.meta +3 -3
- package/Runtime/Unity/Integrations/Zenject/ZenjectRegistrationInstaller.cs.meta +3 -3
- package/Runtime/Unity/MessageAwareComponent.cs.meta +3 -3
- package/Runtime/Unity/MessageBusProviderHandle.cs.meta +3 -3
- package/Runtime/Unity/MessagingComponent.cs.meta +3 -3
- package/Runtime/Unity/MessagingComponentInstaller.cs.meta +3 -3
- package/Runtime/Unity/ScriptableMessageBusProvider.cs.meta +3 -3
- package/Samples~/DI/Reflex/SampleInstaller.cs.meta +3 -3
- package/Samples~/DI/VContainer/SampleLifetimeScope.cs.meta +3 -3
- package/Samples~/DI/Zenject/SampleInstaller.cs.meta +3 -3
- package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.cs +173 -0
- package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.cs.meta +11 -0
- package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.unity +424 -0
- package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingExerciser.unity.meta +7 -0
- package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingReceiver.cs +185 -0
- package/Samples~/Diagnostics Tooling Exerciser/DiagnosticsToolingReceiver.cs.meta +11 -0
- package/Samples~/Diagnostics Tooling Exerciser/Messages.cs +46 -0
- package/Samples~/Diagnostics Tooling Exerciser/Messages.cs.meta +11 -0
- package/Samples~/Diagnostics Tooling Exerciser/README.md +53 -0
- package/Samples~/Diagnostics Tooling Exerciser/README.md.meta +7 -0
- package/Samples~/Diagnostics Tooling Exerciser/WallstopStudios.DxMessaging.DiagnosticsToolingExerciser.Sample.asmdef +13 -0
- package/Samples~/Diagnostics Tooling Exerciser/WallstopStudios.DxMessaging.DiagnosticsToolingExerciser.Sample.asmdef.meta +7 -0
- package/Samples~/Diagnostics Tooling Exerciser.meta +8 -0
- package/Samples~/Mini Combat/Boot.cs.meta +3 -3
- package/Samples~/Mini Combat/Enemy.cs.meta +3 -3
- package/Samples~/Mini Combat/Messages.cs.meta +3 -3
- package/Samples~/Mini Combat/Player.cs.meta +3 -3
- package/Samples~/Mini Combat/README.md +1 -1
- package/Samples~/Mini Combat/UIOverlay.cs.meta +3 -3
- package/Samples~/Mini Combat/Walkthrough.md +3 -3
- package/Samples~/UI Buttons + Inspector/DiagnosticsEnabler.cs.meta +3 -3
- package/Samples~/UI Buttons + Inspector/Messages.cs.meta +3 -3
- package/Samples~/UI Buttons + Inspector/MessagingObserver.cs.meta +3 -3
- package/Samples~/UI Buttons + Inspector/UIButtonEmitter.cs.meta +3 -3
- package/SourceGenerators/Directory.Build.props +5 -5
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/DxAutoConstructorGenerator.cs.meta +3 -3
- package/SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators/WallstopStudios.DxMessaging.SourceGenerators.csproj +12 -11
- package/docs/images/DxMessaging-banner.svg +79 -0
- package/docs/images/DxMessaging-banner.svg.meta +53 -0
- package/docs/images.meta +8 -0
- package/docs.meta +8 -0
- package/package.json +17 -5
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.CSharp.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/Microsoft.CodeAnalysis.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Collections.Immutable.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Reflection.Metadata.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/System.Runtime.CompilerServices.Unsafe.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.Analyzer.dll.meta +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll +0 -0
- /package/{Editor → Runtime}/Analyzers/WallstopStudios.DxMessaging.SourceGenerators.dll.meta +0 -0
|
@@ -0,0 +1,2288 @@
|
|
|
1
|
+
#if UNITY_EDITOR
|
|
2
|
+
namespace DxMessaging.Editor.Windows
|
|
3
|
+
{
|
|
4
|
+
using System;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using System.Text;
|
|
8
|
+
using Core;
|
|
9
|
+
using Core.Diagnostics;
|
|
10
|
+
using Core.MessageBus;
|
|
11
|
+
using Core.Messages;
|
|
12
|
+
using DxMessaging.Editor;
|
|
13
|
+
using DxMessaging.Editor.Testing;
|
|
14
|
+
using DxMessaging.Unity;
|
|
15
|
+
using UnityEditor;
|
|
16
|
+
using UnityEditor.SceneManagement;
|
|
17
|
+
using UnityEngine;
|
|
18
|
+
using UnityEngine.UIElements;
|
|
19
|
+
|
|
20
|
+
public sealed class DxMessagingMessageMonitorWindow : EditorWindow
|
|
21
|
+
{
|
|
22
|
+
internal const string RootClassName = "dxmessaging-monitor";
|
|
23
|
+
internal const string ToolbarClassName = "dxmessaging-monitor-toolbar";
|
|
24
|
+
internal const string RowClassName = "dxmessaging-monitor-row";
|
|
25
|
+
internal const string StatusLabelName = "dxmessaging-monitor-status";
|
|
26
|
+
internal const string FilterFieldName = "dxmessaging-monitor-filter";
|
|
27
|
+
internal const string ActiveFilterSummaryName = "dxmessaging-monitor-active-filter";
|
|
28
|
+
internal const string ActiveFilterSummaryLabelName =
|
|
29
|
+
"dxmessaging-monitor-active-filter-label";
|
|
30
|
+
internal const string ActiveFilterTokenScrollViewName =
|
|
31
|
+
"dxmessaging-monitor-active-filter-token-scroll";
|
|
32
|
+
internal const string ActiveFilterTokenClassName =
|
|
33
|
+
"dxmessaging-monitor-active-filter-token";
|
|
34
|
+
internal const string ActiveFilterClearButtonName =
|
|
35
|
+
"dxmessaging-monitor-active-filter-clear";
|
|
36
|
+
internal const string RefreshButtonName = "dxmessaging-monitor-refresh";
|
|
37
|
+
internal const string ExportButtonName = "dxmessaging-monitor-export";
|
|
38
|
+
internal const string ContentContainerName = "dxmessaging-monitor-content";
|
|
39
|
+
internal const string MessageSectionName = "dxmessaging-monitor-message-section";
|
|
40
|
+
internal const string EmptyStateLabelName = "dxmessaging-monitor-empty";
|
|
41
|
+
internal const string EmptyStateTitleLabelName = "dxmessaging-monitor-empty-title";
|
|
42
|
+
internal const string MessageTypeLabelName = "dxmessaging-monitor-message-type";
|
|
43
|
+
internal const string RouteKindLabelName = "dxmessaging-monitor-route-kind";
|
|
44
|
+
internal const string ContextLabelName = "dxmessaging-monitor-context";
|
|
45
|
+
internal const string StackTraceLabelName = "dxmessaging-monitor-stack";
|
|
46
|
+
internal const string DetailsPaneName = "dxmessaging-monitor-details";
|
|
47
|
+
internal const string DetailsTypeLabelName = "dxmessaging-monitor-details-type";
|
|
48
|
+
internal const string DetailsContextLabelName = "dxmessaging-monitor-details-context";
|
|
49
|
+
internal const string DetailsStackTraceLabelName = "dxmessaging-monitor-details-stack";
|
|
50
|
+
internal const string VisibleMessageTypeLanesName =
|
|
51
|
+
"dxmessaging-monitor-message-type-lanes";
|
|
52
|
+
internal const string VisibleMessageTypeLaneScrollViewName =
|
|
53
|
+
"dxmessaging-monitor-message-type-lane-scroll";
|
|
54
|
+
internal const string VisibleMessageTypeLaneRowClassName =
|
|
55
|
+
"dxmessaging-monitor-message-type-lane-row";
|
|
56
|
+
internal const string VisibleMessageTypeLanesSummaryLabelName =
|
|
57
|
+
"dxmessaging-monitor-message-type-lanes-summary";
|
|
58
|
+
internal const string VisibleMessageTypeLaneTypeLabelName =
|
|
59
|
+
"dxmessaging-monitor-message-type-lane-type";
|
|
60
|
+
internal const string VisibleMessageTypeLaneSummaryLabelName =
|
|
61
|
+
"dxmessaging-monitor-message-type-lane-summary";
|
|
62
|
+
internal const string VisibleMessageTypeLaneContextsLabelName =
|
|
63
|
+
"dxmessaging-monitor-message-type-lane-contexts";
|
|
64
|
+
internal const string VisibleMessageTypeLaneFilterButtonName =
|
|
65
|
+
"dxmessaging-monitor-message-type-lane-filter";
|
|
66
|
+
internal const string VisibleContextLanesName = "dxmessaging-monitor-context-lanes";
|
|
67
|
+
internal const string VisibleContextLaneScrollViewName =
|
|
68
|
+
"dxmessaging-monitor-context-lane-scroll";
|
|
69
|
+
internal const string VisibleContextLaneRowClassName =
|
|
70
|
+
"dxmessaging-monitor-context-lane-row";
|
|
71
|
+
internal const string VisibleContextLanesSummaryLabelName =
|
|
72
|
+
"dxmessaging-monitor-context-lanes-summary";
|
|
73
|
+
internal const string VisibleContextLaneContextLabelName =
|
|
74
|
+
"dxmessaging-monitor-context-lane-context";
|
|
75
|
+
internal const string VisibleContextLaneSummaryLabelName =
|
|
76
|
+
"dxmessaging-monitor-context-lane-summary";
|
|
77
|
+
internal const string VisibleContextLaneMessagesLabelName =
|
|
78
|
+
"dxmessaging-monitor-context-lane-messages";
|
|
79
|
+
internal const string VisibleContextLaneFilterButtonName =
|
|
80
|
+
"dxmessaging-monitor-context-lane-filter";
|
|
81
|
+
internal const string ComponentPanelName = "dxmessaging-monitor-components";
|
|
82
|
+
internal const string ComponentScrollViewName = "dxmessaging-monitor-component-scroll";
|
|
83
|
+
internal const string ComponentRowClassName = "dxmessaging-monitor-component-row";
|
|
84
|
+
internal const string ComponentNameLabelName = "dxmessaging-monitor-component-name";
|
|
85
|
+
internal const string ComponentSummaryLabelName = "dxmessaging-monitor-component-summary";
|
|
86
|
+
internal const string ComponentProviderLabelName = "dxmessaging-monitor-component-provider";
|
|
87
|
+
internal const string ComponentWarningLabelName = "dxmessaging-monitor-component-warning";
|
|
88
|
+
internal const string ComponentEmptyStateLabelName = "dxmessaging-monitor-component-empty";
|
|
89
|
+
|
|
90
|
+
private const string Title = "Message Monitor";
|
|
91
|
+
|
|
92
|
+
private string _filterText = string.Empty;
|
|
93
|
+
private int _selectedEntryIndex;
|
|
94
|
+
private MessageMonitorSnapshot _currentSnapshot = MessageMonitorSnapshot.Unavailable(
|
|
95
|
+
"No message monitor snapshot has been captured yet."
|
|
96
|
+
);
|
|
97
|
+
private IReadOnlyList<ComponentMonitorEntry> _currentComponents =
|
|
98
|
+
Array.Empty<ComponentMonitorEntry>();
|
|
99
|
+
|
|
100
|
+
[MenuItem("Tools/Wallstop Studios/DxMessaging/Message Monitor")]
|
|
101
|
+
public static void Open()
|
|
102
|
+
{
|
|
103
|
+
DxMessagingMessageMonitorWindow window = GetWindow<DxMessagingMessageMonitorWindow>();
|
|
104
|
+
window.titleContent = new GUIContent(Title, DxMessagingEditorTheme.LoadIcon());
|
|
105
|
+
window.minSize = new Vector2(420, 320);
|
|
106
|
+
window.Refresh();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private void CreateGUI()
|
|
110
|
+
{
|
|
111
|
+
titleContent = new GUIContent(Title, DxMessagingEditorTheme.LoadIcon());
|
|
112
|
+
Refresh();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private void Refresh()
|
|
116
|
+
{
|
|
117
|
+
MessageMonitorSnapshot snapshot = MessageHandler.MessageBus is MessageBus messageBus
|
|
118
|
+
? CaptureSnapshot(messageBus)
|
|
119
|
+
: MessageMonitorSnapshot.Unavailable(
|
|
120
|
+
"The active global bus is not the default DxMessaging MessageBus."
|
|
121
|
+
);
|
|
122
|
+
IReadOnlyList<ComponentMonitorEntry> components = CaptureComponentSnapshots();
|
|
123
|
+
_currentSnapshot = snapshot;
|
|
124
|
+
_currentComponents = components;
|
|
125
|
+
BuildMonitorUi(
|
|
126
|
+
rootVisualElement,
|
|
127
|
+
snapshot,
|
|
128
|
+
new MessageMonitorViewState(_filterText, _selectedEntryIndex),
|
|
129
|
+
HandleFilterChanged,
|
|
130
|
+
HandleSelectedEntryChanged,
|
|
131
|
+
Refresh,
|
|
132
|
+
exportText => EditorGUIUtility.systemCopyBuffer = exportText,
|
|
133
|
+
components
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private void HandleFilterChanged(string filterText)
|
|
138
|
+
{
|
|
139
|
+
string normalizedFilterText = filterText ?? string.Empty;
|
|
140
|
+
if (string.Equals(_filterText, normalizedFilterText, StringComparison.Ordinal))
|
|
141
|
+
{
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_filterText = normalizedFilterText;
|
|
146
|
+
_selectedEntryIndex = 0;
|
|
147
|
+
RefreshCurrentSnapshotContent();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private void HandleSelectedEntryChanged(int selectedEntryIndex)
|
|
151
|
+
{
|
|
152
|
+
int normalizedSelectedEntryIndex = Math.Max(0, selectedEntryIndex);
|
|
153
|
+
if (_selectedEntryIndex == normalizedSelectedEntryIndex)
|
|
154
|
+
{
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_selectedEntryIndex = normalizedSelectedEntryIndex;
|
|
159
|
+
RefreshCurrentSnapshotContent();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private void RefreshCurrentSnapshotContent()
|
|
163
|
+
{
|
|
164
|
+
VisualElement messageSection = rootVisualElement.Q<VisualElement>(MessageSectionName);
|
|
165
|
+
Label status = rootVisualElement.Q<Label>(StatusLabelName);
|
|
166
|
+
if (messageSection == null || status == null)
|
|
167
|
+
{
|
|
168
|
+
Refresh();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
MessageMonitorViewState viewState = new(_filterText, _selectedEntryIndex);
|
|
173
|
+
IReadOnlyList<MessageMonitorEntry> filteredEntries = FilterEntries(
|
|
174
|
+
_currentSnapshot.Entries,
|
|
175
|
+
viewState.FilterText
|
|
176
|
+
);
|
|
177
|
+
status.text = CreateStatusText(_currentSnapshot, filteredEntries.Count);
|
|
178
|
+
void RequestFilterChange(string filterText)
|
|
179
|
+
{
|
|
180
|
+
TextField filter = rootVisualElement.Q<TextField>(FilterFieldName);
|
|
181
|
+
if (filter != null)
|
|
182
|
+
{
|
|
183
|
+
filter.value = filterText ?? string.Empty;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
HandleFilterChanged(filterText);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
RenderMessageSection(
|
|
191
|
+
messageSection,
|
|
192
|
+
_currentSnapshot,
|
|
193
|
+
filteredEntries,
|
|
194
|
+
viewState,
|
|
195
|
+
HandleSelectedEntryChanged,
|
|
196
|
+
RequestFilterChange
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
internal static MessageMonitorSnapshot CaptureSnapshot(MessageBus messageBus)
|
|
201
|
+
{
|
|
202
|
+
if (messageBus == null)
|
|
203
|
+
{
|
|
204
|
+
throw new ArgumentNullException(nameof(messageBus));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
IReadOnlyList<MessageMonitorEntry> entries = messageBus
|
|
208
|
+
._emissionBuffer.Reverse()
|
|
209
|
+
.Select(MessageMonitorEntry.FromEmission)
|
|
210
|
+
.ToArray();
|
|
211
|
+
|
|
212
|
+
return new MessageMonitorSnapshot(
|
|
213
|
+
messageBus.DiagnosticsMode,
|
|
214
|
+
IMessageBus.GlobalMessageBufferSize,
|
|
215
|
+
entries
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
internal static void BuildMonitorUi(VisualElement root, MessageMonitorSnapshot snapshot)
|
|
220
|
+
{
|
|
221
|
+
BuildMonitorUi(root, snapshot, MessageMonitorViewState.Default);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
internal static void BuildMonitorUi(
|
|
225
|
+
VisualElement root,
|
|
226
|
+
MessageMonitorSnapshot snapshot,
|
|
227
|
+
IReadOnlyList<ComponentMonitorEntry> componentEntries
|
|
228
|
+
)
|
|
229
|
+
{
|
|
230
|
+
BuildMonitorUi(
|
|
231
|
+
root,
|
|
232
|
+
snapshot,
|
|
233
|
+
MessageMonitorViewState.Default,
|
|
234
|
+
componentEntries: componentEntries
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
internal static void BuildMonitorUi(
|
|
239
|
+
VisualElement root,
|
|
240
|
+
MessageMonitorSnapshot snapshot,
|
|
241
|
+
MessageMonitorViewState viewState,
|
|
242
|
+
Action<string> onFilterChanged = null,
|
|
243
|
+
Action<int> onSelectedEntryChanged = null,
|
|
244
|
+
Action onRefresh = null,
|
|
245
|
+
Action<string> onCopyExport = null,
|
|
246
|
+
IReadOnlyList<ComponentMonitorEntry> componentEntries = null
|
|
247
|
+
)
|
|
248
|
+
{
|
|
249
|
+
if (root == null)
|
|
250
|
+
{
|
|
251
|
+
throw new ArgumentNullException(nameof(root));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
root.Clear();
|
|
255
|
+
componentEntries ??= Array.Empty<ComponentMonitorEntry>();
|
|
256
|
+
DxMessagingEditorTheme.ApplyWindow(root);
|
|
257
|
+
root.AddToClassList(RootClassName);
|
|
258
|
+
root.style.paddingTop = 10;
|
|
259
|
+
root.style.paddingRight = 12;
|
|
260
|
+
root.style.paddingBottom = 12;
|
|
261
|
+
root.style.paddingLeft = 12;
|
|
262
|
+
|
|
263
|
+
VisualElement toolbar = new();
|
|
264
|
+
toolbar.AddToClassList(ToolbarClassName);
|
|
265
|
+
toolbar.AddToClassList(DxMessagingEditorTheme.ToolbarClassName);
|
|
266
|
+
toolbar.style.flexDirection = FlexDirection.Row;
|
|
267
|
+
toolbar.style.justifyContent = Justify.SpaceBetween;
|
|
268
|
+
toolbar.style.alignItems = Align.Center;
|
|
269
|
+
toolbar.style.marginBottom = 10;
|
|
270
|
+
|
|
271
|
+
Label title = new(Title);
|
|
272
|
+
title.style.fontSize = 16;
|
|
273
|
+
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
274
|
+
toolbar.Add(title);
|
|
275
|
+
|
|
276
|
+
IReadOnlyList<MessageMonitorEntry> filteredEntries = FilterEntries(
|
|
277
|
+
snapshot.Entries,
|
|
278
|
+
viewState.FilterText
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
Label status = new(CreateStatusText(snapshot, filteredEntries.Count))
|
|
282
|
+
{
|
|
283
|
+
name = StatusLabelName,
|
|
284
|
+
};
|
|
285
|
+
status.style.unityTextAlign = TextAnchor.MiddleRight;
|
|
286
|
+
toolbar.Add(status);
|
|
287
|
+
root.Add(toolbar);
|
|
288
|
+
|
|
289
|
+
VisualElement content = new() { name = ContentContainerName };
|
|
290
|
+
content.style.flexGrow = 1;
|
|
291
|
+
|
|
292
|
+
if (!snapshot.Available)
|
|
293
|
+
{
|
|
294
|
+
root.Add(content);
|
|
295
|
+
AddEmptyState(content, "Monitor unavailable", snapshot.UnavailableReason);
|
|
296
|
+
content.Add(CreateComponentPanel(componentEntries));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
Action<string> applyFilterText = null;
|
|
301
|
+
void RequestFilterChange(string filterText)
|
|
302
|
+
{
|
|
303
|
+
applyFilterText?.Invoke(filterText);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
void RefreshLocalContent(string filterText)
|
|
307
|
+
{
|
|
308
|
+
IReadOnlyList<MessageMonitorEntry> nextFilteredEntries = FilterEntries(
|
|
309
|
+
snapshot.Entries,
|
|
310
|
+
filterText
|
|
311
|
+
);
|
|
312
|
+
status.text = CreateStatusText(snapshot, nextFilteredEntries.Count);
|
|
313
|
+
RenderMonitorContent(
|
|
314
|
+
content,
|
|
315
|
+
snapshot,
|
|
316
|
+
nextFilteredEntries,
|
|
317
|
+
componentEntries,
|
|
318
|
+
new MessageMonitorViewState(filterText),
|
|
319
|
+
onSelectedEntryChanged,
|
|
320
|
+
RequestFilterChange
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
root.Add(
|
|
325
|
+
CreateControlRow(
|
|
326
|
+
snapshot,
|
|
327
|
+
viewState,
|
|
328
|
+
onFilterChanged,
|
|
329
|
+
onRefresh,
|
|
330
|
+
onCopyExport,
|
|
331
|
+
onFilterChanged == null ? RefreshLocalContent : null,
|
|
332
|
+
out applyFilterText
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
root.Add(content);
|
|
337
|
+
RenderMonitorContent(
|
|
338
|
+
content,
|
|
339
|
+
snapshot,
|
|
340
|
+
filteredEntries,
|
|
341
|
+
componentEntries,
|
|
342
|
+
viewState,
|
|
343
|
+
onSelectedEntryChanged,
|
|
344
|
+
RequestFilterChange
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
internal static string CreateExportText(MessageMonitorSnapshot snapshot, string filterText)
|
|
349
|
+
{
|
|
350
|
+
return CreateExportText(snapshot, FilterEntries(snapshot.Entries, filterText));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
internal static IReadOnlyList<ComponentMonitorEntry> CaptureComponentSnapshots()
|
|
354
|
+
{
|
|
355
|
+
MessagingComponent[] components = FindMessagingComponentsInLoadedScenes();
|
|
356
|
+
return CaptureComponentSnapshots(components.Where(IsSceneComponent));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
internal static IReadOnlyList<ComponentMonitorEntry> CaptureComponentSnapshots(
|
|
360
|
+
IEnumerable<MessagingComponent> components
|
|
361
|
+
)
|
|
362
|
+
{
|
|
363
|
+
if (components == null)
|
|
364
|
+
{
|
|
365
|
+
throw new ArgumentNullException(nameof(components));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
MessagingComponent[] orderedComponents = components
|
|
369
|
+
.Where(component => component != null)
|
|
370
|
+
.OrderBy(component => GetHierarchyPath(component.transform), StringComparer.Ordinal)
|
|
371
|
+
.ThenBy(component => InstanceId.StableId(component))
|
|
372
|
+
.ToArray();
|
|
373
|
+
|
|
374
|
+
List<ComponentMonitorEntry> entries = new(orderedComponents.Length);
|
|
375
|
+
foreach (MessagingComponent component in orderedComponents)
|
|
376
|
+
{
|
|
377
|
+
try
|
|
378
|
+
{
|
|
379
|
+
entries.Add(CreateComponentMonitorEntry(component));
|
|
380
|
+
}
|
|
381
|
+
catch (Exception exception)
|
|
382
|
+
{
|
|
383
|
+
entries.Add(CreateFailedComponentMonitorEntry(component, exception));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return entries;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
internal static string CreateExportText(
|
|
391
|
+
MessageMonitorSnapshot snapshot,
|
|
392
|
+
IReadOnlyList<MessageMonitorEntry> entries
|
|
393
|
+
)
|
|
394
|
+
{
|
|
395
|
+
if (entries == null)
|
|
396
|
+
{
|
|
397
|
+
throw new ArgumentNullException(nameof(entries));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
IReadOnlyList<MessageMonitorEntry> exportEntries = snapshot.DiagnosticsEnabled
|
|
401
|
+
? entries
|
|
402
|
+
: Array.Empty<MessageMonitorEntry>();
|
|
403
|
+
|
|
404
|
+
StringBuilder builder = new();
|
|
405
|
+
builder.AppendLine("{");
|
|
406
|
+
builder
|
|
407
|
+
.Append(" \"diagnosticsEnabled\": ")
|
|
408
|
+
.Append(snapshot.DiagnosticsEnabled ? "true" : "false")
|
|
409
|
+
.AppendLine(",");
|
|
410
|
+
builder.Append(" \"capacity\": ").Append(snapshot.Capacity).AppendLine(",");
|
|
411
|
+
builder.Append(" \"entryCount\": ").Append(exportEntries.Count).AppendLine(",");
|
|
412
|
+
builder.AppendLine(" \"entries\": [");
|
|
413
|
+
for (int i = 0; i < exportEntries.Count; i++)
|
|
414
|
+
{
|
|
415
|
+
MessageMonitorEntry entry = exportEntries[i];
|
|
416
|
+
builder.AppendLine(" {");
|
|
417
|
+
AppendJsonProperty(
|
|
418
|
+
builder,
|
|
419
|
+
"messageType",
|
|
420
|
+
entry.MessageTypeName,
|
|
421
|
+
trailingComma: true
|
|
422
|
+
);
|
|
423
|
+
AppendJsonProperty(builder, "context", entry.ContextText, trailingComma: true);
|
|
424
|
+
AppendJsonProperty(builder, "stackTrace", entry.StackTrace, trailingComma: false);
|
|
425
|
+
builder.Append(" }");
|
|
426
|
+
if (i < exportEntries.Count - 1)
|
|
427
|
+
{
|
|
428
|
+
builder.Append(",");
|
|
429
|
+
}
|
|
430
|
+
builder.AppendLine();
|
|
431
|
+
}
|
|
432
|
+
builder.AppendLine(" ]");
|
|
433
|
+
builder.Append("}");
|
|
434
|
+
return builder.ToString();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private static void RenderMonitorContent(
|
|
438
|
+
VisualElement content,
|
|
439
|
+
MessageMonitorSnapshot snapshot,
|
|
440
|
+
IReadOnlyList<MessageMonitorEntry> filteredEntries,
|
|
441
|
+
IReadOnlyList<ComponentMonitorEntry> componentEntries,
|
|
442
|
+
MessageMonitorViewState viewState,
|
|
443
|
+
Action<int> onSelectedEntryChanged,
|
|
444
|
+
Action<string> onFilterRequested
|
|
445
|
+
)
|
|
446
|
+
{
|
|
447
|
+
if (content == null)
|
|
448
|
+
{
|
|
449
|
+
throw new ArgumentNullException(nameof(content));
|
|
450
|
+
}
|
|
451
|
+
if (filteredEntries == null)
|
|
452
|
+
{
|
|
453
|
+
throw new ArgumentNullException(nameof(filteredEntries));
|
|
454
|
+
}
|
|
455
|
+
if (componentEntries == null)
|
|
456
|
+
{
|
|
457
|
+
throw new ArgumentNullException(nameof(componentEntries));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
content.Clear();
|
|
461
|
+
VisualElement messageSection = new() { name = MessageSectionName };
|
|
462
|
+
messageSection.style.flexGrow = 1;
|
|
463
|
+
content.Add(messageSection);
|
|
464
|
+
RenderMessageSection(
|
|
465
|
+
messageSection,
|
|
466
|
+
snapshot,
|
|
467
|
+
filteredEntries,
|
|
468
|
+
viewState,
|
|
469
|
+
onSelectedEntryChanged,
|
|
470
|
+
onFilterRequested
|
|
471
|
+
);
|
|
472
|
+
content.Add(CreateComponentPanel(componentEntries));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private static void RenderMessageSection(
|
|
476
|
+
VisualElement messageSection,
|
|
477
|
+
MessageMonitorSnapshot snapshot,
|
|
478
|
+
IReadOnlyList<MessageMonitorEntry> filteredEntries,
|
|
479
|
+
MessageMonitorViewState viewState,
|
|
480
|
+
Action<int> onSelectedEntryChanged,
|
|
481
|
+
Action<string> onFilterRequested
|
|
482
|
+
)
|
|
483
|
+
{
|
|
484
|
+
if (messageSection == null)
|
|
485
|
+
{
|
|
486
|
+
throw new ArgumentNullException(nameof(messageSection));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
messageSection.Clear();
|
|
490
|
+
|
|
491
|
+
if (!snapshot.DiagnosticsEnabled)
|
|
492
|
+
{
|
|
493
|
+
AddEmptyState(
|
|
494
|
+
messageSection,
|
|
495
|
+
"Diagnostics are Off",
|
|
496
|
+
"Enable diagnostics to collect message history."
|
|
497
|
+
);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (snapshot.Entries.Count == 0)
|
|
502
|
+
{
|
|
503
|
+
AddEmptyState(
|
|
504
|
+
messageSection,
|
|
505
|
+
"No messages yet",
|
|
506
|
+
"Diagnostics are On. No messages have been recorded yet."
|
|
507
|
+
);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (filteredEntries.Count == 0)
|
|
512
|
+
{
|
|
513
|
+
AddEmptyState(
|
|
514
|
+
messageSection,
|
|
515
|
+
"No matches",
|
|
516
|
+
"No messages match the current filter."
|
|
517
|
+
);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
messageSection.Add(CreateVisibleMessageTypeLanes(filteredEntries, onFilterRequested));
|
|
522
|
+
messageSection.Add(CreateVisibleContextLanes(filteredEntries, onFilterRequested));
|
|
523
|
+
|
|
524
|
+
ScrollView list = new(ScrollViewMode.Vertical);
|
|
525
|
+
list.style.flexGrow = 1;
|
|
526
|
+
int selectedEntryIndex = ClampSelectedIndex(
|
|
527
|
+
viewState.SelectedEntryIndex,
|
|
528
|
+
filteredEntries.Count
|
|
529
|
+
);
|
|
530
|
+
for (int i = 0; i < filteredEntries.Count; i++)
|
|
531
|
+
{
|
|
532
|
+
list.Add(
|
|
533
|
+
CreateRow(
|
|
534
|
+
filteredEntries[i],
|
|
535
|
+
i,
|
|
536
|
+
i == selectedEntryIndex,
|
|
537
|
+
onSelectedEntryChanged
|
|
538
|
+
)
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
messageSection.Add(list);
|
|
542
|
+
messageSection.Add(CreateDetailsPane(filteredEntries[selectedEntryIndex]));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private static IReadOnlyList<MessageMonitorEntry> FilterEntries(
|
|
546
|
+
IReadOnlyList<MessageMonitorEntry> entries,
|
|
547
|
+
string filterText
|
|
548
|
+
)
|
|
549
|
+
{
|
|
550
|
+
if (entries == null)
|
|
551
|
+
{
|
|
552
|
+
throw new ArgumentNullException(nameof(entries));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (string.IsNullOrWhiteSpace(filterText))
|
|
556
|
+
{
|
|
557
|
+
return entries;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return entries.Where(entry => entry.Matches(filterText)).ToArray();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
private static string CreateStatusText(MessageMonitorSnapshot snapshot, int visibleCount)
|
|
564
|
+
{
|
|
565
|
+
if (!snapshot.Available)
|
|
566
|
+
{
|
|
567
|
+
return "Unavailable";
|
|
568
|
+
}
|
|
569
|
+
string enabled = snapshot.DiagnosticsEnabled ? "On" : "Off";
|
|
570
|
+
if (
|
|
571
|
+
snapshot.DiagnosticsEnabled
|
|
572
|
+
&& visibleCount >= 0
|
|
573
|
+
&& visibleCount != snapshot.Entries.Count
|
|
574
|
+
)
|
|
575
|
+
{
|
|
576
|
+
return $"Diagnostics {enabled} | {visibleCount}/{snapshot.Entries.Count} shown | {snapshot.Entries.Count}/{snapshot.Capacity}";
|
|
577
|
+
}
|
|
578
|
+
return $"Diagnostics {enabled} | {snapshot.Entries.Count}/{snapshot.Capacity}";
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private static VisualElement CreateVisibleMessageTypeLanes(
|
|
582
|
+
IReadOnlyList<MessageMonitorEntry> entries,
|
|
583
|
+
Action<string> onFilterRequested
|
|
584
|
+
)
|
|
585
|
+
{
|
|
586
|
+
MessageMonitorTypeLane[] lanes = BuildVisibleMessageTypeLanes(entries);
|
|
587
|
+
VisualElement lanesRoot = new() { name = VisibleMessageTypeLanesName };
|
|
588
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(
|
|
589
|
+
lanesRoot,
|
|
590
|
+
DxMessagingEditorPalette.BorderPanel
|
|
591
|
+
);
|
|
592
|
+
lanesRoot.style.marginBottom = 8;
|
|
593
|
+
lanesRoot.style.paddingTop = 8;
|
|
594
|
+
lanesRoot.style.paddingRight = 8;
|
|
595
|
+
lanesRoot.style.paddingBottom = 8;
|
|
596
|
+
lanesRoot.style.paddingLeft = 8;
|
|
597
|
+
|
|
598
|
+
Label title = new("Visible Message Type Lanes");
|
|
599
|
+
title.AddToClassList(DxMessagingEditorTheme.CardLabelClassName);
|
|
600
|
+
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
601
|
+
lanesRoot.Add(title);
|
|
602
|
+
|
|
603
|
+
Label summary = new(CreateVisibleMessageTypeLanesSummaryText(lanes))
|
|
604
|
+
{
|
|
605
|
+
name = VisibleMessageTypeLanesSummaryLabelName,
|
|
606
|
+
};
|
|
607
|
+
summary.style.marginTop = 2;
|
|
608
|
+
summary.style.whiteSpace = WhiteSpace.Normal;
|
|
609
|
+
lanesRoot.Add(summary);
|
|
610
|
+
|
|
611
|
+
ScrollView laneRows = new(ScrollViewMode.Vertical)
|
|
612
|
+
{
|
|
613
|
+
name = VisibleMessageTypeLaneScrollViewName,
|
|
614
|
+
};
|
|
615
|
+
laneRows.style.maxHeight = 160;
|
|
616
|
+
laneRows.style.marginTop = 2;
|
|
617
|
+
lanesRoot.Add(laneRows);
|
|
618
|
+
|
|
619
|
+
int totalEntries = lanes.Sum(lane => lane.EntryCount);
|
|
620
|
+
foreach (MessageMonitorTypeLane lane in lanes)
|
|
621
|
+
{
|
|
622
|
+
laneRows.Add(
|
|
623
|
+
CreateVisibleMessageTypeLaneRow(lane, totalEntries, onFilterRequested)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return lanesRoot;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private static VisualElement CreateVisibleContextLanes(
|
|
631
|
+
IReadOnlyList<MessageMonitorEntry> entries,
|
|
632
|
+
Action<string> onFilterRequested
|
|
633
|
+
)
|
|
634
|
+
{
|
|
635
|
+
MessageMonitorContextLane[] lanes = BuildVisibleContextLanes(entries);
|
|
636
|
+
VisualElement lanesRoot = new() { name = VisibleContextLanesName };
|
|
637
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(
|
|
638
|
+
lanesRoot,
|
|
639
|
+
DxMessagingEditorPalette.BorderPanel
|
|
640
|
+
);
|
|
641
|
+
lanesRoot.style.marginBottom = 8;
|
|
642
|
+
lanesRoot.style.paddingTop = 8;
|
|
643
|
+
lanesRoot.style.paddingRight = 8;
|
|
644
|
+
lanesRoot.style.paddingBottom = 8;
|
|
645
|
+
lanesRoot.style.paddingLeft = 8;
|
|
646
|
+
|
|
647
|
+
Label title = new("Visible Context Lanes");
|
|
648
|
+
title.AddToClassList(DxMessagingEditorTheme.CardLabelClassName);
|
|
649
|
+
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
650
|
+
lanesRoot.Add(title);
|
|
651
|
+
|
|
652
|
+
Label summary = new(CreateVisibleContextLanesSummaryText(lanes))
|
|
653
|
+
{
|
|
654
|
+
name = VisibleContextLanesSummaryLabelName,
|
|
655
|
+
};
|
|
656
|
+
summary.style.marginTop = 2;
|
|
657
|
+
summary.style.whiteSpace = WhiteSpace.Normal;
|
|
658
|
+
lanesRoot.Add(summary);
|
|
659
|
+
|
|
660
|
+
ScrollView laneRows = new(ScrollViewMode.Vertical)
|
|
661
|
+
{
|
|
662
|
+
name = VisibleContextLaneScrollViewName,
|
|
663
|
+
};
|
|
664
|
+
laneRows.style.maxHeight = 160;
|
|
665
|
+
laneRows.style.marginTop = 2;
|
|
666
|
+
lanesRoot.Add(laneRows);
|
|
667
|
+
|
|
668
|
+
int totalEntries = lanes.Sum(lane => lane.EntryCount);
|
|
669
|
+
foreach (MessageMonitorContextLane lane in lanes)
|
|
670
|
+
{
|
|
671
|
+
laneRows.Add(CreateVisibleContextLaneRow(lane, totalEntries, onFilterRequested));
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return lanesRoot;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private static VisualElement CreateVisibleMessageTypeLaneRow(
|
|
678
|
+
MessageMonitorTypeLane lane,
|
|
679
|
+
int totalEntries,
|
|
680
|
+
Action<string> onFilterRequested
|
|
681
|
+
)
|
|
682
|
+
{
|
|
683
|
+
VisualElement row = new();
|
|
684
|
+
row.AddToClassList(VisibleMessageTypeLaneRowClassName);
|
|
685
|
+
row.AddToClassList(DxMessagingEditorTheme.CardClassName);
|
|
686
|
+
row.style.flexDirection = FlexDirection.Row;
|
|
687
|
+
row.style.alignItems = Align.Center;
|
|
688
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(row, DxMessagingEditorPalette.Amber);
|
|
689
|
+
row.style.marginTop = 6;
|
|
690
|
+
row.style.paddingTop = 7;
|
|
691
|
+
row.style.paddingRight = 8;
|
|
692
|
+
row.style.paddingBottom = 7;
|
|
693
|
+
row.style.paddingLeft = 10;
|
|
694
|
+
|
|
695
|
+
Label type = new(lane.MessageTypeName) { name = VisibleMessageTypeLaneTypeLabelName };
|
|
696
|
+
type.style.flexBasis = 0;
|
|
697
|
+
type.style.flexGrow = 1;
|
|
698
|
+
type.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
699
|
+
type.style.whiteSpace = WhiteSpace.Normal;
|
|
700
|
+
row.Add(type);
|
|
701
|
+
|
|
702
|
+
Label summary = new(
|
|
703
|
+
$"Entries: {lane.EntryCount} | Contexts: {lane.ContextCount} | Share: {CreateEntryShareText(lane.EntryCount, totalEntries)}"
|
|
704
|
+
)
|
|
705
|
+
{
|
|
706
|
+
name = VisibleMessageTypeLaneSummaryLabelName,
|
|
707
|
+
};
|
|
708
|
+
summary.style.flexBasis = 0;
|
|
709
|
+
summary.style.flexGrow = 2;
|
|
710
|
+
summary.style.marginLeft = 8;
|
|
711
|
+
summary.style.whiteSpace = WhiteSpace.Normal;
|
|
712
|
+
row.Add(summary);
|
|
713
|
+
|
|
714
|
+
Label contexts = new($"Contexts: {lane.ContextsText}")
|
|
715
|
+
{
|
|
716
|
+
name = VisibleMessageTypeLaneContextsLabelName,
|
|
717
|
+
};
|
|
718
|
+
contexts.style.flexBasis = 0;
|
|
719
|
+
contexts.style.flexGrow = 3;
|
|
720
|
+
contexts.style.marginLeft = 8;
|
|
721
|
+
contexts.style.whiteSpace = WhiteSpace.Normal;
|
|
722
|
+
row.Add(contexts);
|
|
723
|
+
|
|
724
|
+
row.Add(
|
|
725
|
+
CreateLaneFilterButton(
|
|
726
|
+
VisibleMessageTypeLaneFilterButtonName,
|
|
727
|
+
CreateMessageTypeLaneFilterText(lane.MessageTypeName),
|
|
728
|
+
onFilterRequested
|
|
729
|
+
)
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
return row;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private static VisualElement CreateVisibleContextLaneRow(
|
|
736
|
+
MessageMonitorContextLane lane,
|
|
737
|
+
int totalEntries,
|
|
738
|
+
Action<string> onFilterRequested
|
|
739
|
+
)
|
|
740
|
+
{
|
|
741
|
+
VisualElement row = new();
|
|
742
|
+
row.AddToClassList(VisibleContextLaneRowClassName);
|
|
743
|
+
row.AddToClassList(DxMessagingEditorTheme.CardClassName);
|
|
744
|
+
row.style.flexDirection = FlexDirection.Row;
|
|
745
|
+
row.style.alignItems = Align.Center;
|
|
746
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(row, DxMessagingEditorPalette.AmberSoft);
|
|
747
|
+
row.style.marginTop = 6;
|
|
748
|
+
row.style.paddingTop = 7;
|
|
749
|
+
row.style.paddingRight = 8;
|
|
750
|
+
row.style.paddingBottom = 7;
|
|
751
|
+
row.style.paddingLeft = 10;
|
|
752
|
+
|
|
753
|
+
Label context = new(lane.ContextText) { name = VisibleContextLaneContextLabelName };
|
|
754
|
+
context.style.flexBasis = 0;
|
|
755
|
+
context.style.flexGrow = 1;
|
|
756
|
+
context.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
757
|
+
context.style.whiteSpace = WhiteSpace.Normal;
|
|
758
|
+
row.Add(context);
|
|
759
|
+
|
|
760
|
+
Label summary = new(
|
|
761
|
+
$"Entries: {lane.EntryCount} | Message types: {lane.MessageTypeCount} | Share: {CreateEntryShareText(lane.EntryCount, totalEntries)}"
|
|
762
|
+
)
|
|
763
|
+
{
|
|
764
|
+
name = VisibleContextLaneSummaryLabelName,
|
|
765
|
+
};
|
|
766
|
+
summary.style.flexBasis = 0;
|
|
767
|
+
summary.style.flexGrow = 2;
|
|
768
|
+
summary.style.marginLeft = 8;
|
|
769
|
+
summary.style.whiteSpace = WhiteSpace.Normal;
|
|
770
|
+
row.Add(summary);
|
|
771
|
+
|
|
772
|
+
Label messages = new($"Messages: {lane.MessageTypesText}")
|
|
773
|
+
{
|
|
774
|
+
name = VisibleContextLaneMessagesLabelName,
|
|
775
|
+
};
|
|
776
|
+
messages.style.flexBasis = 0;
|
|
777
|
+
messages.style.flexGrow = 3;
|
|
778
|
+
messages.style.marginLeft = 8;
|
|
779
|
+
messages.style.whiteSpace = WhiteSpace.Normal;
|
|
780
|
+
row.Add(messages);
|
|
781
|
+
|
|
782
|
+
row.Add(
|
|
783
|
+
CreateLaneFilterButton(
|
|
784
|
+
VisibleContextLaneFilterButtonName,
|
|
785
|
+
CreateContextLaneFilterText(lane.ContextText),
|
|
786
|
+
onFilterRequested
|
|
787
|
+
)
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
return row;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
private static Button CreateLaneFilterButton(
|
|
794
|
+
string name,
|
|
795
|
+
string filterText,
|
|
796
|
+
Action<string> onFilterRequested
|
|
797
|
+
)
|
|
798
|
+
{
|
|
799
|
+
Button button = new() { name = name, text = "Filter" };
|
|
800
|
+
button.AddToClassList(DxMessagingEditorTheme.ButtonGhostClassName);
|
|
801
|
+
button.tooltip = $"Filter to {filterText}";
|
|
802
|
+
button.SetEnabled(onFilterRequested != null && !string.IsNullOrWhiteSpace(filterText));
|
|
803
|
+
button.RegisterCallback<ClickEvent>(_ => onFilterRequested?.Invoke(filterText));
|
|
804
|
+
button.style.marginLeft = 8;
|
|
805
|
+
button.style.flexShrink = 0;
|
|
806
|
+
return button;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
private static string CreateMessageTypeLaneFilterText(string messageTypeName)
|
|
810
|
+
{
|
|
811
|
+
return $"type:{NormalizeMessageTypeName(messageTypeName)}";
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private static string CreateContextLaneFilterText(string contextText)
|
|
815
|
+
{
|
|
816
|
+
return $"context:{QuoteFilterValue(NormalizeContextText(contextText))}";
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
private static string QuoteFilterValue(string value)
|
|
820
|
+
{
|
|
821
|
+
return $"\"{(value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
private static string CreateVisibleMessageTypeLanesSummaryText(
|
|
825
|
+
IReadOnlyList<MessageMonitorTypeLane> lanes
|
|
826
|
+
)
|
|
827
|
+
{
|
|
828
|
+
int totalEntries = lanes.Sum(lane => lane.EntryCount);
|
|
829
|
+
if (lanes.Count == 0 || totalEntries <= 0 || lanes[0].EntryCount <= 0)
|
|
830
|
+
{
|
|
831
|
+
return $"{FormatCount(lanes.Count, "message type lane")} | Entries: {totalEntries} | Busiest message type: none";
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
MessageMonitorTypeLane busiestLane = lanes[0];
|
|
835
|
+
return $"{FormatCount(lanes.Count, "message type lane")} | Entries: {totalEntries} | Busiest message type: {busiestLane.MessageTypeName} | Share: {CreateEntryShareText(busiestLane.EntryCount, totalEntries)}";
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
private static string CreateVisibleContextLanesSummaryText(
|
|
839
|
+
IReadOnlyList<MessageMonitorContextLane> lanes
|
|
840
|
+
)
|
|
841
|
+
{
|
|
842
|
+
int totalEntries = lanes.Sum(lane => lane.EntryCount);
|
|
843
|
+
if (lanes.Count == 0 || totalEntries <= 0 || lanes[0].EntryCount <= 0)
|
|
844
|
+
{
|
|
845
|
+
return $"{FormatCount(lanes.Count, "context lane")} | Entries: {totalEntries} | Busiest context: none";
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
MessageMonitorContextLane busiestLane = lanes[0];
|
|
849
|
+
return $"{FormatCount(lanes.Count, "context lane")} | Entries: {totalEntries} | Busiest context: {busiestLane.ContextText} | Share: {CreateEntryShareText(busiestLane.EntryCount, totalEntries)}";
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private static MessageMonitorTypeLane[] BuildVisibleMessageTypeLanes(
|
|
853
|
+
IReadOnlyList<MessageMonitorEntry> entries
|
|
854
|
+
)
|
|
855
|
+
{
|
|
856
|
+
var laneGroups = entries
|
|
857
|
+
.GroupBy(entry => NormalizeMessageTypeIdentity(entry))
|
|
858
|
+
.Select(group =>
|
|
859
|
+
{
|
|
860
|
+
MessageMonitorEntry[] groupEntries = group.ToArray();
|
|
861
|
+
MessageMonitorEntry firstEntry = groupEntries[0];
|
|
862
|
+
string[] contexts = groupEntries
|
|
863
|
+
.Select(entry => entry.ContextText)
|
|
864
|
+
.Where(context => !string.IsNullOrWhiteSpace(context))
|
|
865
|
+
.Distinct(StringComparer.Ordinal)
|
|
866
|
+
.OrderBy(context => context, StringComparer.Ordinal)
|
|
867
|
+
.ToArray();
|
|
868
|
+
|
|
869
|
+
return new
|
|
870
|
+
{
|
|
871
|
+
MessageTypeName = NormalizeMessageTypeName(firstEntry.MessageTypeName),
|
|
872
|
+
MessageTypeDisplayPath = NormalizeMessageTypeName(
|
|
873
|
+
firstEntry.MessageTypeDisplayPath
|
|
874
|
+
),
|
|
875
|
+
EntryCount = groupEntries.Length,
|
|
876
|
+
Contexts = contexts,
|
|
877
|
+
};
|
|
878
|
+
})
|
|
879
|
+
.ToArray();
|
|
880
|
+
|
|
881
|
+
HashSet<string> duplicateDisplayNames = new(
|
|
882
|
+
laneGroups
|
|
883
|
+
.GroupBy(group => group.MessageTypeName, StringComparer.Ordinal)
|
|
884
|
+
.Where(group => group.Count() > 1)
|
|
885
|
+
.Select(group => group.Key),
|
|
886
|
+
StringComparer.Ordinal
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
return laneGroups
|
|
890
|
+
.Select(group => new MessageMonitorTypeLane(
|
|
891
|
+
duplicateDisplayNames.Contains(group.MessageTypeName)
|
|
892
|
+
? group.MessageTypeDisplayPath
|
|
893
|
+
: group.MessageTypeName,
|
|
894
|
+
group.EntryCount,
|
|
895
|
+
group.Contexts
|
|
896
|
+
))
|
|
897
|
+
.OrderByDescending(lane => lane.EntryCount)
|
|
898
|
+
.ThenBy(lane => lane.MessageTypeName, StringComparer.Ordinal)
|
|
899
|
+
.ToArray();
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private static MessageMonitorContextLane[] BuildVisibleContextLanes(
|
|
903
|
+
IReadOnlyList<MessageMonitorEntry> entries
|
|
904
|
+
)
|
|
905
|
+
{
|
|
906
|
+
HashSet<string> duplicateDisplayNames = CreateDuplicateMessageTypeDisplayNames(entries);
|
|
907
|
+
|
|
908
|
+
return entries
|
|
909
|
+
.GroupBy(entry => NormalizeContextText(entry.ContextText), StringComparer.Ordinal)
|
|
910
|
+
.Select(group =>
|
|
911
|
+
{
|
|
912
|
+
MessageMonitorEntry[] groupEntries = group.ToArray();
|
|
913
|
+
string[] messageTypes = CreateVisibleMessageTypeDisplayNames(
|
|
914
|
+
groupEntries,
|
|
915
|
+
duplicateDisplayNames
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
return new MessageMonitorContextLane(
|
|
919
|
+
group.Key,
|
|
920
|
+
groupEntries.Length,
|
|
921
|
+
messageTypes
|
|
922
|
+
);
|
|
923
|
+
})
|
|
924
|
+
.OrderByDescending(lane => lane.EntryCount)
|
|
925
|
+
.ThenBy(lane => lane.ContextText, StringComparer.Ordinal)
|
|
926
|
+
.ToArray();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
private static string[] CreateVisibleMessageTypeDisplayNames(
|
|
930
|
+
IReadOnlyList<MessageMonitorEntry> entries,
|
|
931
|
+
HashSet<string> duplicateDisplayNames
|
|
932
|
+
)
|
|
933
|
+
{
|
|
934
|
+
var typeGroups = entries
|
|
935
|
+
.GroupBy(entry => NormalizeMessageTypeIdentity(entry))
|
|
936
|
+
.Select(group =>
|
|
937
|
+
{
|
|
938
|
+
MessageMonitorEntry firstEntry = group.First();
|
|
939
|
+
return new
|
|
940
|
+
{
|
|
941
|
+
MessageTypeName = NormalizeMessageTypeName(firstEntry.MessageTypeName),
|
|
942
|
+
MessageTypeDisplayPath = NormalizeMessageTypeName(
|
|
943
|
+
firstEntry.MessageTypeDisplayPath
|
|
944
|
+
),
|
|
945
|
+
};
|
|
946
|
+
})
|
|
947
|
+
.ToArray();
|
|
948
|
+
|
|
949
|
+
return typeGroups
|
|
950
|
+
.Select(group =>
|
|
951
|
+
duplicateDisplayNames.Contains(group.MessageTypeName)
|
|
952
|
+
? group.MessageTypeDisplayPath
|
|
953
|
+
: group.MessageTypeName
|
|
954
|
+
)
|
|
955
|
+
.OrderBy(messageType => messageType, StringComparer.Ordinal)
|
|
956
|
+
.ToArray();
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
private static HashSet<string> CreateDuplicateMessageTypeDisplayNames(
|
|
960
|
+
IReadOnlyList<MessageMonitorEntry> entries
|
|
961
|
+
)
|
|
962
|
+
{
|
|
963
|
+
string[] typeNames = entries
|
|
964
|
+
.GroupBy(entry => NormalizeMessageTypeIdentity(entry))
|
|
965
|
+
.Select(group => NormalizeMessageTypeName(group.First().MessageTypeName))
|
|
966
|
+
.ToArray();
|
|
967
|
+
|
|
968
|
+
return new HashSet<string>(
|
|
969
|
+
typeNames
|
|
970
|
+
.GroupBy(typeName => typeName, StringComparer.Ordinal)
|
|
971
|
+
.Where(group => group.Count() > 1)
|
|
972
|
+
.Select(group => group.Key),
|
|
973
|
+
StringComparer.Ordinal
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
private static string NormalizeMessageTypeName(string messageTypeName)
|
|
978
|
+
{
|
|
979
|
+
return string.IsNullOrWhiteSpace(messageTypeName)
|
|
980
|
+
? "<unknown>"
|
|
981
|
+
: messageTypeName.Trim();
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
private static string NormalizeMessageTypeIdentity(MessageMonitorEntry entry)
|
|
985
|
+
{
|
|
986
|
+
return string.IsNullOrWhiteSpace(entry.MessageTypeIdentity)
|
|
987
|
+
? NormalizeMessageTypeName(entry.MessageTypeName)
|
|
988
|
+
: entry.MessageTypeIdentity.Trim();
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
private static string NormalizeContextText(string contextText)
|
|
992
|
+
{
|
|
993
|
+
return string.IsNullOrWhiteSpace(contextText) ? "Context: none" : contextText.Trim();
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
private static string CreateEntryShareText(int count, int total)
|
|
997
|
+
{
|
|
998
|
+
if (total <= 0)
|
|
999
|
+
{
|
|
1000
|
+
return $"{count}/{total} (n/a)";
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
int percent = (int)
|
|
1004
|
+
Math.Round((double)count / total * 100, MidpointRounding.AwayFromZero);
|
|
1005
|
+
return $"{count}/{total} ({percent}%)";
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
private static string FormatCount(int count, string noun)
|
|
1009
|
+
{
|
|
1010
|
+
return count == 1 ? $"1 {noun}" : $"{count} {noun}s";
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
private static VisualElement CreateControlRow(
|
|
1014
|
+
MessageMonitorSnapshot snapshot,
|
|
1015
|
+
MessageMonitorViewState viewState,
|
|
1016
|
+
Action<string> onFilterChanged,
|
|
1017
|
+
Action onRefresh,
|
|
1018
|
+
Action<string> onCopyExport,
|
|
1019
|
+
Action<string> onLocalFilterChanged,
|
|
1020
|
+
out Action<string> applyFilterText
|
|
1021
|
+
)
|
|
1022
|
+
{
|
|
1023
|
+
VisualElement controls = new();
|
|
1024
|
+
controls.style.marginBottom = 10;
|
|
1025
|
+
|
|
1026
|
+
VisualElement filterRow = new();
|
|
1027
|
+
filterRow.style.flexDirection = FlexDirection.Row;
|
|
1028
|
+
filterRow.style.alignItems = Align.Center;
|
|
1029
|
+
controls.Add(filterRow);
|
|
1030
|
+
|
|
1031
|
+
TextField filter = new("Filter") { name = FilterFieldName };
|
|
1032
|
+
filter.AddToClassList(DxMessagingEditorTheme.SearchClassName);
|
|
1033
|
+
filter.SetValueWithoutNotify(viewState.FilterText);
|
|
1034
|
+
filter.tooltip =
|
|
1035
|
+
"Use plain text, or field filters such as type:, message:, context:, and stack:.";
|
|
1036
|
+
filter.style.flexGrow = 1;
|
|
1037
|
+
filter.style.marginRight = 8;
|
|
1038
|
+
Button export = null;
|
|
1039
|
+
VisualElement activeFilter = null;
|
|
1040
|
+
void ApplyFilterState(string filterText)
|
|
1041
|
+
{
|
|
1042
|
+
string normalizedFilterText = filterText ?? string.Empty;
|
|
1043
|
+
onFilterChanged?.Invoke(normalizedFilterText);
|
|
1044
|
+
onLocalFilterChanged?.Invoke(normalizedFilterText);
|
|
1045
|
+
SetExportButtonEnabled(export, snapshot, normalizedFilterText, onCopyExport);
|
|
1046
|
+
UpdateActiveFilterSummary(
|
|
1047
|
+
activeFilter,
|
|
1048
|
+
normalizedFilterText,
|
|
1049
|
+
() => ClearFilter(filter, activeFilter, snapshot, onCopyExport, export)
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
applyFilterText = filterText =>
|
|
1054
|
+
{
|
|
1055
|
+
string normalizedFilterText = filterText ?? string.Empty;
|
|
1056
|
+
if (filter.panel != null)
|
|
1057
|
+
{
|
|
1058
|
+
if (
|
|
1059
|
+
!string.Equals(filter.value, normalizedFilterText, StringComparison.Ordinal)
|
|
1060
|
+
)
|
|
1061
|
+
{
|
|
1062
|
+
filter.value = normalizedFilterText;
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
ApplyFilterState(normalizedFilterText);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
filter.SetValueWithoutNotify(normalizedFilterText);
|
|
1071
|
+
ApplyFilterState(normalizedFilterText);
|
|
1072
|
+
};
|
|
1073
|
+
filter.RegisterValueChangedCallback(evt =>
|
|
1074
|
+
{
|
|
1075
|
+
ApplyFilterState(evt.newValue);
|
|
1076
|
+
});
|
|
1077
|
+
filterRow.Add(filter);
|
|
1078
|
+
|
|
1079
|
+
Button refresh = new(() => onRefresh?.Invoke())
|
|
1080
|
+
{
|
|
1081
|
+
name = RefreshButtonName,
|
|
1082
|
+
text = "Refresh",
|
|
1083
|
+
};
|
|
1084
|
+
refresh.AddToClassList(DxMessagingEditorTheme.ToolButtonClassName);
|
|
1085
|
+
refresh.SetEnabled(onRefresh != null);
|
|
1086
|
+
refresh.style.marginRight = 6;
|
|
1087
|
+
filterRow.Add(refresh);
|
|
1088
|
+
|
|
1089
|
+
export = new(() => onCopyExport?.Invoke(CreateExportText(snapshot, filter.value)))
|
|
1090
|
+
{
|
|
1091
|
+
name = ExportButtonName,
|
|
1092
|
+
text = "Copy JSON",
|
|
1093
|
+
};
|
|
1094
|
+
export.AddToClassList(DxMessagingEditorTheme.ToolButtonClassName);
|
|
1095
|
+
SetExportButtonEnabled(export, snapshot, viewState.FilterText, onCopyExport);
|
|
1096
|
+
filterRow.Add(export);
|
|
1097
|
+
|
|
1098
|
+
activeFilter = CreateActiveFilterSummary(
|
|
1099
|
+
viewState.FilterText,
|
|
1100
|
+
() => ClearFilter(filter, activeFilter, snapshot, onCopyExport, export)
|
|
1101
|
+
);
|
|
1102
|
+
controls.Add(activeFilter);
|
|
1103
|
+
|
|
1104
|
+
return controls;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
private static VisualElement CreateActiveFilterSummary(string filterText, Action onClear)
|
|
1108
|
+
{
|
|
1109
|
+
VisualElement summary = new() { name = ActiveFilterSummaryName };
|
|
1110
|
+
summary.AddToClassList(DxMessagingEditorTheme.CardClassName);
|
|
1111
|
+
summary.style.flexDirection = FlexDirection.Row;
|
|
1112
|
+
summary.style.alignItems = Align.Center;
|
|
1113
|
+
summary.style.marginTop = 6;
|
|
1114
|
+
summary.style.paddingTop = 5;
|
|
1115
|
+
summary.style.paddingRight = 6;
|
|
1116
|
+
summary.style.paddingBottom = 5;
|
|
1117
|
+
summary.style.paddingLeft = 8;
|
|
1118
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(summary, DxMessagingEditorPalette.Amber);
|
|
1119
|
+
|
|
1120
|
+
UpdateActiveFilterSummary(summary, filterText, onClear);
|
|
1121
|
+
return summary;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private static void UpdateActiveFilterSummary(
|
|
1125
|
+
VisualElement summary,
|
|
1126
|
+
string filterText,
|
|
1127
|
+
Action onClear
|
|
1128
|
+
)
|
|
1129
|
+
{
|
|
1130
|
+
if (summary == null)
|
|
1131
|
+
{
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
summary.Clear();
|
|
1136
|
+
if (string.IsNullOrWhiteSpace(filterText))
|
|
1137
|
+
{
|
|
1138
|
+
summary.style.display = DisplayStyle.None;
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
summary.style.display = DisplayStyle.Flex;
|
|
1143
|
+
bool typedFilter = MessageMonitorFilterQuery.TryCreateDisplayTokens(
|
|
1144
|
+
filterText,
|
|
1145
|
+
out string[] displayTokens
|
|
1146
|
+
);
|
|
1147
|
+
if (!typedFilter)
|
|
1148
|
+
{
|
|
1149
|
+
displayTokens = new[] { filterText.Trim() };
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
Label label = new(typedFilter ? "Active typed filters" : "Active text filter")
|
|
1153
|
+
{
|
|
1154
|
+
name = ActiveFilterSummaryLabelName,
|
|
1155
|
+
};
|
|
1156
|
+
label.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
1157
|
+
label.style.marginRight = 8;
|
|
1158
|
+
label.style.flexShrink = 0;
|
|
1159
|
+
summary.Add(label);
|
|
1160
|
+
|
|
1161
|
+
Button clear = new() { name = ActiveFilterClearButtonName, text = "Clear" };
|
|
1162
|
+
clear.AddToClassList(DxMessagingEditorTheme.ButtonGhostClassName);
|
|
1163
|
+
clear.RegisterCallback<ClickEvent>(_ => onClear?.Invoke());
|
|
1164
|
+
clear.style.marginRight = 8;
|
|
1165
|
+
clear.style.flexShrink = 0;
|
|
1166
|
+
summary.Add(clear);
|
|
1167
|
+
|
|
1168
|
+
ScrollView tokenScroll = new(ScrollViewMode.Vertical)
|
|
1169
|
+
{
|
|
1170
|
+
name = ActiveFilterTokenScrollViewName,
|
|
1171
|
+
};
|
|
1172
|
+
tokenScroll.style.flexGrow = 1;
|
|
1173
|
+
tokenScroll.style.flexShrink = 1;
|
|
1174
|
+
tokenScroll.style.maxHeight = 72;
|
|
1175
|
+
tokenScroll.contentContainer.style.flexDirection = FlexDirection.Row;
|
|
1176
|
+
tokenScroll.contentContainer.style.flexWrap = Wrap.Wrap;
|
|
1177
|
+
summary.Add(tokenScroll);
|
|
1178
|
+
|
|
1179
|
+
foreach (string token in displayTokens)
|
|
1180
|
+
{
|
|
1181
|
+
Label tokenLabel = new(token);
|
|
1182
|
+
tokenLabel.AddToClassList(ActiveFilterTokenClassName);
|
|
1183
|
+
tokenLabel.style.marginTop = 2;
|
|
1184
|
+
tokenLabel.style.marginRight = 6;
|
|
1185
|
+
tokenLabel.style.marginBottom = 2;
|
|
1186
|
+
tokenLabel.style.paddingTop = 2;
|
|
1187
|
+
tokenLabel.style.paddingRight = 5;
|
|
1188
|
+
tokenLabel.style.paddingBottom = 2;
|
|
1189
|
+
tokenLabel.style.paddingLeft = 5;
|
|
1190
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(
|
|
1191
|
+
tokenLabel,
|
|
1192
|
+
DxMessagingEditorPalette.Border
|
|
1193
|
+
);
|
|
1194
|
+
tokenLabel.style.whiteSpace = WhiteSpace.Normal;
|
|
1195
|
+
tokenScroll.Add(tokenLabel);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
private static void ClearFilter(
|
|
1200
|
+
TextField filter,
|
|
1201
|
+
VisualElement activeFilter,
|
|
1202
|
+
MessageMonitorSnapshot snapshot,
|
|
1203
|
+
Action<string> onCopyExport,
|
|
1204
|
+
Button export
|
|
1205
|
+
)
|
|
1206
|
+
{
|
|
1207
|
+
if (filter == null || string.IsNullOrEmpty(filter.value))
|
|
1208
|
+
{
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
if (filter.panel != null)
|
|
1213
|
+
{
|
|
1214
|
+
filter.value = string.Empty;
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
filter.SetValueWithoutNotify(string.Empty);
|
|
1219
|
+
SetExportButtonEnabled(export, snapshot, string.Empty, onCopyExport);
|
|
1220
|
+
UpdateActiveFilterSummary(activeFilter, string.Empty, null);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
private static void SetExportButtonEnabled(
|
|
1224
|
+
Button export,
|
|
1225
|
+
MessageMonitorSnapshot snapshot,
|
|
1226
|
+
string filterText,
|
|
1227
|
+
Action<string> onCopyExport
|
|
1228
|
+
)
|
|
1229
|
+
{
|
|
1230
|
+
if (export == null)
|
|
1231
|
+
{
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
export.SetEnabled(
|
|
1236
|
+
onCopyExport != null
|
|
1237
|
+
&& snapshot.DiagnosticsEnabled
|
|
1238
|
+
&& FilterEntries(snapshot.Entries, filterText).Count > 0
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
private static bool IsSceneComponent(MessagingComponent component)
|
|
1243
|
+
{
|
|
1244
|
+
return component != null
|
|
1245
|
+
&& component.gameObject != null
|
|
1246
|
+
&& component.gameObject.scene.IsValid()
|
|
1247
|
+
&& !EditorSceneManager.IsPreviewSceneObject(component.gameObject)
|
|
1248
|
+
&& !EditorUtility.IsPersistent(component);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
private static MessagingComponent[] FindMessagingComponentsInLoadedScenes()
|
|
1252
|
+
{
|
|
1253
|
+
#if UNITY_2023_1_OR_NEWER
|
|
1254
|
+
// FindObjectsByType's two-argument (FindObjectsInactive, FindObjectsSortMode)
|
|
1255
|
+
// overload exists across all 2023.1+ editors, including every 6000.x. The
|
|
1256
|
+
// one-argument FindObjectsByType(FindObjectsInactive) convenience overload only
|
|
1257
|
+
// exists on some 6000.x patch releases (e.g. 6000.4, not 6000.3), so always pass
|
|
1258
|
+
// both arguments to stay portable across the whole 6000.x range.
|
|
1259
|
+
return UnityEngine.Object.FindObjectsByType<MessagingComponent>(
|
|
1260
|
+
FindObjectsInactive.Include,
|
|
1261
|
+
FindObjectsSortMode.None
|
|
1262
|
+
);
|
|
1263
|
+
#else
|
|
1264
|
+
return UnityEngine.Object.FindObjectsOfType<MessagingComponent>(includeInactive: true);
|
|
1265
|
+
#endif
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
private static ComponentMonitorEntry CreateComponentMonitorEntry(
|
|
1269
|
+
MessagingComponent component
|
|
1270
|
+
)
|
|
1271
|
+
{
|
|
1272
|
+
MessagingComponentInspectorState state = MessagingComponentEditorHarness.Capture(
|
|
1273
|
+
component,
|
|
1274
|
+
resolveSerializedProviderBus: false
|
|
1275
|
+
);
|
|
1276
|
+
int listenerCount = state.Listeners.Count;
|
|
1277
|
+
int enabledListenerCount = state.Listeners.Count(listener => listener.TokenEnabled);
|
|
1278
|
+
int diagnosticsListenerCount = state.Listeners.Count(listener =>
|
|
1279
|
+
listener.DiagnosticsEnabled
|
|
1280
|
+
);
|
|
1281
|
+
int registrationCount = state.Listeners.Sum(listener => listener.Registrations.Count);
|
|
1282
|
+
int callCount = state.Listeners.Sum(listener =>
|
|
1283
|
+
listener.Registrations.Sum(registration => registration.CallCount)
|
|
1284
|
+
);
|
|
1285
|
+
int localEmissionCount = state.Listeners.Sum(listener =>
|
|
1286
|
+
listener.EmissionHistory.Count
|
|
1287
|
+
);
|
|
1288
|
+
|
|
1289
|
+
return new ComponentMonitorEntry(
|
|
1290
|
+
GetHierarchyPath(component.transform),
|
|
1291
|
+
component.GetType().Name,
|
|
1292
|
+
component.gameObject.activeInHierarchy,
|
|
1293
|
+
listenerCount,
|
|
1294
|
+
enabledListenerCount,
|
|
1295
|
+
diagnosticsListenerCount,
|
|
1296
|
+
registrationCount,
|
|
1297
|
+
callCount,
|
|
1298
|
+
localEmissionCount,
|
|
1299
|
+
CreateProviderStatusText(state.ProviderDiagnostics),
|
|
1300
|
+
CreateProviderWarningText(state.ProviderDiagnostics)
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
private static ComponentMonitorEntry CreateFailedComponentMonitorEntry(
|
|
1305
|
+
MessagingComponent component,
|
|
1306
|
+
Exception exception
|
|
1307
|
+
)
|
|
1308
|
+
{
|
|
1309
|
+
return new ComponentMonitorEntry(
|
|
1310
|
+
GetHierarchyPath(component != null ? component.transform : null),
|
|
1311
|
+
component != null ? component.GetType().Name : "<missing>",
|
|
1312
|
+
component != null && component.gameObject.activeInHierarchy,
|
|
1313
|
+
listenerCount: 0,
|
|
1314
|
+
enabledListenerCount: 0,
|
|
1315
|
+
diagnosticsListenerCount: 0,
|
|
1316
|
+
registrationCount: 0,
|
|
1317
|
+
callCount: 0,
|
|
1318
|
+
localEmissionCount: 0,
|
|
1319
|
+
providerStatusText: "Provider: unavailable",
|
|
1320
|
+
warningText: $"Diagnostics capture failed: {exception}"
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
private static string CreateProviderStatusText(ProviderDiagnosticsView providerDiagnostics)
|
|
1325
|
+
{
|
|
1326
|
+
List<string> states = new();
|
|
1327
|
+
if (providerDiagnostics.HasMessageBusOverride)
|
|
1328
|
+
{
|
|
1329
|
+
states.Add("bus override");
|
|
1330
|
+
}
|
|
1331
|
+
if (providerDiagnostics.HasRuntimeProvider)
|
|
1332
|
+
{
|
|
1333
|
+
states.Add("runtime provider");
|
|
1334
|
+
}
|
|
1335
|
+
if (providerDiagnostics.HasSerializedProvider)
|
|
1336
|
+
{
|
|
1337
|
+
states.Add("serialized provider");
|
|
1338
|
+
}
|
|
1339
|
+
if (states.Count == 0)
|
|
1340
|
+
{
|
|
1341
|
+
states.Add("global bus");
|
|
1342
|
+
}
|
|
1343
|
+
if (providerDiagnostics.AutoConfigureSerializedProviderOnAwake)
|
|
1344
|
+
{
|
|
1345
|
+
states.Add("auto-configure");
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
return "Provider: " + string.Join(", ", states);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
private static string CreateProviderWarningText(ProviderDiagnosticsView providerDiagnostics)
|
|
1352
|
+
{
|
|
1353
|
+
List<string> warnings = new();
|
|
1354
|
+
if (providerDiagnostics.SerializedProviderMissingWarning)
|
|
1355
|
+
{
|
|
1356
|
+
warnings.Add("Serialized provider missing");
|
|
1357
|
+
}
|
|
1358
|
+
if (providerDiagnostics.SerializedProviderNullBusWarning)
|
|
1359
|
+
{
|
|
1360
|
+
warnings.Add("Serialized provider resolves no bus");
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
return string.Join("; ", warnings);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
private static string GetHierarchyPath(Transform transform)
|
|
1367
|
+
{
|
|
1368
|
+
if (transform == null)
|
|
1369
|
+
{
|
|
1370
|
+
return "<missing>";
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
Stack<string> segments = new();
|
|
1374
|
+
Transform current = transform;
|
|
1375
|
+
while (current != null)
|
|
1376
|
+
{
|
|
1377
|
+
segments.Push(current.name);
|
|
1378
|
+
current = current.parent;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
return string.Join("/", segments);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
private static void AddEmptyState(VisualElement root, string title, string body)
|
|
1385
|
+
{
|
|
1386
|
+
VisualElement empty = DxMessagingEditorTheme.CreateEmptyState(
|
|
1387
|
+
title,
|
|
1388
|
+
body,
|
|
1389
|
+
bodyName: EmptyStateLabelName,
|
|
1390
|
+
titleName: EmptyStateTitleLabelName
|
|
1391
|
+
);
|
|
1392
|
+
empty.style.marginTop = 8;
|
|
1393
|
+
root.Add(empty);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
private static VisualElement CreateRow(
|
|
1397
|
+
MessageMonitorEntry entry,
|
|
1398
|
+
int entryIndex,
|
|
1399
|
+
bool selected,
|
|
1400
|
+
Action<int> onSelectedEntryChanged
|
|
1401
|
+
)
|
|
1402
|
+
{
|
|
1403
|
+
VisualElement row = new();
|
|
1404
|
+
row.AddToClassList(RowClassName);
|
|
1405
|
+
row.AddToClassList(DxMessagingEditorTheme.CardClassName);
|
|
1406
|
+
Color routeColor = DxMessagingEditorPalette.RouteKindColor(entry.RouteKind);
|
|
1407
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(row, routeColor);
|
|
1408
|
+
row.style.marginBottom = 8;
|
|
1409
|
+
row.style.paddingTop = 8;
|
|
1410
|
+
row.style.paddingRight = 8;
|
|
1411
|
+
row.style.paddingBottom = 8;
|
|
1412
|
+
row.style.paddingLeft = 10;
|
|
1413
|
+
if (selected)
|
|
1414
|
+
{
|
|
1415
|
+
row.style.backgroundColor = DxMessagingEditorPalette.SelectedWash;
|
|
1416
|
+
}
|
|
1417
|
+
if (onSelectedEntryChanged != null)
|
|
1418
|
+
{
|
|
1419
|
+
row.RegisterCallback<ClickEvent>(_ => onSelectedEntryChanged.Invoke(entryIndex));
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
Label type = new(entry.MessageTypeName) { name = MessageTypeLabelName };
|
|
1423
|
+
type.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
1424
|
+
row.Add(type);
|
|
1425
|
+
|
|
1426
|
+
string routeKind = DxMessagingEditorPalette.NormalizeRouteKind(entry.RouteKind);
|
|
1427
|
+
if (!string.IsNullOrWhiteSpace(routeKind))
|
|
1428
|
+
{
|
|
1429
|
+
Label kind = new(routeKind) { name = RouteKindLabelName };
|
|
1430
|
+
DxMessagingEditorTheme.AddRouteKindTypeBadgeClasses(kind, routeKind);
|
|
1431
|
+
kind.style.marginTop = 2;
|
|
1432
|
+
kind.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
1433
|
+
row.Add(kind);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
Label context = new(entry.ContextText) { name = ContextLabelName };
|
|
1437
|
+
context.style.marginTop = 2;
|
|
1438
|
+
row.Add(context);
|
|
1439
|
+
|
|
1440
|
+
if (!string.IsNullOrWhiteSpace(entry.StackTrace))
|
|
1441
|
+
{
|
|
1442
|
+
Label stack = new(entry.StackTrace) { name = StackTraceLabelName };
|
|
1443
|
+
stack.style.marginTop = 6;
|
|
1444
|
+
stack.style.whiteSpace = WhiteSpace.Normal;
|
|
1445
|
+
row.Add(stack);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
return row;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
private static VisualElement CreateComponentPanel(
|
|
1452
|
+
IReadOnlyList<ComponentMonitorEntry> componentEntries
|
|
1453
|
+
)
|
|
1454
|
+
{
|
|
1455
|
+
VisualElement panel = new() { name = ComponentPanelName };
|
|
1456
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(panel, DxMessagingEditorPalette.BorderPanel);
|
|
1457
|
+
panel.style.marginTop = 10;
|
|
1458
|
+
panel.style.paddingTop = 8;
|
|
1459
|
+
panel.style.paddingRight = 8;
|
|
1460
|
+
panel.style.paddingBottom = 8;
|
|
1461
|
+
panel.style.paddingLeft = 8;
|
|
1462
|
+
|
|
1463
|
+
Label title = new($"Component Diagnostics ({componentEntries.Count})");
|
|
1464
|
+
title.AddToClassList(DxMessagingEditorTheme.CardLabelClassName);
|
|
1465
|
+
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
1466
|
+
panel.Add(title);
|
|
1467
|
+
|
|
1468
|
+
if (componentEntries.Count == 0)
|
|
1469
|
+
{
|
|
1470
|
+
Label empty = new("No MessagingComponent instances are loaded in open scenes.")
|
|
1471
|
+
{
|
|
1472
|
+
name = ComponentEmptyStateLabelName,
|
|
1473
|
+
};
|
|
1474
|
+
empty.AddToClassList(DxMessagingEditorTheme.EmptyBodyClassName);
|
|
1475
|
+
empty.style.whiteSpace = WhiteSpace.Normal;
|
|
1476
|
+
empty.style.marginTop = 6;
|
|
1477
|
+
panel.Add(empty);
|
|
1478
|
+
return panel;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
ScrollView componentScroll = new(ScrollViewMode.Vertical)
|
|
1482
|
+
{
|
|
1483
|
+
name = ComponentScrollViewName,
|
|
1484
|
+
};
|
|
1485
|
+
componentScroll.style.maxHeight = 180;
|
|
1486
|
+
componentScroll.style.marginTop = 2;
|
|
1487
|
+
panel.Add(componentScroll);
|
|
1488
|
+
|
|
1489
|
+
foreach (ComponentMonitorEntry componentEntry in componentEntries)
|
|
1490
|
+
{
|
|
1491
|
+
componentScroll.Add(CreateComponentRow(componentEntry));
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
return panel;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
private static VisualElement CreateComponentRow(ComponentMonitorEntry componentEntry)
|
|
1498
|
+
{
|
|
1499
|
+
VisualElement row = new();
|
|
1500
|
+
row.AddToClassList(ComponentRowClassName);
|
|
1501
|
+
row.AddToClassList(DxMessagingEditorTheme.CardClassName);
|
|
1502
|
+
DxMessagingEditorTheme.ApplyCompleteBorder(row, DxMessagingEditorPalette.Amber);
|
|
1503
|
+
row.style.marginTop = 8;
|
|
1504
|
+
row.style.paddingTop = 8;
|
|
1505
|
+
row.style.paddingRight = 8;
|
|
1506
|
+
row.style.paddingBottom = 8;
|
|
1507
|
+
row.style.paddingLeft = 10;
|
|
1508
|
+
|
|
1509
|
+
string activeText = componentEntry.ActiveInHierarchy ? "active" : "inactive";
|
|
1510
|
+
Label name = new(
|
|
1511
|
+
$"{componentEntry.HierarchyPath} ({componentEntry.ComponentTypeName}, {activeText})"
|
|
1512
|
+
)
|
|
1513
|
+
{
|
|
1514
|
+
name = ComponentNameLabelName,
|
|
1515
|
+
};
|
|
1516
|
+
name.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
1517
|
+
row.Add(name);
|
|
1518
|
+
|
|
1519
|
+
Label summary = new(
|
|
1520
|
+
$"Listeners: {componentEntry.ListenerCount} ({componentEntry.EnabledListenerCount} enabled, {componentEntry.DiagnosticsListenerCount} diagnostics) | Registrations: {componentEntry.RegistrationCount} | Calls: {componentEntry.CallCount} | Local messages: {componentEntry.LocalEmissionCount}"
|
|
1521
|
+
)
|
|
1522
|
+
{
|
|
1523
|
+
name = ComponentSummaryLabelName,
|
|
1524
|
+
};
|
|
1525
|
+
summary.style.marginTop = 2;
|
|
1526
|
+
summary.style.whiteSpace = WhiteSpace.Normal;
|
|
1527
|
+
row.Add(summary);
|
|
1528
|
+
|
|
1529
|
+
Label provider = new(componentEntry.ProviderStatusText)
|
|
1530
|
+
{
|
|
1531
|
+
name = ComponentProviderLabelName,
|
|
1532
|
+
};
|
|
1533
|
+
provider.style.marginTop = 2;
|
|
1534
|
+
provider.style.whiteSpace = WhiteSpace.Normal;
|
|
1535
|
+
row.Add(provider);
|
|
1536
|
+
|
|
1537
|
+
if (!string.IsNullOrWhiteSpace(componentEntry.WarningText))
|
|
1538
|
+
{
|
|
1539
|
+
Label warning = new(componentEntry.WarningText)
|
|
1540
|
+
{
|
|
1541
|
+
name = ComponentWarningLabelName,
|
|
1542
|
+
};
|
|
1543
|
+
warning.AddToClassList(DxMessagingEditorTheme.WarningClassName);
|
|
1544
|
+
warning.style.marginTop = 4;
|
|
1545
|
+
warning.style.whiteSpace = WhiteSpace.Normal;
|
|
1546
|
+
row.Add(warning);
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
return row;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
private static VisualElement CreateDetailsPane(MessageMonitorEntry entry)
|
|
1553
|
+
{
|
|
1554
|
+
VisualElement details = new() { name = DetailsPaneName };
|
|
1555
|
+
details.AddToClassList(DxMessagingEditorTheme.CardClassName);
|
|
1556
|
+
details.style.borderTopWidth = 1;
|
|
1557
|
+
details.style.borderTopColor = DxMessagingEditorPalette.BorderPanel;
|
|
1558
|
+
details.style.marginTop = 8;
|
|
1559
|
+
details.style.paddingTop = 8;
|
|
1560
|
+
|
|
1561
|
+
Label title = new("Details");
|
|
1562
|
+
title.AddToClassList(DxMessagingEditorTheme.CardLabelClassName);
|
|
1563
|
+
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
|
1564
|
+
details.Add(title);
|
|
1565
|
+
|
|
1566
|
+
Label type = new($"Message: {entry.MessageTypeName}") { name = DetailsTypeLabelName };
|
|
1567
|
+
type.style.marginTop = 4;
|
|
1568
|
+
details.Add(type);
|
|
1569
|
+
|
|
1570
|
+
Label context = new(entry.ContextText) { name = DetailsContextLabelName };
|
|
1571
|
+
context.style.marginTop = 2;
|
|
1572
|
+
details.Add(context);
|
|
1573
|
+
|
|
1574
|
+
string stackText = string.IsNullOrWhiteSpace(entry.StackTrace)
|
|
1575
|
+
? "Stack trace: not captured"
|
|
1576
|
+
: entry.StackTrace;
|
|
1577
|
+
Label stack = new(stackText) { name = DetailsStackTraceLabelName };
|
|
1578
|
+
stack.style.marginTop = 6;
|
|
1579
|
+
stack.style.whiteSpace = WhiteSpace.Normal;
|
|
1580
|
+
details.Add(stack);
|
|
1581
|
+
|
|
1582
|
+
return details;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
private static int ClampSelectedIndex(int selectedEntryIndex, int entryCount)
|
|
1586
|
+
{
|
|
1587
|
+
if (entryCount <= 0)
|
|
1588
|
+
{
|
|
1589
|
+
return 0;
|
|
1590
|
+
}
|
|
1591
|
+
if (selectedEntryIndex < 0)
|
|
1592
|
+
{
|
|
1593
|
+
return 0;
|
|
1594
|
+
}
|
|
1595
|
+
return selectedEntryIndex >= entryCount ? entryCount - 1 : selectedEntryIndex;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
private static void AppendJsonProperty(
|
|
1599
|
+
StringBuilder builder,
|
|
1600
|
+
string name,
|
|
1601
|
+
string value,
|
|
1602
|
+
bool trailingComma
|
|
1603
|
+
)
|
|
1604
|
+
{
|
|
1605
|
+
builder
|
|
1606
|
+
.Append(" \"")
|
|
1607
|
+
.Append(name)
|
|
1608
|
+
.Append("\": \"")
|
|
1609
|
+
.Append(EscapeJson(value))
|
|
1610
|
+
.Append("\"");
|
|
1611
|
+
if (trailingComma)
|
|
1612
|
+
{
|
|
1613
|
+
builder.Append(",");
|
|
1614
|
+
}
|
|
1615
|
+
builder.AppendLine();
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
private static string EscapeJson(string value)
|
|
1619
|
+
{
|
|
1620
|
+
if (string.IsNullOrEmpty(value))
|
|
1621
|
+
{
|
|
1622
|
+
return string.Empty;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
StringBuilder builder = new(value.Length + 8);
|
|
1626
|
+
foreach (char c in value)
|
|
1627
|
+
{
|
|
1628
|
+
switch (c)
|
|
1629
|
+
{
|
|
1630
|
+
case '\\':
|
|
1631
|
+
builder.Append("\\\\");
|
|
1632
|
+
break;
|
|
1633
|
+
case '"':
|
|
1634
|
+
builder.Append("\\\"");
|
|
1635
|
+
break;
|
|
1636
|
+
case '\n':
|
|
1637
|
+
builder.Append("\\n");
|
|
1638
|
+
break;
|
|
1639
|
+
case '\r':
|
|
1640
|
+
builder.Append("\\r");
|
|
1641
|
+
break;
|
|
1642
|
+
case '\t':
|
|
1643
|
+
builder.Append("\\t");
|
|
1644
|
+
break;
|
|
1645
|
+
default:
|
|
1646
|
+
if (char.IsControl(c))
|
|
1647
|
+
{
|
|
1648
|
+
builder.Append("\\u").Append(((int)c).ToString("x4"));
|
|
1649
|
+
}
|
|
1650
|
+
else
|
|
1651
|
+
{
|
|
1652
|
+
builder.Append(c);
|
|
1653
|
+
}
|
|
1654
|
+
break;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return builder.ToString();
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
internal readonly struct MessageMonitorViewState
|
|
1662
|
+
{
|
|
1663
|
+
internal static MessageMonitorViewState Default { get; } = new();
|
|
1664
|
+
|
|
1665
|
+
internal MessageMonitorViewState(string filterText = "", int selectedEntryIndex = 0)
|
|
1666
|
+
{
|
|
1667
|
+
FilterText = filterText ?? string.Empty;
|
|
1668
|
+
SelectedEntryIndex = selectedEntryIndex;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
internal string FilterText { get; }
|
|
1672
|
+
|
|
1673
|
+
internal int SelectedEntryIndex { get; }
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
internal readonly struct ComponentMonitorEntry
|
|
1677
|
+
{
|
|
1678
|
+
internal ComponentMonitorEntry(
|
|
1679
|
+
string hierarchyPath,
|
|
1680
|
+
string componentTypeName,
|
|
1681
|
+
bool activeInHierarchy,
|
|
1682
|
+
int listenerCount,
|
|
1683
|
+
int enabledListenerCount,
|
|
1684
|
+
int diagnosticsListenerCount,
|
|
1685
|
+
int registrationCount,
|
|
1686
|
+
int callCount,
|
|
1687
|
+
int localEmissionCount,
|
|
1688
|
+
string providerStatusText,
|
|
1689
|
+
string warningText
|
|
1690
|
+
)
|
|
1691
|
+
{
|
|
1692
|
+
HierarchyPath = hierarchyPath ?? string.Empty;
|
|
1693
|
+
ComponentTypeName = componentTypeName ?? string.Empty;
|
|
1694
|
+
ActiveInHierarchy = activeInHierarchy;
|
|
1695
|
+
ListenerCount = listenerCount;
|
|
1696
|
+
EnabledListenerCount = enabledListenerCount;
|
|
1697
|
+
DiagnosticsListenerCount = diagnosticsListenerCount;
|
|
1698
|
+
RegistrationCount = registrationCount;
|
|
1699
|
+
CallCount = callCount;
|
|
1700
|
+
LocalEmissionCount = localEmissionCount;
|
|
1701
|
+
ProviderStatusText = providerStatusText ?? string.Empty;
|
|
1702
|
+
WarningText = warningText ?? string.Empty;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
internal string HierarchyPath { get; }
|
|
1706
|
+
|
|
1707
|
+
internal string ComponentTypeName { get; }
|
|
1708
|
+
|
|
1709
|
+
internal bool ActiveInHierarchy { get; }
|
|
1710
|
+
|
|
1711
|
+
internal int ListenerCount { get; }
|
|
1712
|
+
|
|
1713
|
+
internal int EnabledListenerCount { get; }
|
|
1714
|
+
|
|
1715
|
+
internal int DiagnosticsListenerCount { get; }
|
|
1716
|
+
|
|
1717
|
+
internal int RegistrationCount { get; }
|
|
1718
|
+
|
|
1719
|
+
internal int CallCount { get; }
|
|
1720
|
+
|
|
1721
|
+
internal int LocalEmissionCount { get; }
|
|
1722
|
+
|
|
1723
|
+
internal string ProviderStatusText { get; }
|
|
1724
|
+
|
|
1725
|
+
internal string WarningText { get; }
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
internal readonly struct MessageMonitorTypeLane
|
|
1729
|
+
{
|
|
1730
|
+
internal MessageMonitorTypeLane(
|
|
1731
|
+
string messageTypeName,
|
|
1732
|
+
int entryCount,
|
|
1733
|
+
IReadOnlyList<string> contexts
|
|
1734
|
+
)
|
|
1735
|
+
{
|
|
1736
|
+
MessageTypeName = string.IsNullOrWhiteSpace(messageTypeName)
|
|
1737
|
+
? "<unknown>"
|
|
1738
|
+
: messageTypeName.Trim();
|
|
1739
|
+
EntryCount = entryCount;
|
|
1740
|
+
Contexts = contexts ?? Array.Empty<string>();
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
internal string MessageTypeName { get; }
|
|
1744
|
+
|
|
1745
|
+
internal int EntryCount { get; }
|
|
1746
|
+
|
|
1747
|
+
internal IReadOnlyList<string> Contexts { get; }
|
|
1748
|
+
|
|
1749
|
+
internal int ContextCount => Contexts.Count;
|
|
1750
|
+
|
|
1751
|
+
internal string ContextsText => Contexts.Count == 0 ? "none" : string.Join(", ", Contexts);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
internal readonly struct MessageMonitorContextLane
|
|
1755
|
+
{
|
|
1756
|
+
internal MessageMonitorContextLane(
|
|
1757
|
+
string contextText,
|
|
1758
|
+
int entryCount,
|
|
1759
|
+
IReadOnlyList<string> messageTypes
|
|
1760
|
+
)
|
|
1761
|
+
{
|
|
1762
|
+
ContextText = string.IsNullOrWhiteSpace(contextText)
|
|
1763
|
+
? "Context: none"
|
|
1764
|
+
: contextText.Trim();
|
|
1765
|
+
EntryCount = entryCount;
|
|
1766
|
+
MessageTypes = messageTypes ?? Array.Empty<string>();
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
internal string ContextText { get; }
|
|
1770
|
+
|
|
1771
|
+
internal int EntryCount { get; }
|
|
1772
|
+
|
|
1773
|
+
internal IReadOnlyList<string> MessageTypes { get; }
|
|
1774
|
+
|
|
1775
|
+
internal int MessageTypeCount => MessageTypes.Count;
|
|
1776
|
+
|
|
1777
|
+
internal string MessageTypesText =>
|
|
1778
|
+
MessageTypes.Count == 0 ? "none" : string.Join(", ", MessageTypes);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
internal readonly struct MessageMonitorSnapshot
|
|
1782
|
+
{
|
|
1783
|
+
internal MessageMonitorSnapshot(
|
|
1784
|
+
bool diagnosticsEnabled,
|
|
1785
|
+
int capacity,
|
|
1786
|
+
IReadOnlyList<MessageMonitorEntry> entries,
|
|
1787
|
+
bool available = true,
|
|
1788
|
+
string unavailableReason = ""
|
|
1789
|
+
)
|
|
1790
|
+
{
|
|
1791
|
+
DiagnosticsEnabled = diagnosticsEnabled;
|
|
1792
|
+
Capacity = capacity;
|
|
1793
|
+
Entries = entries ?? throw new ArgumentNullException(nameof(entries));
|
|
1794
|
+
Available = available;
|
|
1795
|
+
UnavailableReason = unavailableReason ?? string.Empty;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
internal bool Available { get; }
|
|
1799
|
+
|
|
1800
|
+
internal bool DiagnosticsEnabled { get; }
|
|
1801
|
+
|
|
1802
|
+
internal int Capacity { get; }
|
|
1803
|
+
|
|
1804
|
+
internal IReadOnlyList<MessageMonitorEntry> Entries { get; }
|
|
1805
|
+
|
|
1806
|
+
internal string UnavailableReason { get; }
|
|
1807
|
+
|
|
1808
|
+
internal static MessageMonitorSnapshot Unavailable(string reason)
|
|
1809
|
+
{
|
|
1810
|
+
return new MessageMonitorSnapshot(
|
|
1811
|
+
diagnosticsEnabled: false,
|
|
1812
|
+
capacity: 0,
|
|
1813
|
+
entries: Array.Empty<MessageMonitorEntry>(),
|
|
1814
|
+
available: false,
|
|
1815
|
+
unavailableReason: reason
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
internal readonly struct MessageMonitorEntry
|
|
1821
|
+
{
|
|
1822
|
+
private const string EmptyContextText = "Context: none";
|
|
1823
|
+
|
|
1824
|
+
internal MessageMonitorEntry(
|
|
1825
|
+
string messageTypeName,
|
|
1826
|
+
string contextText,
|
|
1827
|
+
string stackTrace,
|
|
1828
|
+
string messageTypeIdentity = null,
|
|
1829
|
+
string messageTypeDisplayPath = null,
|
|
1830
|
+
string routeKind = null
|
|
1831
|
+
)
|
|
1832
|
+
{
|
|
1833
|
+
MessageTypeName = messageTypeName;
|
|
1834
|
+
MessageTypeIdentity = string.IsNullOrWhiteSpace(messageTypeIdentity)
|
|
1835
|
+
? messageTypeName
|
|
1836
|
+
: messageTypeIdentity;
|
|
1837
|
+
MessageTypeDisplayPath = string.IsNullOrWhiteSpace(messageTypeDisplayPath)
|
|
1838
|
+
? messageTypeName
|
|
1839
|
+
: messageTypeDisplayPath;
|
|
1840
|
+
ContextText = contextText;
|
|
1841
|
+
StackTrace = stackTrace;
|
|
1842
|
+
RouteKind = routeKind ?? string.Empty;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
internal string MessageTypeName { get; }
|
|
1846
|
+
|
|
1847
|
+
internal string MessageTypeIdentity { get; }
|
|
1848
|
+
|
|
1849
|
+
internal string MessageTypeDisplayPath { get; }
|
|
1850
|
+
|
|
1851
|
+
internal string ContextText { get; }
|
|
1852
|
+
|
|
1853
|
+
internal string StackTrace { get; }
|
|
1854
|
+
|
|
1855
|
+
internal string RouteKind { get; }
|
|
1856
|
+
|
|
1857
|
+
internal static MessageMonitorEntry FromEmission(MessageEmissionData emission)
|
|
1858
|
+
{
|
|
1859
|
+
Type messageType = emission.message?.MessageType;
|
|
1860
|
+
string typeName = messageType == null ? "<unknown>" : messageType.Name;
|
|
1861
|
+
string typeIdentity = CreateMessageTypeIdentity(messageType, typeName);
|
|
1862
|
+
string typeDisplayPath = CreateMessageTypeDisplayPath(messageType, typeName);
|
|
1863
|
+
string contextText = FormatContext(emission.context);
|
|
1864
|
+
return new MessageMonitorEntry(
|
|
1865
|
+
typeName,
|
|
1866
|
+
contextText,
|
|
1867
|
+
emission.stackTrace ?? string.Empty,
|
|
1868
|
+
typeIdentity,
|
|
1869
|
+
typeDisplayPath,
|
|
1870
|
+
CreateRouteKind(messageType)
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
private static string CreateRouteKind(Type messageType)
|
|
1875
|
+
{
|
|
1876
|
+
if (messageType == null)
|
|
1877
|
+
{
|
|
1878
|
+
return string.Empty;
|
|
1879
|
+
}
|
|
1880
|
+
if (typeof(IUntargetedMessage).IsAssignableFrom(messageType))
|
|
1881
|
+
{
|
|
1882
|
+
return DxMessagingEditorPalette.UntargetedKind;
|
|
1883
|
+
}
|
|
1884
|
+
if (typeof(ITargetedMessage).IsAssignableFrom(messageType))
|
|
1885
|
+
{
|
|
1886
|
+
return DxMessagingEditorPalette.TargetedKind;
|
|
1887
|
+
}
|
|
1888
|
+
if (typeof(IBroadcastMessage).IsAssignableFrom(messageType))
|
|
1889
|
+
{
|
|
1890
|
+
return DxMessagingEditorPalette.BroadcastKind;
|
|
1891
|
+
}
|
|
1892
|
+
return string.Empty;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
internal bool Matches(string filterText)
|
|
1896
|
+
{
|
|
1897
|
+
if (string.IsNullOrWhiteSpace(filterText))
|
|
1898
|
+
{
|
|
1899
|
+
return true;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
if (
|
|
1903
|
+
!MessageMonitorFilterQuery.TryCreateTerms(
|
|
1904
|
+
filterText,
|
|
1905
|
+
out MessageMonitorFilterTerm[] terms
|
|
1906
|
+
)
|
|
1907
|
+
)
|
|
1908
|
+
{
|
|
1909
|
+
return ContainsAnyField(filterText);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
return terms.All(MatchesFilterTerm);
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
private bool MatchesFilterTerm(MessageMonitorFilterTerm term)
|
|
1916
|
+
{
|
|
1917
|
+
if (string.IsNullOrWhiteSpace(term.Text))
|
|
1918
|
+
{
|
|
1919
|
+
return true;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
switch (term.Facet)
|
|
1923
|
+
{
|
|
1924
|
+
case MessageMonitorFilterFacet.MessageType:
|
|
1925
|
+
return Contains(MessageTypeName, term.Text)
|
|
1926
|
+
|| Contains(MessageTypeDisplayPath, term.Text);
|
|
1927
|
+
case MessageMonitorFilterFacet.Context:
|
|
1928
|
+
if (term.Exact)
|
|
1929
|
+
{
|
|
1930
|
+
return string.Equals(
|
|
1931
|
+
ContextText,
|
|
1932
|
+
term.Text,
|
|
1933
|
+
StringComparison.OrdinalIgnoreCase
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
return Contains(ContextText, term.Text);
|
|
1938
|
+
case MessageMonitorFilterFacet.Stack:
|
|
1939
|
+
return Contains(StackTrace, term.Text);
|
|
1940
|
+
default:
|
|
1941
|
+
return ContainsAnyField(term.Text);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
private bool ContainsAnyField(string filterText)
|
|
1946
|
+
{
|
|
1947
|
+
return Contains(MessageTypeName, filterText)
|
|
1948
|
+
|| Contains(MessageTypeDisplayPath, filterText)
|
|
1949
|
+
|| Contains(ContextText, filterText)
|
|
1950
|
+
|| Contains(StackTrace, filterText);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
private static string CreateMessageTypeIdentity(Type messageType, string fallback)
|
|
1954
|
+
{
|
|
1955
|
+
if (messageType == null)
|
|
1956
|
+
{
|
|
1957
|
+
return fallback;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
return messageType.AssemblyQualifiedName ?? messageType.FullName ?? fallback;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
private static string CreateMessageTypeDisplayPath(Type messageType, string fallback)
|
|
1964
|
+
{
|
|
1965
|
+
if (messageType == null)
|
|
1966
|
+
{
|
|
1967
|
+
return fallback;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
return (messageType.FullName ?? fallback).Replace('+', '.');
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
private static bool Contains(string value, string filterText)
|
|
1974
|
+
{
|
|
1975
|
+
return !string.IsNullOrEmpty(value)
|
|
1976
|
+
&& value.IndexOf(filterText, StringComparison.OrdinalIgnoreCase) >= 0;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
private static string FormatContext(InstanceId? context)
|
|
1980
|
+
{
|
|
1981
|
+
if (!context.HasValue)
|
|
1982
|
+
{
|
|
1983
|
+
return EmptyContextText;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
InstanceId instanceId = context.Value;
|
|
1987
|
+
#if UNITY_2021_3_OR_NEWER
|
|
1988
|
+
UnityEngine.Object unityObject = instanceId.Object;
|
|
1989
|
+
if (unityObject != null)
|
|
1990
|
+
{
|
|
1991
|
+
return $"Context: {unityObject.name} ({instanceId.Id})";
|
|
1992
|
+
}
|
|
1993
|
+
#endif
|
|
1994
|
+
return $"Context: {instanceId.Id}";
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
internal enum MessageMonitorFilterFacet
|
|
1999
|
+
{
|
|
2000
|
+
Any,
|
|
2001
|
+
MessageType,
|
|
2002
|
+
Context,
|
|
2003
|
+
Stack,
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
internal static class MessageMonitorFilterQuery
|
|
2007
|
+
{
|
|
2008
|
+
internal static bool TryCreateTerms(string filterText, out MessageMonitorFilterTerm[] terms)
|
|
2009
|
+
{
|
|
2010
|
+
string[] tokens = SplitFilterTokens(filterText);
|
|
2011
|
+
if (tokens.Length == 0)
|
|
2012
|
+
{
|
|
2013
|
+
terms = Array.Empty<MessageMonitorFilterTerm>();
|
|
2014
|
+
return false;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
List<MessageMonitorFilterTerm> parsedTerms = new(tokens.Length);
|
|
2018
|
+
foreach (string token in tokens)
|
|
2019
|
+
{
|
|
2020
|
+
if (!TryCreateFilterTerm(token, out MessageMonitorFilterTerm term, out _))
|
|
2021
|
+
{
|
|
2022
|
+
terms = Array.Empty<MessageMonitorFilterTerm>();
|
|
2023
|
+
return false;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
parsedTerms.Add(term);
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
terms = parsedTerms.ToArray();
|
|
2030
|
+
return true;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
internal static bool TryCreateDisplayTokens(string filterText, out string[] displayTokens)
|
|
2034
|
+
{
|
|
2035
|
+
string[] tokens = SplitFilterTokens(filterText);
|
|
2036
|
+
if (tokens.Length == 0)
|
|
2037
|
+
{
|
|
2038
|
+
displayTokens = Array.Empty<string>();
|
|
2039
|
+
return false;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
List<string> parsedTokens = new(tokens.Length);
|
|
2043
|
+
foreach (string token in tokens)
|
|
2044
|
+
{
|
|
2045
|
+
if (!TryCreateFilterTerm(token, out _, out string displayToken))
|
|
2046
|
+
{
|
|
2047
|
+
displayTokens = Array.Empty<string>();
|
|
2048
|
+
return false;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
parsedTokens.Add(displayToken);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
displayTokens = parsedTokens.ToArray();
|
|
2055
|
+
return true;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
private static string[] SplitFilterTokens(string filterText)
|
|
2059
|
+
{
|
|
2060
|
+
string source = filterText ?? string.Empty;
|
|
2061
|
+
if (source.Length == 0)
|
|
2062
|
+
{
|
|
2063
|
+
return Array.Empty<string>();
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
List<string> tokens = new();
|
|
2067
|
+
StringBuilder current = new();
|
|
2068
|
+
bool quoted = false;
|
|
2069
|
+
bool escaped = false;
|
|
2070
|
+
|
|
2071
|
+
foreach (char character in source)
|
|
2072
|
+
{
|
|
2073
|
+
if (escaped)
|
|
2074
|
+
{
|
|
2075
|
+
current.Append(character);
|
|
2076
|
+
escaped = false;
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
if (quoted && character == '\\')
|
|
2081
|
+
{
|
|
2082
|
+
current.Append(character);
|
|
2083
|
+
escaped = true;
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
if (character == '"')
|
|
2088
|
+
{
|
|
2089
|
+
quoted = !quoted;
|
|
2090
|
+
current.Append(character);
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
if (!quoted && char.IsWhiteSpace(character))
|
|
2095
|
+
{
|
|
2096
|
+
AddCurrentToken(tokens, current);
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
current.Append(character);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
AddCurrentToken(tokens, current);
|
|
2104
|
+
return tokens.ToArray();
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
private static void AddCurrentToken(List<string> tokens, StringBuilder current)
|
|
2108
|
+
{
|
|
2109
|
+
if (current.Length == 0)
|
|
2110
|
+
{
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
tokens.Add(current.ToString());
|
|
2115
|
+
current.Clear();
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
private static bool TryCreateFilterTerm(
|
|
2119
|
+
string token,
|
|
2120
|
+
out MessageMonitorFilterTerm term,
|
|
2121
|
+
out string displayToken
|
|
2122
|
+
)
|
|
2123
|
+
{
|
|
2124
|
+
int separatorIndex = token.IndexOf(':');
|
|
2125
|
+
if (separatorIndex <= 0)
|
|
2126
|
+
{
|
|
2127
|
+
term = default;
|
|
2128
|
+
displayToken = string.Empty;
|
|
2129
|
+
return false;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
string prefix = token.Substring(0, separatorIndex);
|
|
2133
|
+
string rawValue = token.Substring(separatorIndex + 1);
|
|
2134
|
+
if (
|
|
2135
|
+
!TryCreateFilterValue(rawValue, out string value, out bool exact)
|
|
2136
|
+
|| !TryCreateFilterFacet(prefix, out MessageMonitorFilterFacet facet)
|
|
2137
|
+
)
|
|
2138
|
+
{
|
|
2139
|
+
term = default;
|
|
2140
|
+
displayToken = string.Empty;
|
|
2141
|
+
return false;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
term = new MessageMonitorFilterTerm(facet, value, exact);
|
|
2145
|
+
displayToken =
|
|
2146
|
+
$"{CreateFacetDisplayPrefix(prefix, facet)}:{CreateFilterValueDisplay(value, exact)}";
|
|
2147
|
+
return true;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
private static bool TryCreateFilterValue(string rawValue, out string value, out bool exact)
|
|
2151
|
+
{
|
|
2152
|
+
string trimmedValue = rawValue?.Trim() ?? string.Empty;
|
|
2153
|
+
value = string.Empty;
|
|
2154
|
+
exact = false;
|
|
2155
|
+
if (string.IsNullOrWhiteSpace(trimmedValue))
|
|
2156
|
+
{
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
bool startsQuoted = trimmedValue[0] == '"';
|
|
2161
|
+
bool endsQuoted = trimmedValue[trimmedValue.Length - 1] == '"';
|
|
2162
|
+
if (startsQuoted || endsQuoted)
|
|
2163
|
+
{
|
|
2164
|
+
if (!startsQuoted || !endsQuoted || trimmedValue.Length < 2)
|
|
2165
|
+
{
|
|
2166
|
+
return false;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
exact = true;
|
|
2170
|
+
return TryUnescapeQuotedFilterValue(
|
|
2171
|
+
trimmedValue.Substring(1, trimmedValue.Length - 2),
|
|
2172
|
+
out value
|
|
2173
|
+
) && !string.IsNullOrWhiteSpace(value);
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
value = trimmedValue;
|
|
2177
|
+
return true;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
private static bool TryUnescapeQuotedFilterValue(string quotedValue, out string value)
|
|
2181
|
+
{
|
|
2182
|
+
StringBuilder builder = new();
|
|
2183
|
+
bool escaped = false;
|
|
2184
|
+
|
|
2185
|
+
foreach (char character in quotedValue ?? string.Empty)
|
|
2186
|
+
{
|
|
2187
|
+
if (escaped)
|
|
2188
|
+
{
|
|
2189
|
+
builder.Append(character);
|
|
2190
|
+
escaped = false;
|
|
2191
|
+
continue;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
if (character == '\\')
|
|
2195
|
+
{
|
|
2196
|
+
escaped = true;
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
builder.Append(character);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
value = builder.ToString();
|
|
2204
|
+
return !escaped;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
private static string CreateFilterValueDisplay(string value, bool exact)
|
|
2208
|
+
{
|
|
2209
|
+
if (!exact && !RequiresQuotedFilterValue(value))
|
|
2210
|
+
{
|
|
2211
|
+
return value;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
return $"\"{EscapeQuotedFilterValue(value)}\"";
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
private static bool RequiresQuotedFilterValue(string value)
|
|
2218
|
+
{
|
|
2219
|
+
return (value ?? string.Empty).Any(char.IsWhiteSpace);
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
private static string EscapeQuotedFilterValue(string value)
|
|
2223
|
+
{
|
|
2224
|
+
return (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"");
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
private static bool TryCreateFilterFacet(string prefix, out MessageMonitorFilterFacet facet)
|
|
2228
|
+
{
|
|
2229
|
+
switch (prefix?.Trim().ToLowerInvariant())
|
|
2230
|
+
{
|
|
2231
|
+
case "type":
|
|
2232
|
+
case "message":
|
|
2233
|
+
facet = MessageMonitorFilterFacet.MessageType;
|
|
2234
|
+
return true;
|
|
2235
|
+
case "context":
|
|
2236
|
+
facet = MessageMonitorFilterFacet.Context;
|
|
2237
|
+
return true;
|
|
2238
|
+
case "stack":
|
|
2239
|
+
facet = MessageMonitorFilterFacet.Stack;
|
|
2240
|
+
return true;
|
|
2241
|
+
default:
|
|
2242
|
+
facet = MessageMonitorFilterFacet.Any;
|
|
2243
|
+
return false;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
private static string CreateFacetDisplayPrefix(
|
|
2248
|
+
string prefix,
|
|
2249
|
+
MessageMonitorFilterFacet facet
|
|
2250
|
+
)
|
|
2251
|
+
{
|
|
2252
|
+
string normalizedPrefix = prefix?.Trim().ToLowerInvariant();
|
|
2253
|
+
if (string.Equals(normalizedPrefix, "message", StringComparison.Ordinal))
|
|
2254
|
+
{
|
|
2255
|
+
return "message";
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
switch (facet)
|
|
2259
|
+
{
|
|
2260
|
+
case MessageMonitorFilterFacet.MessageType:
|
|
2261
|
+
return "type";
|
|
2262
|
+
case MessageMonitorFilterFacet.Context:
|
|
2263
|
+
return "context";
|
|
2264
|
+
case MessageMonitorFilterFacet.Stack:
|
|
2265
|
+
return "stack";
|
|
2266
|
+
default:
|
|
2267
|
+
return normalizedPrefix ?? string.Empty;
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
internal readonly struct MessageMonitorFilterTerm
|
|
2273
|
+
{
|
|
2274
|
+
internal MessageMonitorFilterTerm(MessageMonitorFilterFacet facet, string text, bool exact)
|
|
2275
|
+
{
|
|
2276
|
+
Facet = facet;
|
|
2277
|
+
Text = text ?? string.Empty;
|
|
2278
|
+
Exact = exact;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
internal MessageMonitorFilterFacet Facet { get; }
|
|
2282
|
+
|
|
2283
|
+
internal string Text { get; }
|
|
2284
|
+
|
|
2285
|
+
internal bool Exact { get; }
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
#endif
|