A game about forced loneliness, made by TACStudios
at master 1603 lines 67 kB view raw
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}