A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Reflection;
4using System.Runtime.CompilerServices;
5using System.Text;
6using System.Threading.Tasks;
7using Unity.Burst.LowLevel;
8using UnityEditor;
9using System.Text.RegularExpressions;
10using UnityEditor.IMGUI.Controls;
11using UnityEngine;
12
13[assembly: InternalsVisibleTo("Unity.Burst.Editor.Tests")]
14[assembly: InternalsVisibleTo("Unity.Burst.Tester.Editor.Tests")]
15
16namespace Unity.Burst.Editor
17{
18 internal class BurstInspectorGUI : EditorWindow
19 {
20 private static bool Initialized;
21
22 private static void EnsureInitialized()
23 {
24 if (Initialized)
25 {
26 return;
27 }
28
29 Initialized = true;
30
31 BurstLoader.OnBurstShutdown += () =>
32 {
33 if (EditorWindow.HasOpenInstances<BurstInspectorGUI>())
34 {
35 var window = EditorWindow.GetWindow<BurstInspectorGUI>("Burst Inspector");
36 window.Close();
37 }
38 };
39 }
40
41 private const string FontSizeIndexPref = "BurstInspectorFontSizeIndex";
42
43 private static readonly string[] DisassemblyKindNames =
44 {
45 "Assembly",
46 ".NET IL",
47 "LLVM IR (Unoptimized)",
48 "LLVM IR (Optimized)",
49 "LLVM IR Optimisation Diagnostics"
50 };
51
52 internal enum AssemblyOptions
53 {
54 PlainWithoutDebugInformation = 0,
55 PlainWithDebugInformation = 1,
56 EnhancedWithMinimalDebugInformation = 2,
57 EnhancedWithFullDebugInformation = 3,
58 ColouredWithMinimalDebugInformation = 4,
59 ColouredWithFullDebugInformation = 5
60 }
61 internal AssemblyOptions? _assemblyKind = null;
62 private AssemblyOptions? _assemblyKindPrior = null;
63 private AssemblyOptions _oldAssemblyKind;
64
65 private bool SupportsEnhancedRendering => _disasmKind == DisassemblyKind.Asm || _disasmKind == DisassemblyKind.OptimizedIR || _disasmKind == DisassemblyKind.UnoptimizedIR;
66
67 private static string[] DisasmOptions;
68
69 internal static string[] GetDisasmOptions()
70 {
71 if (DisasmOptions == null)
72 {
73 // We can't initialize this in BurstInspectorGUI.cctor because BurstCompilerOptions may not yet
74 // have been initialized by BurstLoader. So we initialize on-demand here. This method doesn't need to
75 // be thread-safe because it's only called from the UI thread.
76 DisasmOptions = new[]
77 {
78 "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.Asm),
79 "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IL),
80 "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IR),
81 "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IROptimized),
82 "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IRPassAnalysis)
83 };
84 }
85 return DisasmOptions;
86 }
87
88 private static readonly SplitterState TreeViewSplitterState = new SplitterState(new float[] { 30, 70 }, new int[] { 128, 128 }, null);
89
90 private static readonly string[] TargetCpuNames = Enum.GetNames(typeof(BurstTargetCpu));
91 private static readonly string[] SIMDSmellTest = { "False", "True" };
92
93 private static readonly int[] FontSizes =
94 {
95 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20
96 };
97
98 private static string[] _fontSizesText;
99 internal const int _scrollbarThickness = 14;
100
101 internal float _buttonOverlapInspectorView = 0;
102
103 /// <remarks>Used because it's not legal to change layout of GUI in a frame without the users input.</remarks>
104 private float _buttonBarWidth = -1;
105
106 [NonSerialized]
107 internal readonly BurstDisassembler _burstDisassembler;
108
109 private const string BurstSettingText = "Inspector Settings/";
110
111 [SerializeField] private BurstTargetCpu _targetCpu = BurstTargetCpu.Auto;
112
113 [SerializeField] private DisassemblyKind _disasmKind = DisassemblyKind.Asm;
114 [SerializeField] private DisassemblyKind _oldDisasmKind = DisassemblyKind.Asm;
115
116 [NonSerialized]
117 internal GUIStyle fixedFontStyle;
118
119 [NonSerialized]
120 internal int fontSizeIndex = -1;
121
122 [SerializeField] private int _previousTargetIndex = -1;
123
124 [SerializeField] private bool _safetyChecks = false;
125 [SerializeField] private bool _showBranchMarkers = true;
126 [SerializeField] private bool _enhancedDisassembly = true;
127 [SerializeField] private string _searchFilterJobs;
128 [SerializeField] private bool _showUnityNamespaceJobs = false;
129 [SerializeField] private bool _showDOTSGeneratedJobs = false;
130 [SerializeField] private bool _focusTargetJob = true;
131 [SerializeField] private string _searchFilterAssembly = String.Empty;
132
133 [SerializeField] private bool _sameTargetButDifferentAssemblyKind = false;
134 [SerializeField] internal Vector2 _scrollPos;
135 internal SearchField _searchFieldJobs;
136 internal SearchField _searchFieldAssembly;
137 private bool saveSearchFieldFromEvent = false;
138
139 [SerializeField] private bool _searchBarVisible = true;
140
141 [SerializeField] private string _selectedItem;
142
143 [NonSerialized]
144 private BurstCompileTarget _target;
145 [NonSerialized]
146 private List<BurstCompileTarget> _targets;
147 // Used as a serialized representation of _targets:
148 [SerializeField] private List<string> targetNames;
149
150 [NonSerialized]
151 internal LongTextArea _textArea;
152
153 internal Rect _inspectorView;
154
155 [NonSerialized]
156 internal Font _font;
157
158 [NonSerialized]
159 internal BurstMethodTreeView _treeView;
160 // Serialized representation of _treeView:
161 [SerializeField] private TreeViewState treeViewState;
162
163 [NonSerialized]
164 internal bool _initialized;
165
166 [NonSerialized]
167 private bool _requiresRepaint;
168
169 private int FontSize => FontSizes[fontSizeIndex];
170
171 private static readonly Regex _rx = new Regex(@"^.*\(\d+,\d+\):\sBurst\serror");
172
173 private bool _leftClicked = false;
174
175 [SerializeField] private bool _isCompileError = false;
176 [SerializeField] private bool _prevWasCompileError;
177
178 [SerializeField] private bool _smellTest = false;
179
180 // Caching GUIContent and style options for button bar
181 private readonly GUIContent _contentShowUnityNamespaceJobs = new GUIContent("Show Unity Namespace");
182 private readonly GUIContent _contentShowDOTSGeneratedJobs = new GUIContent("Show \".Generated\"");
183 private readonly GUIContent _contentDisasm = new GUIContent("Enhanced With Minimal Debug Information");
184 private readonly GUIContent _contentCollapseToCode = new GUIContent("Focus on Code");
185 private readonly GUIContent _contentExpandAll = new GUIContent("Expand All");
186 private readonly GUIContent _contentBranchLines = new GUIContent("Show Branch Flow");
187 private readonly GUIContent[] _contentsTarget;
188 private readonly GUIContent[] _contentsFontSize;
189 private readonly GUIContent[] _contentsSmellTest =
190 {
191 new GUIContent("Highlight SIMD Scalar vs Packed (False)"),
192 new GUIContent("Highlight SIMD Scalar vs Packed (True)")
193 };
194
195 // content for button search bar
196 private readonly GUIContent _ignoreCase = new GUIContent("Match Case");
197 private readonly GUIContent _matchWord = new GUIContent("Whole words");
198 private readonly GUIContent _regexSearch = new GUIContent("Regex");
199
200 private readonly GUILayoutOption[] _toolbarStyleOptions = { GUILayout.ExpandWidth(true), GUILayout.MinWidth(5 * 10) };
201
202 private readonly string[] _branchMarkerOptions = { "Hide Branch Flow", "Show Branch Flow" };
203 private readonly string[] _safetyCheckOptions = { "Safety Check On", "Safety Check Off" };
204
205
206 private enum KeyboardOperation
207 {
208 SelectAll,
209 Copy,
210 MoveLeft,
211 MoveRight,
212 MoveUp,
213 MoveDown,
214 Search,
215 Escape,
216 Enter,
217 }
218
219 private Dictionary<Event, KeyboardOperation> _keyboardEvents;
220
221 private void FillKeyboardEvent()
222 {
223 if (_keyboardEvents != null)
224 {
225 return;
226 }
227
228 _keyboardEvents = new Dictionary<Event, KeyboardOperation>();
229
230 _keyboardEvents.Add(Event.KeyboardEvent("#left"), KeyboardOperation.MoveLeft);
231 _keyboardEvents.Add(Event.KeyboardEvent("#right"), KeyboardOperation.MoveRight);
232 _keyboardEvents.Add(Event.KeyboardEvent("#down"), KeyboardOperation.MoveDown);
233 _keyboardEvents.Add(Event.KeyboardEvent("#up"), KeyboardOperation.MoveUp);
234 _keyboardEvents.Add(Event.KeyboardEvent("escape"), KeyboardOperation.Escape);
235 _keyboardEvents.Add(Event.KeyboardEvent("return"), KeyboardOperation.Enter);
236 _keyboardEvents.Add(Event.KeyboardEvent("#return"), KeyboardOperation.Enter);
237
238 _keyboardEvents.Add(Event.KeyboardEvent("left"), KeyboardOperation.MoveLeft);
239 _keyboardEvents.Add(Event.KeyboardEvent("right"), KeyboardOperation.MoveRight);
240 _keyboardEvents.Add(Event.KeyboardEvent("up"), KeyboardOperation.MoveUp);
241 _keyboardEvents.Add(Event.KeyboardEvent("down"), KeyboardOperation.MoveDown);
242
243 if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX)
244 {
245 _keyboardEvents.Add(Event.KeyboardEvent("%a"), KeyboardOperation.SelectAll);
246 _keyboardEvents.Add(Event.KeyboardEvent("%c"), KeyboardOperation.Copy);
247 _keyboardEvents.Add(Event.KeyboardEvent("%f"), KeyboardOperation.Search);
248 }
249 else
250 {
251 // windows or linux bindings.
252 _keyboardEvents.Add(Event.KeyboardEvent("^a"), KeyboardOperation.SelectAll);
253 _keyboardEvents.Add(Event.KeyboardEvent("^c"), KeyboardOperation.Copy);
254 _keyboardEvents.Add(Event.KeyboardEvent("^f"), KeyboardOperation.Search);
255 }
256 }
257
258 public BurstInspectorGUI()
259 {
260 _burstDisassembler = new BurstDisassembler();
261
262 string[] names = Enum.GetNames(typeof(BurstTargetCpu));
263 int size = names.Length;
264 _contentsTarget = new GUIContent[size];
265 for (int i = 0; i < size; i++)
266 {
267 _contentsTarget[i] = new GUIContent($"Target ({names[i]})");
268 }
269
270 size = FontSizes.Length;
271 _contentsFontSize = new GUIContent[size];
272 for (int i = 0; i < size; i++)
273 {
274 _contentsFontSize[i] = new GUIContent($"Font Size ({FontSizes[i].ToString()})");
275 }
276 }
277
278 private bool DisplayAssemblyKind(Enum assemblyKind)
279 {
280 var assemblyOption = (AssemblyOptions)assemblyKind;
281 if (_disasmKind != DisassemblyKind.Asm || _isCompileError)
282 {
283 return assemblyOption == AssemblyOptions.PlainWithoutDebugInformation;
284 }
285 return true;
286 }
287
288 public void OnEnable()
289 {
290 EnsureInitialized();
291
292 var newTreeState = false;
293 if (treeViewState is null)
294 {
295 treeViewState = new TreeViewState();
296 newTreeState = true;
297 }
298 _treeView ??= _treeView = new BurstMethodTreeView
299 (
300 treeViewState,
301 () => _searchFilterJobs,
302 () => (_showUnityNamespaceJobs, _showDOTSGeneratedJobs)
303 );
304
305 if (_keyboardEvents == null) FillKeyboardEvent();
306
307 var assemblyList = BurstReflection.EditorAssembliesThatCanPossiblyContainJobs;
308
309 Task.Run(
310 () =>
311 {
312 // Do this stuff asynchronously.
313 var result = BurstReflection.FindExecuteMethods(assemblyList, BurstReflectionAssemblyOptions.None);
314 _targets = result.CompileTargets;
315 _targets.Sort((left, right) => string.Compare(left.GetDisplayName(), right.GetDisplayName(), StringComparison.Ordinal));
316 return result;
317 })
318 .ContinueWith(t =>
319 {
320 // Do this stuff on the main (UI) thread.
321 if (t.Status == TaskStatus.RanToCompletion)
322 {
323 foreach (var logMessage in t.Result.LogMessages)
324 {
325 switch (logMessage.LogType)
326 {
327 case BurstReflection.LogType.Warning:
328 Debug.LogWarning(logMessage.Message);
329 break;
330 case BurstReflection.LogType.Exception:
331 Debug.LogException(logMessage.Exception);
332 break;
333 default:
334 throw new InvalidOperationException();
335 }
336 }
337
338 var newNames = new List<string>(_targets.Count);
339 foreach (var target in _targets)
340 {
341 newNames.Add(target.GetDisplayName());
342 }
343
344 bool identical = !newTreeState && newNames.Count == targetNames.Count;
345 int len = newNames.Count;
346 int i = 0;
347 while (identical && i < len)
348 {
349 identical = newNames[i] == targetNames[i];
350 i++;
351 }
352 targetNames = newNames;
353 _treeView.Initialize(_targets, identical);
354
355 if (_selectedItem == null || !_treeView.TrySelectByDisplayName(_selectedItem))
356 {
357 _previousTargetIndex = -1;
358 _scrollPos = Vector2.zero;
359 }
360
361 _requiresRepaint = true;
362 _initialized = true;
363 }
364 else if (t.Exception != null)
365 {
366 Debug.LogError($"Could not load Inspector: {t.Exception}");
367 }
368 });
369 }
370
371#if !UNITY_2023_1_OR_NEWER
372 private void CleanupFont()
373 {
374 if (_font != null)
375 {
376 DestroyImmediate(_font, true);
377 _font = null;
378 }
379 }
380
381 public void OnDisable()
382 {
383 CleanupFont();
384 }
385#endif
386
387 public void Update()
388 {
389 // Need to do this because if we call Repaint from anywhere else,
390 // it doesn't do anything if this window is not currently focused.
391 if (_requiresRepaint)
392 {
393 Repaint();
394 _requiresRepaint = false;
395 }
396
397 // Need this because pressing new target, and then not invoking new events,
398 // will leave the assembly unrendered.
399 // This is not included in above, to minimize needed calls.
400 if (_target != null && _target.JustLoaded)
401 {
402 Repaint();
403 }
404 }
405
406 /// <summary>
407 /// Checks if there is space for given content withs style, and starts new horizontalgroup
408 /// if there is no space on this line.
409 /// </summary>
410 private void FlowToNewLine(ref float remainingWidth, float width, Vector2 size)
411 {
412 float sizeX = size.x + _scrollbarThickness / 2;
413 if (sizeX >= remainingWidth)
414 {
415 _buttonOverlapInspectorView += size.y + 2;
416 remainingWidth = width - sizeX;
417 GUILayout.EndHorizontal();
418 GUILayout.BeginHorizontal();
419 }
420 else
421 {
422 remainingWidth -= sizeX;
423 }
424 }
425
426 private bool IsRaw(AssemblyOptions kind)
427 {
428 return kind == AssemblyOptions.PlainWithoutDebugInformation || kind == AssemblyOptions.PlainWithDebugInformation;
429 }
430
431 private bool IsEnhanced(AssemblyOptions kind)
432 {
433 return !IsRaw(kind);
434 }
435
436 private bool IsColoured(AssemblyOptions kind)
437 {
438 return kind == AssemblyOptions.ColouredWithMinimalDebugInformation || kind == AssemblyOptions.ColouredWithFullDebugInformation;
439 }
440
441 /// <summary>
442 /// Renders buttons bar, and handles saving/loading of _assemblyKind options when changing in inspector settings
443 /// that disable/enables some options for _assemblyKind.
444 /// </summary>
445 private void HandleButtonBars(BurstCompileTarget target, bool targetChanged, out int fontIndex, out bool collapse, out bool focusCode)
446 {
447 // We can only make an educated guess for the correct width.
448 if (_buttonBarWidth == -1)
449 {
450 _buttonBarWidth = (position.width * 2) / 3 - _scrollbarThickness;
451 }
452
453 RenderButtonBars(_buttonBarWidth, target, out fontIndex, out collapse, out focusCode);
454
455 var disasmKindChanged = _oldDisasmKind != _disasmKind;
456
457 // Handles saving and loading _assemblyKind option when going between settings, that disable/enable some options for it
458 if ((disasmKindChanged && _oldDisasmKind == DisassemblyKind.Asm && !_isCompileError)
459 || (targetChanged && !_prevWasCompileError && _isCompileError && _disasmKind == DisassemblyKind.Asm))
460 {
461 // save when _disasmKind changed from Asm WHEN we are not looking at a burst compile error,
462 // or when target changed from non compile error to compile error and current _disasmKind is Asm.
463 _oldAssemblyKind = (AssemblyOptions)_assemblyKind;
464 }
465 else if ((disasmKindChanged && _disasmKind == DisassemblyKind.Asm && !_isCompileError) ||
466 (targetChanged && _prevWasCompileError && _disasmKind == DisassemblyKind.Asm))
467 {
468 // load when _diasmKind changed to Asm and we are not at burst compile error,
469 // or when target changed from a burst compile error while _disasmKind is Asm.
470 _assemblyKind = _oldAssemblyKind;
471 }
472
473 // if _assemblyKind is something that is not available, force it up to PlainWithoutDebugInformation.
474 if ((_disasmKind != DisassemblyKind.Asm && _assemblyKind != AssemblyOptions.PlainWithoutDebugInformation)
475 || _isCompileError)
476 {
477 _assemblyKind = AssemblyOptions.PlainWithoutDebugInformation;
478 }
479 }
480
481 private void RenderButtonBars(float width, BurstCompileTarget target, out int fontIndex, out bool collapse, out bool focus)
482 {
483 var remainingWidth = width;
484 GUILayout.BeginHorizontal();
485
486 // First button should never call beginHorizontal().
487 remainingWidth -= (EditorStyles.popup.CalcSize(_contentDisasm).x + _scrollbarThickness / 2f);
488
489 EditorGUI.BeginDisabledGroup(target.DisassemblyKind == DisassemblyKind.IRPassAnalysis);
490
491 _assemblyKind = (AssemblyOptions)EditorGUILayout.EnumPopup(GUIContent.none, _assemblyKind, DisplayAssemblyKind, true);
492
493 EditorGUI.EndDisabledGroup();
494
495 FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(_contentBranchLines));
496 // Reversed "logic" to match the array of options, which has "positive" case on idx 0.
497 _safetyChecks = EditorGUILayout.Popup(_safetyChecks ? 0 : 1, _safetyCheckOptions) == 0;
498
499 EditorGUI.BeginDisabledGroup(!target.HasRequiredBurstCompileAttributes);
500
501 GUIContent targetContent = _contentsTarget[(int)_targetCpu];
502 FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(targetContent));
503 _targetCpu = (BurstTargetCpu)LabeledPopup.Popup((int)_targetCpu, targetContent, TargetCpuNames);
504
505 GUIContent fontSizeContent = _contentsFontSize[fontSizeIndex];
506 FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(fontSizeContent));
507 fontIndex = LabeledPopup.Popup(fontSizeIndex, fontSizeContent, _fontSizesText);
508
509 EditorGUI.EndDisabledGroup();
510
511 EditorGUI.BeginDisabledGroup(!IsEnhanced((AssemblyOptions)_assemblyKind) || !SupportsEnhancedRendering || _isCompileError);
512
513 FlowToNewLine(ref remainingWidth, width, EditorStyles.miniButton.CalcSize(_contentCollapseToCode));
514 focus = GUILayout.Button(_contentCollapseToCode, EditorStyles.miniButton);
515
516 FlowToNewLine(ref remainingWidth, width, EditorStyles.miniButton.CalcSize(_contentExpandAll));
517 collapse = GUILayout.Button(_contentExpandAll, EditorStyles.miniButton);
518
519 FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(_contentBranchLines));
520 _showBranchMarkers = EditorGUILayout.Popup(Convert.ToInt32(_showBranchMarkers), _branchMarkerOptions) == 1;
521
522 int smellTestIdx = Convert.ToInt32(_smellTest);
523 GUIContent smellTestContent = _contentsSmellTest[smellTestIdx];
524 FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(smellTestContent));
525 _smellTest = LabeledPopup.Popup(smellTestIdx, smellTestContent, SIMDSmellTest) == 1;
526
527 EditorGUI.EndDisabledGroup();
528
529 GUILayout.EndHorizontal();
530
531 _oldDisasmKind = _disasmKind;
532 _disasmKind = (DisassemblyKind)GUILayout.Toolbar((int)_disasmKind, DisassemblyKindNames, _toolbarStyleOptions);
533 }
534
535 /// <summary>
536 /// Handles mouse events for selecting text.
537 /// </summary>
538 /// <remarks>
539 /// Must be called after Render(...), as it uses the mouse events, and Render(...)
540 /// need mouse events for buttons etc.
541 /// </remarks>
542 private void HandleMouseEventForSelection(Rect workingArea, int controlID, bool showBranchMarkers)
543 {
544 var evt = Event.current;
545 var mousePos = evt.mousePosition;
546
547 if (_textArea.MouseOutsideView(workingArea, mousePos, controlID))
548 {
549 return;
550 }
551
552 switch (evt.type)
553 {
554 case EventType.MouseDown:
555 // button 0 is left and 1 is right
556 if (evt.button == 0)
557 {
558 _textArea.MouseClicked(showBranchMarkers, evt.shift, mousePos, controlID);
559 }
560 else
561 {
562 _leftClicked = true;
563 }
564 evt.Use();
565 break;
566 case EventType.MouseDrag:
567 _textArea.DragMouse(mousePos, showBranchMarkers);
568 evt.Use();
569 break;
570 case EventType.MouseUp:
571 _textArea.MouseReleased();
572 evt.Use();
573 break;
574 case EventType.ScrollWheel:
575 _textArea.DoScroll(workingArea, evt.delta.y);
576 // we cannot Use() (consume) scrollWheel events, as they are still needed in EndScrollView.
577 break;
578 }
579 }
580
581 private bool AssemblyFocused() => !((_treeView != null && _treeView.HasFocus()) || (_searchFieldAssembly != null && _searchFieldAssembly.HasFocus()));
582
583 private void HandleKeyboardEventAssemblyView(Rect workingArea, KeyboardOperation op, Event evt, bool showBranchMarkers)
584 {
585 switch (op)
586 {
587 case KeyboardOperation.SelectAll:
588 _textArea.SelectAll();
589 evt.Use();
590 break;
591
592 case KeyboardOperation.Copy:
593 _textArea.DoSelectionCopy();
594 evt.Use();
595 break;
596
597 case KeyboardOperation.MoveLeft:
598 if (evt.shift)
599 {
600 if (_textArea.HasSelection) _textArea.MoveSelectionLeft(workingArea, showBranchMarkers);
601 }
602 else
603 {
604 _textArea.MoveView(LongTextArea.Direction.Left, workingArea);
605 }
606
607 evt.Use();
608 break;
609
610 case KeyboardOperation.MoveRight:
611 if (evt.shift)
612 {
613 if (_textArea.HasSelection) _textArea.MoveSelectionRight(workingArea, showBranchMarkers);
614 }
615 else
616 {
617 _textArea.MoveView(LongTextArea.Direction.Right, workingArea);
618 }
619
620 evt.Use();
621 break;
622
623 case KeyboardOperation.MoveUp:
624 if (evt.shift)
625 {
626 if (_textArea.HasSelection) _textArea.MoveSelectionUp(workingArea, showBranchMarkers);
627 }
628 else
629 {
630 _textArea.MoveView(LongTextArea.Direction.Up, workingArea);
631 }
632
633 evt.Use();
634 break;
635
636 case KeyboardOperation.MoveDown:
637 if (evt.shift)
638 {
639 if (_textArea.HasSelection) _textArea.MoveSelectionDown(workingArea, showBranchMarkers);
640 }
641 else
642 {
643 _textArea.MoveView(LongTextArea.Direction.Down, workingArea);
644 }
645
646 evt.Use();
647 break;
648 case KeyboardOperation.Search:
649 _searchBarVisible = true;
650 _searchFieldAssembly?.SetFocus();
651 evt.Use();
652 break;
653 }
654 }
655
656 /// <remarks>
657 /// Must be called after Render(...) because of depenency on LongTextArea.finalAreaSize.
658 /// </remarks>
659 private void HandleKeyboardEventForSelection(Rect workingArea, bool showBranchMarkers)
660 {
661 var evt = Event.current;
662
663 if (!_keyboardEvents.TryGetValue(evt, out var op))
664 {
665 return;
666 }
667
668 if (AssemblyFocused())
669 {
670 // Do input handling for assembly view.
671 HandleKeyboardEventAssemblyView(workingArea, op, evt, showBranchMarkers);
672 }
673 else
674 {
675 // This amounts to logic for all else.
676 switch (op)
677 {
678 case KeyboardOperation.Escape:
679 if (_searchFieldAssembly != null && _searchFieldAssembly.HasFocus() && _searchFilterAssembly == "")
680 {
681 _searchBarVisible = false;
682 evt.Use();
683 }
684 break;
685 case KeyboardOperation.Enter:
686 if (_searchFieldAssembly != null && _searchFieldAssembly.HasFocus())
687 {
688 _textArea.NextSearchHit(evt.shift, workingArea);
689 saveSearchFieldFromEvent = true;
690 evt.Use();
691 }
692 break;
693 }
694 }
695 }
696
697 private void RenderCompileTargetsFilters(float width)
698 {
699 GUILayout.BeginHorizontal();
700 // Handle and render filtering toggles:
701 var newShowUnityTests = GUILayout.Toggle(_showUnityNamespaceJobs, _contentShowUnityNamespaceJobs);
702
703 FlowToNewLine(ref width, width, EditorStyles.toggle.CalcSize(_contentShowDOTSGeneratedJobs));
704 var newShowDOTSGeneratedJobs = GUILayout.Toggle(_showDOTSGeneratedJobs, _contentShowDOTSGeneratedJobs);
705 GUILayout.EndHorizontal();
706
707 if (newShowUnityTests != _showUnityNamespaceJobs || newShowDOTSGeneratedJobs != _showDOTSGeneratedJobs)
708 {
709 _showDOTSGeneratedJobs = newShowDOTSGeneratedJobs;
710 _showUnityNamespaceJobs = newShowUnityTests;
711 _treeView.Reload();
712 }
713
714 // Handle and render search filter:
715 var newFilter = _searchFieldJobs.OnGUI(_searchFilterJobs);
716 if (newFilter != _searchFilterJobs)
717 {
718 _searchFilterJobs = newFilter;
719 _treeView.Reload();
720 }
721 }
722
723
724 private void CompileNewTarget(BurstCompileTarget target, BurstCompilerOptions targetOptions)
725 {
726 if (target.IsLoading) { return; }
727
728 target.IsLoading = true;
729 target.JustLoaded = false;
730
731 // Setup target and it's compilation options.
732 // This is done here as EditorGUIUtility.isProSkin must be on main thread.
733 target.TargetCpu = _targetCpu;
734 target.DisassemblyKind = _disasmKind;
735 targetOptions.EnableBurstSafetyChecks = _safetyChecks;
736 target.IsDarkMode = EditorGUIUtility.isProSkin;
737
738 // Don't set debug mode, because it disables optimizations.
739 // Instead we set debug level (None, Full, LineOnly) below.
740 targetOptions.EnableBurstDebug = false;
741
742 Task.Run(() =>
743 {
744 var options = new StringBuilder();
745
746 if (targetOptions.TryGetOptions(target.IsStaticMethod ? (MemberInfo)target.Method : target.JobType, out var defaultOptions, isForCompilerClient: true))
747 {
748 options.AppendLine(defaultOptions);
749
750 // Disables the 2 current warnings generated from code (since they clutter up the inspector display)
751 // BC1370 - throw inside code not guarded with ConditionalSafetyCheck attribute
752 // BC1322 - loop intrinsic on loop that has been optimized away
753 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDisableWarnings, "BC1370;BC1322")}");
754
755 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionTarget, TargetCpuNames[(int)_targetCpu])}");
756
757 // For IRPassAnalysis, we always want full debug information.
758 if (_disasmKind != DisassemblyKind.IRPassAnalysis)
759 {
760 switch (_assemblyKind)
761 {
762 case AssemblyOptions.EnhancedWithMinimalDebugInformation:
763 case AssemblyOptions.ColouredWithMinimalDebugInformation:
764 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "2")}");
765 break;
766 case AssemblyOptions.ColouredWithFullDebugInformation:
767 case AssemblyOptions.EnhancedWithFullDebugInformation:
768 case AssemblyOptions.PlainWithDebugInformation:
769 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "1")}");
770 break;
771 default:
772 case AssemblyOptions.PlainWithoutDebugInformation:
773 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "0")}");
774 break;
775 }
776 }
777 else
778 {
779 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "1")}");
780 }
781
782 var baseOptions = options.ToString();
783
784 target.RawDisassembly = GetDisassembly(target.Method, baseOptions + GetDisasmOptions()[(int)_disasmKind]);
785
786 target.FormattedDisassembly = null;
787
788 target.IsBurstError = IsBurstError(target.RawDisassembly);
789 }
790
791 target.IsLoading = false;
792 target.JustLoaded = true;
793 });
794 }
795
796 private void RenderBurstJobMenu()
797 {
798 float width = position.width / 3;
799 GUILayout.BeginVertical(GUILayout.Width(width));
800
801 // Render Treeview showing burst targets:
802 GUILayout.Label("Compile Targets", EditorStyles.boldLabel);
803 RenderCompileTargetsFilters(width);
804
805 // Does not give proper rect during layout event.
806 _inspectorView = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
807
808 _treeView.OnGUI(_inspectorView);
809
810 GUILayout.EndVertical();
811 }
812
813 private void HandleHorizontalFocus(float workingWidth, bool shouldSetupText, bool isTextFormatted)
814 {
815 if (!shouldSetupText || !isTextFormatted || !_burstDisassembler.IsInitialized) { return; }
816
817 var branchFiller = _textArea.MaxLineDepth * 10;
818
819 if (branchFiller < workingWidth / 2f) { return; }
820
821 // Do horizontal padding:
822 _scrollPos.x = _textArea.MaxLineDepth * 10;
823 }
824
825
826 private static void RenderLoading()
827 {
828 GUILayout.BeginHorizontal();
829 GUILayout.FlexibleSpace();
830 GUILayout.BeginVertical();
831 GUILayout.FlexibleSpace();
832 GUILayout.Label("Loading...");
833 GUILayout.FlexibleSpace();
834 GUILayout.EndVertical();
835 GUILayout.FlexibleSpace();
836 GUILayout.EndHorizontal();
837 }
838
839 public void OnGUI()
840 {
841 if (!_initialized)
842 {
843 RenderLoading();
844 return;
845 }
846 // used to give hot control to inspector when a mouseDown event has happened.
847 // This way we can register a mouseUp happening outside inspector.
848 int controlID = GUIUtility.GetControlID(FocusType.Passive);
849
850 // Make sure that editor options are synchronized
851 BurstEditorOptions.EnsureSynchronized();
852
853 if (_fontSizesText == null)
854 {
855 _fontSizesText = new string[FontSizes.Length];
856 for (var i = 0; i < FontSizes.Length; ++i) _fontSizesText[i] = FontSizes[i].ToString();
857 }
858
859 if (fontSizeIndex == -1)
860 {
861 fontSizeIndex = EditorPrefs.GetInt(FontSizeIndexPref, 5);
862 fontSizeIndex = Math.Max(0, fontSizeIndex);
863 fontSizeIndex = Math.Min(fontSizeIndex, FontSizes.Length - 1);
864 }
865
866 if (fixedFontStyle == null || fixedFontStyle.font == null) // also check .font as it's reset somewhere when going out of play mode.
867 {
868 fixedFontStyle = new GUIStyle(GUI.skin.label);
869
870#if UNITY_2023_1_OR_NEWER
871 _font = EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Regular.ttf") as Font;
872#else
873 string fontName;
874 if (Application.platform == RuntimePlatform.WindowsEditor)
875 {
876 fontName = "Consolas";
877 }
878 else
879 {
880 fontName = "Courier";
881 }
882
883 CleanupFont();
884
885 _font = Font.CreateDynamicFontFromOSFont(fontName, FontSize);
886#endif
887
888 fixedFontStyle.font = _font;
889 fixedFontStyle.fontSize = FontSize;
890 }
891
892 if (_searchFieldJobs == null) _searchFieldJobs = new SearchField();
893
894 if (_textArea == null) _textArea = new LongTextArea();
895
896 GUILayout.BeginHorizontal();
897
898 // SplitterGUILayout.BeginHorizontalSplit is internal in Unity but we don't have much choice
899 SplitterGUILayout.BeginHorizontalSplit(TreeViewSplitterState);
900
901 RenderBurstJobMenu();
902
903 GUILayout.BeginVertical();
904
905 var selection = _treeView.GetSelection();
906 if (selection.Count == 1)
907 {
908 var targetIndex = selection[0];
909 _target = _targets[targetIndex - 1];
910 var targetOptions = _target.Options;
911
912 var targetChanged = _previousTargetIndex != targetIndex;
913
914 _previousTargetIndex = targetIndex;
915
916 // Stash selected item name to handle domain reloads more gracefully
917 _selectedItem = _target.GetDisplayName();
918
919 if (_assemblyKind == null)
920 {
921 if (_enhancedDisassembly)
922 {
923 _assemblyKind = AssemblyOptions.ColouredWithMinimalDebugInformation;
924 }
925 else
926 {
927 _assemblyKind = AssemblyOptions.PlainWithoutDebugInformation;
928 }
929 _oldAssemblyKind = (AssemblyOptions)_assemblyKind;
930 }
931
932 // We are currently formatting only Asm output
933 var isTextFormatted = IsEnhanced((AssemblyOptions)_assemblyKind) && SupportsEnhancedRendering;
934
935 // Depending if we are formatted or not, we don't render the same text
936 var textToRender = _target.RawDisassembly?.TrimStart('\n');
937
938 // Only refresh if we are switching to a new selection that hasn't been disassembled yet
939 // Or we are changing disassembly settings (safety checks / enhanced disassembly)
940 var targetRefresh = textToRender == null
941 || _target.DisassemblyKind != _disasmKind
942 || targetOptions.EnableBurstSafetyChecks != _safetyChecks
943 || _target.TargetCpu != _targetCpu
944 || _target.IsDarkMode != EditorGUIUtility.isProSkin;
945
946 if (_assemblyKindPrior != _assemblyKind)
947 {
948 targetRefresh = true;
949 _assemblyKindPrior = _assemblyKind; // Needs to be refreshed, as we need to change disassembly options
950
951 // If the target did not changed but our assembly kind did, we need to remember this.
952 if (!targetChanged)
953 {
954 _sameTargetButDifferentAssemblyKind = true;
955 }
956 }
957
958 // If the previous target changed the assembly kind and we have a target change, we need to
959 // refresh the assembly because we'll have cached the previous assembly kinds output rather
960 // than the one requested.
961 if (_sameTargetButDifferentAssemblyKind && targetChanged)
962 {
963 targetRefresh = true;
964 _sameTargetButDifferentAssemblyKind = false;
965 }
966
967 if (targetRefresh)
968 {
969 CompileNewTarget(_target, targetOptions);
970 }
971
972 _prevWasCompileError = _isCompileError;
973 _isCompileError = _target.IsBurstError;
974
975 _buttonOverlapInspectorView = 0;
976 var oldSimdSmellTest = _smellTest;
977 HandleButtonBars(_target, targetChanged, out var fontSize, out var expandAllBlocks, out var focusCode);
978 var simdSmellTestChanged = oldSimdSmellTest != _smellTest;
979
980 // Guard against _textArea being used, as the assembly isn't ready yet.
981 // Have to test against event so it cannot finish between a Layout event and Repaint event;
982 // this is necessary as we cannot alter GUI between these events.
983 if (_target.HasRequiredBurstCompileAttributes && (_target.IsLoading || (_target.JustLoaded && Event.current.type != EventType.Layout)))
984 {
985 RenderLoading();
986
987 // Need to close the splits we opened.
988 GUILayout.EndVertical();
989 SplitterGUILayout.EndHorizontalSplit();
990 GUILayout.EndHorizontal();
991 return;
992 }
993
994 var justLoaded = _target.JustLoaded;
995 _target.JustLoaded = false;
996
997 // If ´CompileNewTarget´ finishes before we enter loading screen above `textToRender` might not be set.
998 textToRender ??= _target.RawDisassembly?.TrimStart('\n');
999
1000 if (!string.IsNullOrEmpty(textToRender))
1001 {
1002 // we should only call SetDisassembler(...) the first time assemblyKind is changed with same target.
1003 // Otherwise it will kep re-initializing fields such as _folded, meaning we can no longer fold/unfold.
1004 var shouldSetupText = !_textArea.IsTextSet(_selectedItem)
1005 || justLoaded
1006 || simdSmellTestChanged;
1007
1008 if (shouldSetupText)
1009 {
1010 _textArea.SetText(
1011 _selectedItem,
1012 textToRender,
1013 _target.IsDarkMode,
1014 _burstDisassembler,
1015 isTextFormatted && _burstDisassembler.Initialize(
1016 textToRender,
1017 FetchAsmKind(_targetCpu, _disasmKind),
1018 _target.IsDarkMode,
1019 IsColoured((AssemblyOptions)_assemblyKind),
1020 _smellTest));
1021 }
1022 if (justLoaded)
1023 {
1024 _scrollPos = Vector2.zero;
1025 }
1026
1027 HandleHorizontalFocus(
1028 _inspectorView.width == 1f ? _buttonBarWidth : _inspectorView.width,
1029 shouldSetupText,
1030 isTextFormatted
1031 );
1032
1033 // Fixing lastRectSize to actually be size of scroll view
1034 _inspectorView.position = _scrollPos;
1035 _inspectorView.width = position.width - (_inspectorView.width + _scrollbarThickness);
1036 _inspectorView.height -= (_buttonOverlapInspectorView + 4); //+4 for alignment.
1037 if (_searchBarVisible) _inspectorView.height -= EditorStyles.searchField.CalcHeight(GUIContent.none, 2); // 2 is just arbitrary, as the width does not alter height
1038
1039 // repaint indicate end of frame, so we can alter width for menu items to new correct.
1040 if (Event.current.type == EventType.Repaint)
1041 {
1042 _buttonBarWidth = _inspectorView.width - _scrollbarThickness;
1043 }
1044
1045 // Do search if we did not try and find assembly and we were actually going to do a search.
1046 if (_focusTargetJob && TryFocusJobInAssemblyView(ref _inspectorView, shouldSetupText, _target))
1047 {
1048 _scrollPos.y = _inspectorView.y - _textArea.fontHeight*10;
1049 }
1050
1051 _scrollPos = GUILayout.BeginScrollView(_scrollPos, true, true);
1052
1053 if (Event.current.type != EventType.Layout) // we always want mouse position feedback
1054 {
1055 _textArea.Interact(_inspectorView, Event.current.type);
1056 }
1057
1058 // Set up search information if it is happening.
1059 Regex regx = default;
1060 SearchCriteria sc = default;
1061 var doSearch = _searchBarVisible && _searchFilterAssembly != "";
1062 var wrongRegx = false;
1063 if (doSearch)
1064 {
1065 sc = new SearchCriteria(_searchFilterAssembly, _doIgnoreCase, _doWholeWordMatch, _doRegex);
1066 if (_doRegex)
1067 {
1068 try
1069 {
1070 var opt = RegexOptions.Compiled | RegexOptions.CultureInvariant;
1071 if (!_doIgnoreCase) opt |= RegexOptions.IgnoreCase;
1072
1073 var filter = _searchFilterAssembly;
1074 if (_doWholeWordMatch) filter = @"\b" + filter + @"\b";
1075
1076 regx = new Regex(filter, opt);
1077 }
1078 catch (Exception)
1079 {
1080 // Regex was invalid
1081 wrongRegx = true;
1082 doSearch = false;
1083 }
1084 }
1085 }
1086
1087 var doRepaint = _textArea.Render(fixedFontStyle, _inspectorView, _showBranchMarkers, doSearch, sc, regx);
1088
1089 // A change in the underlying textArea has happened, that requires the GUI to be repainted during this frame.
1090 if (doRepaint) Repaint();
1091
1092 if (Event.current.type != EventType.Layout)
1093 {
1094 HandleMouseEventForSelection(_inspectorView, controlID, _showBranchMarkers);
1095 HandleKeyboardEventForSelection(_inspectorView, _showBranchMarkers);
1096 }
1097
1098 if (_leftClicked)
1099 {
1100 GenericMenu menu = new GenericMenu();
1101
1102 menu.AddItem(EditorGUIUtility.TrTextContent("Copy Selection"), false, _textArea.DoSelectionCopy);
1103 menu.AddItem(EditorGUIUtility.TrTextContent("Copy Color Tags"), _textArea.CopyColorTags, _textArea.ChangeCopyMode);
1104 menu.AddItem(EditorGUIUtility.TrTextContent("Select All"), false, _textArea.SelectAll);
1105 menu.AddItem(EditorGUIUtility.TrTextContent($"Find in {DisassemblyKindNames[(int)_disasmKind]}"), _searchBarVisible, EnableDisableSearchBar);
1106 menu.ShowAsContext();
1107
1108 _leftClicked = false;
1109 }
1110
1111 GUILayout.EndScrollView();
1112
1113 if (_searchBarVisible)
1114 {
1115 if (_searchFieldAssembly == null)
1116 {
1117 _searchFieldAssembly = new SearchField();
1118 _searchFieldAssembly.autoSetFocusOnFindCommand = false;
1119 }
1120
1121 int hitnumbers = _textArea.NrSearchHits > 0 ? _textArea.ActiveSearchNr + 1 : 0;
1122 var hitNumberContent = new GUIContent(" " + hitnumbers + " of " + _textArea.NrSearchHits + " hits");
1123
1124 GUILayout.BeginHorizontal();
1125
1126 // Makes sure that on "enter" keyboard event, the focus is not taken away from searchField.
1127 if (saveSearchFieldFromEvent) GUI.FocusControl("BurstInspectorGUI");
1128
1129 string newFilterAssembly;
1130 if (wrongRegx)
1131 {
1132 var colb = GUI.contentColor;
1133 GUI.contentColor = Color.red;
1134 newFilterAssembly = _searchFieldAssembly.OnGUI(_searchFilterAssembly);
1135 GUI.contentColor = colb;
1136 }
1137 else
1138 {
1139 newFilterAssembly = _searchFieldAssembly.OnGUI(_searchFilterAssembly);
1140 }
1141 // Give back focus to the searchField, if we took it away.
1142 if (saveSearchFieldFromEvent)
1143 {
1144 _searchFieldAssembly.SetFocus();
1145 saveSearchFieldFromEvent = false;
1146 }
1147
1148
1149 if (newFilterAssembly != _searchFilterAssembly)
1150 {
1151 _searchFilterAssembly = newFilterAssembly;
1152 _textArea.StopSearching();
1153 }
1154
1155 _doIgnoreCase = GUILayout.Toggle(_doIgnoreCase, _ignoreCase);
1156 _doWholeWordMatch = GUILayout.Toggle(_doWholeWordMatch, _matchWord);
1157 _doRegex = GUILayout.Toggle(_doRegex, _regexSearch);
1158 GUILayout.Label(hitNumberContent);
1159 if (GUILayout.Button(GUIContent.none, EditorStyles.searchFieldCancelButton))
1160 {
1161 _searchBarVisible = false;
1162 _textArea.StopSearching();
1163 }
1164
1165 GUILayout.EndHorizontal();
1166 }
1167 }
1168
1169 if (fontSize != fontSizeIndex)
1170 {
1171 _textArea.Invalidate();
1172 fontSizeIndex = fontSize;
1173 EditorPrefs.SetInt(FontSizeIndexPref, fontSize);
1174 fixedFontStyle = null;
1175 }
1176
1177 if (expandAllBlocks)
1178 {
1179 _textArea.ExpandAllBlocks();
1180 }
1181
1182 if (focusCode)
1183 {
1184 _textArea.FocusCodeBlocks();
1185 }
1186 }
1187
1188 GUILayout.EndVertical();
1189
1190 SplitterGUILayout.EndHorizontalSplit();
1191
1192 GUILayout.EndHorizontal();
1193 }
1194
1195 public static bool IsBurstError(string disassembly)
1196 {
1197 return _rx.IsMatch(disassembly ?? "");
1198 }
1199
1200 /// <summary>
1201 /// Focuses the view on the active function if a jump is doable.
1202 /// </summary>
1203 /// <param name="workingArea">Current assembly view.</param>
1204 /// <param name="wasTextSetup">Whether text was set in <see cref="_textArea"/>.</param>
1205 /// <param name="target">Target job to find function in.</param>
1206 /// <returns>Whether a focus was attempted or not.</returns>
1207 private bool TryFocusJobInAssemblyView(ref Rect workingArea, bool wasTextSetup, BurstCompileTarget target)
1208 {
1209 bool TryFindByLabel(ref Rect workingArea)
1210 {
1211 var regx = default(Regex);
1212 var sb = new StringBuilder();
1213 if (target.IsStaticMethod)
1214 {
1215 // Search for fullname as label
1216 // Null reference not a danger, because of target being a static method
1217 sb.Append(target.Method.DeclaringType.ToString().Replace("+", "."));
1218
1219 // Generic labels will be sorounded by "", while standard static methods won't
1220 var genericArguments = target.JobType.GenericTypeArguments;
1221 if (genericArguments.Length > 0)
1222 {
1223 // Need to alter the generic arguments from [] to <> form
1224 // Removing [] form
1225 var idx = sb.ToString().LastIndexOf('[');
1226 sb.Remove(idx, sb.Length - idx);
1227
1228 // Adding <> form
1229 sb.Append('<').Append(BurstCompileTarget.Pretty(genericArguments[0]));
1230 for (var i = 1; i < genericArguments.Length; i++)
1231 {
1232 sb.Append(",").Append(BurstCompileTarget.Pretty(genericArguments[i]));
1233 }
1234 sb.Append('>').Append('.').Append(target.Method.Name);
1235 }
1236 else
1237 {
1238 sb.Append('.').Append(target.Method.Name);
1239 }
1240
1241 const RegexOptions opt = RegexOptions.Compiled | RegexOptions.CultureInvariant;
1242 regx = new Regex(@$"{Regex.Escape(sb.ToString())}[^"":]+"":", opt);
1243 }
1244 else
1245 {
1246 // Append full method name. Using display name for simpler access
1247 var targetName = target.GetDisplayName();
1248 // Remove part that tells about used interface
1249 var idx = 0;
1250 // If generic the argument part must also be removed, as they won't match
1251 if ((idx = targetName.IndexOf('[')) == -1) idx = targetName.IndexOf('-') - 1;
1252 targetName = targetName.Remove(idx);
1253
1254 sb.Append($@""".*<{Regex.Escape(targetName)}.*"":");
1255
1256 const RegexOptions opt = RegexOptions.Compiled | RegexOptions.CultureInvariant;
1257 regx = new Regex(sb.ToString(), opt);
1258 }
1259
1260 var sc = new SearchCriteria(sb.ToString(), false, false, true);
1261
1262 return _textArea.SearchText(sc, regx, ref workingArea, true, true);
1263 }
1264
1265 var foundTarget = false;
1266 // _isTextSetLastEvent used so we call this at the first scroll-able event after text was set.
1267 // We cannot scroll during used or layout events, and the order of events are:
1268 // 1. Used event: text is set in textArea
1269 // 2. Layout event: Cannot do the jump yet
1270 // 3. Repaint event: Now jump is doable
1271 // Hence _isTextSetLastEvent is only set on layout events (during phase 2)
1272 if (wasTextSetup)
1273 {
1274 // Need to call Layout to setup fontsize before searching
1275 _textArea.Layout(fixedFontStyle, _textArea.horizontalPad);
1276
1277 foundTarget = TryFindByLabel(ref workingArea);
1278 _textArea.StopSearching(); // Clear the internals of _textArea from this search; to avoid highlighting
1279
1280 // Clear other possible search, so it won't interfere with this.
1281 _searchFilterAssembly = string.Empty;
1282
1283 // We need to do a Repaint() in order for the view to actually update immediately.
1284 if (foundTarget) Repaint();
1285 }
1286
1287 return foundTarget;
1288 }
1289
1290 private void EnableDisableSearchBar()
1291 {
1292 _searchBarVisible = !_searchBarVisible;
1293
1294 if (_searchBarVisible && _searchFieldAssembly != null)
1295 {
1296 _searchFieldAssembly.SetFocus();
1297 }
1298 else if (!_searchBarVisible)
1299 {
1300 _textArea.StopSearching();
1301 }
1302 }
1303 private bool _doIgnoreCase = false;
1304 private bool _doWholeWordMatch = false;
1305 private bool _doRegex = false;
1306
1307 internal static string GetDisassembly(MethodInfo method, string options)
1308 {
1309 try
1310 {
1311 var result = BurstCompilerService.GetDisassembly(method, options);
1312 if (result.IndexOf('\t') >= 0)
1313 {
1314 result = result.Replace("\t", " ");
1315 }
1316
1317 // Workaround to remove timings
1318 if (result.Contains("Burst timings"))
1319 {
1320 var index = result.IndexOf("While compiling", StringComparison.Ordinal);
1321 if (index > 0)
1322 {
1323 result = result.Substring(index);
1324 }
1325 }
1326
1327 return result;
1328 }
1329 catch (Exception e)
1330 {
1331 return "Failed to compile:\n" + e.Message;
1332 }
1333 }
1334
1335 internal static BurstDisassembler.AsmKind FetchAsmKind(BurstTargetCpu cpu, DisassemblyKind kind)
1336 {
1337 if (kind == DisassemblyKind.Asm)
1338 {
1339 switch (cpu)
1340 {
1341 case BurstTargetCpu.Auto:
1342 string cpuType = BurstCompiler.GetTargetCpuFromHost();
1343 if (cpuType.Contains("Arm"))
1344 {
1345 return BurstDisassembler.AsmKind.ARM;
1346 }
1347 return BurstDisassembler.AsmKind.Intel;
1348 case BurstTargetCpu.ARMV7A_NEON32:
1349 case BurstTargetCpu.ARMV8A_AARCH64:
1350 case BurstTargetCpu.ARMV8A_AARCH64_HALFFP:
1351 case BurstTargetCpu.THUMB2_NEON32:
1352 case BurstTargetCpu.ARMV9A:
1353 return BurstDisassembler.AsmKind.ARM;
1354 case BurstTargetCpu.WASM32:
1355 return BurstDisassembler.AsmKind.Wasm;
1356 default:
1357 return BurstDisassembler.AsmKind.Intel;
1358 }
1359 }
1360 else
1361 {
1362 return BurstDisassembler.AsmKind.LLVMIR;
1363 }
1364 }
1365 }
1366
1367 /// <summary>
1368 /// Important: id for namespaces are negative, and ids for jobs are positive.
1369 /// This lets us use the id for a job as an index directy into <see cref="_targets"/>.
1370 /// Hence before going from <see cref="TreeViewItem"/> to <see cref="_targets"/> index,
1371 /// One should check whether current item has any children (Only jobs are leafs).
1372 /// </summary>
1373 internal class BurstMethodTreeView : TreeView
1374 {
1375 private readonly Func<string> _getFilter;
1376 private readonly Func<(bool,bool)> _getJobListFilterToggles;
1377
1378 private List<BurstCompileTarget> _targets;
1379
1380 public BurstMethodTreeView(TreeViewState state, Func<string> getFilter, Func<(bool,bool)> getJobListFilterToggles) : base(state)
1381 {
1382 _getFilter = getFilter;
1383 _getJobListFilterToggles = getJobListFilterToggles;
1384 showBorder = true;
1385 }
1386
1387 public void Initialize(List<BurstCompileTarget> targets, bool identicalTargets)
1388 {
1389 _targets = targets;
1390 Reload();
1391 if (!identicalTargets) { ExpandAll(); }
1392 }
1393
1394 /// <remarks>
1395 /// Assumes that <see cref="str"/> is derived from <see cref="Type"/>.<see cref="Type.FullName"/>
1396 /// i.e. types are separated by '+'.
1397 /// </remarks>
1398 /// <param name="str">Given type name string.</param>
1399 /// <returns>(List of namespaces/types, index of method name in <see cref="str"/>)</returns>
1400 internal static (List<StringSlice> ns, int nsEndIdx) ExtractNameSpaces(in string str)
1401 {
1402 if (str is null) { throw new ArgumentNullException(nameof(str)); }
1403
1404 var nameSpaces = new List<StringSlice>();
1405 int len = str.Length;
1406 int scope = 0;
1407 int previdx = 0;
1408 for (int i = 0; i < len; i++)
1409 {
1410 bool stop = false;
1411 char c = str[i];
1412 switch (c)
1413 {
1414 case '(':
1415 // Jump out as we just found argument list!!!
1416 stop = true;
1417 break;
1418 // We keep looking, as classes might have these in name:
1419 case '{':
1420 case '<':
1421 case '[':
1422 scope++;
1423 break;
1424 case '}':
1425 case '>':
1426 case ']':
1427 scope--;
1428 break;
1429 case '+' when scope == 0:
1430 nameSpaces.Add(new StringSlice(str, previdx, i - previdx));
1431 previdx = i + 1;
1432 break;
1433 }
1434
1435 if (stop) { break; }
1436 }
1437 return (nameSpaces, previdx);
1438 }
1439
1440 internal static (int idN, List<TreeViewItem> added, List<StringSlice> nameSpace)
1441 ProcessNewItem(int idN, int idJ, BurstCompileTarget newTarget, List<StringSlice> oldNameSpace)
1442 {
1443 // Find all namespaces used for new target:
1444 string fns = newTarget.JobType.FullName;
1445 string dn = newTarget.GetDisplayName();
1446
1447 (List<StringSlice> newNameSpaces, int nameSpaceEndIdx) = ExtractNameSpaces(fns);
1448
1449 int methodNameIdx = nameSpaceEndIdx;
1450 if (newTarget.IsStaticMethod)
1451 {
1452 // Static method does not have the function name in fns, so fix methodNameIdx.
1453 methodNameIdx = dn.IndexOf('(', methodNameIdx) - newTarget.Method.Name.Length;
1454 // Add the last namespace:
1455 newNameSpaces.Add(new StringSlice(dn, nameSpaceEndIdx, methodNameIdx-1 - nameSpaceEndIdx));
1456 }
1457 string methodName = dn.Substring(methodNameIdx);
1458
1459 int iNewNs = 0;
1460 int lNewNs = newNameSpaces.Count;
1461 int iOldNs = 0;
1462 int lOldNs = oldNameSpace.Count;
1463
1464 var added = new List<TreeViewItem>(lNewNs);
1465 int depth = 0;
1466
1467 // Skip all namespaces shared by previous but increase depth accordingly:
1468 for (; iNewNs < lNewNs && iOldNs < lOldNs && newNameSpaces[iNewNs] == oldNameSpace[iOldNs];
1469 depth++, iNewNs++, iOldNs++) {}
1470
1471 // Handle all new namespaces:
1472 for (; iNewNs < lNewNs;
1473 depth++, iNewNs++)
1474 {
1475 added.Add(new TreeViewItem { id = --idN, depth = depth, displayName = newNameSpaces[iNewNs].ToString()});
1476 }
1477
1478 // Add the function name:
1479 added.Add(new TreeViewItem { id = idJ, depth = depth, displayName = methodName });
1480
1481 return (idN, added, newNameSpaces);
1482 }
1483
1484 protected override TreeViewItem BuildRoot()
1485 {
1486 var root = new TreeViewItem {id = 0, depth = -1, displayName = "Root"};
1487 var allItems = new List<TreeViewItem>();
1488
1489 if (_targets != null)
1490 {
1491 var filter = _getFilter();
1492 var (showUnityNamespaceJobs, showDOTSGeneratedJobs) = _getJobListFilterToggles();
1493 // Have two separate ids so "jobs ids == jobs index".
1494 int idJ = 0;
1495 int idN = 0;
1496 var oldNameSpace = new List<StringSlice>();
1497 foreach (BurstCompileTarget target in _targets)
1498 {
1499 // idJ used as index into _targets, which means it should also take hidden targets into account!
1500 idJ++;
1501
1502 string displayName = target.GetDisplayName();
1503
1504 bool filtered =
1505 (!string.IsNullOrEmpty(filter) &&
1506 displayName.IndexOf(filter, 0, displayName.Length,
1507 StringComparison.InvariantCultureIgnoreCase) < 0)
1508 || (!showUnityNamespaceJobs &&
1509 displayName.StartsWith("Unity.", StringComparison.InvariantCultureIgnoreCase))
1510 || (!showDOTSGeneratedJobs &&
1511 displayName.Contains(".Generated"));
1512
1513 if (filtered) { continue; }
1514
1515 try
1516 {
1517 var (newIdN, added, nameSpace) =
1518 ProcessNewItem(idN, idJ, target, oldNameSpace);
1519
1520 allItems.AddRange(added);
1521 idN = newIdN;
1522 oldNameSpace = nameSpace;
1523 }
1524 catch (Exception ex)
1525 {
1526 Debug.Log($"Internal error: Could not add {displayName}\n Because: {ex.Message}");
1527 }
1528 }
1529 }
1530 SetupParentsAndChildrenFromDepths(root, allItems);
1531 return root;
1532 }
1533
1534 public new IList<int> GetSelection()
1535 {
1536 IList<int> selection = base.GetSelection();
1537 // selection == non-leaf node => no job selected
1538 if (selection.Count > 0 && selection[0] < 0) { return new List<int>(); }
1539 return selection;
1540 }
1541
1542 internal bool TrySelectByDisplayName(string name)
1543 {
1544 var id = 1;
1545 foreach (var t in _targets)
1546 {
1547 if (t.GetDisplayName() == name)
1548 {
1549 try
1550 {
1551 SetSelection(new[] { id });
1552 FrameItem(id);
1553 return true;
1554 }
1555 catch (ArgumentException)
1556 {
1557 // When a search is made in the job list, such that the job we search for is filtered away
1558 // FrameItem(id) will throw a dictionary error. So we catch this, and tell the caller that
1559 // it cannot be selected.
1560 return false;
1561 }
1562 }
1563 else
1564 {
1565 ++id;
1566 }
1567 }
1568 return false;
1569 }
1570
1571 protected override void RowGUI(RowGUIArgs args)
1572 {
1573 if (!args.item.hasChildren)
1574 {
1575 var target = _targets[args.item.id - 1];
1576 var wasEnabled = GUI.enabled;
1577 GUI.enabled = target.HasRequiredBurstCompileAttributes;
1578 base.RowGUI(args);
1579 GUI.enabled = wasEnabled;
1580 }
1581 else
1582 {
1583 // Label GUI:
1584 base.RowGUI(args);
1585 }
1586 }
1587
1588 protected override void SingleClickedItem(int id)
1589 {
1590 // If labeled click try and fold/expand:
1591 if (id < 0)
1592 {
1593 SetExpanded(id, !IsExpanded(id));
1594 SetSelection(new List<int>());
1595 }
1596 }
1597
1598 protected override bool CanMultiSelect(TreeViewItem item)
1599 {
1600 return false;
1601 }
1602 }
1603}