1572 lines
		
	
	
		
			64 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			1572 lines
		
	
	
		
			64 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|   | using System; | |||
|  | using System.Collections.Generic; | |||
|  | using System.Linq; | |||
|  | using System.Reflection; | |||
|  | using System.Runtime.CompilerServices; | |||
|  | using System.Text; | |||
|  | using System.Threading.Tasks; | |||
|  | using Unity.Burst.LowLevel; | |||
|  | using UnityEditor; | |||
|  | using System.Text.RegularExpressions; | |||
|  | using UnityEditor.IMGUI.Controls; | |||
|  | using UnityEngine; | |||
|  | 
 | |||
|  | [assembly: InternalsVisibleTo("Unity.Burst.Editor.Tests")] | |||
|  | [assembly: InternalsVisibleTo("Unity.Burst.Tester.Editor.Tests")] | |||
|  | 
 | |||
|  | namespace Unity.Burst.Editor | |||
|  | { | |||
|  |     internal class BurstInspectorGUI : EditorWindow | |||
|  |     { | |||
|  |         private static bool Initialized; | |||
|  | 
 | |||
|  |         private static void EnsureInitialized() | |||
|  |         { | |||
|  |             if (Initialized) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             Initialized = true; | |||
|  | 
 | |||
|  |             BurstLoader.OnBurstShutdown += () => | |||
|  |             { | |||
|  |                 if (EditorWindow.HasOpenInstances<BurstInspectorGUI>()) | |||
|  |                 { | |||
|  |                     var window = EditorWindow.GetWindow<BurstInspectorGUI>("Burst Inspector"); | |||
|  |                     window.Close(); | |||
|  |                 } | |||
|  |             }; | |||
|  |         } | |||
|  | 
 | |||
|  |         private const string FontSizeIndexPref = "BurstInspectorFontSizeIndex"; | |||
|  | 
 | |||
|  |         private static readonly string[] DisassemblyKindNames = | |||
|  |         { | |||
|  |             "Assembly", | |||
|  |             ".NET IL", | |||
|  |             "LLVM IR (Unoptimized)", | |||
|  |             "LLVM IR (Optimized)", | |||
|  |             "LLVM IR Optimisation Diagnostics" | |||
|  |         }; | |||
|  | 
 | |||
|  |         internal enum AssemblyOptions | |||
|  |         { | |||
|  |             PlainWithoutDebugInformation = 0, | |||
|  |             PlainWithDebugInformation = 1, | |||
|  |             EnhancedWithMinimalDebugInformation = 2, | |||
|  |             EnhancedWithFullDebugInformation = 3, | |||
|  |             ColouredWithMinimalDebugInformation = 4, | |||
|  |             ColouredWithFullDebugInformation = 5 | |||
|  |         } | |||
|  |         internal AssemblyOptions? _assemblyKind = null; | |||
|  |         private AssemblyOptions? _assemblyKindPrior = null; | |||
|  |         private AssemblyOptions _oldAssemblyKind; | |||
|  | 
 | |||
|  |         private bool SupportsEnhancedRendering => _disasmKind == DisassemblyKind.Asm || _disasmKind == DisassemblyKind.OptimizedIR || _disasmKind == DisassemblyKind.UnoptimizedIR; | |||
|  | 
 | |||
|  |         private static string[] DisasmOptions; | |||
|  | 
 | |||
|  |         internal static string[] GetDisasmOptions() | |||
|  |         { | |||
|  |             if (DisasmOptions == null) | |||
|  |             { | |||
|  |                 // We can't initialize this in BurstInspectorGUI.cctor because BurstCompilerOptions may not yet | |||
|  |                 // have been initialized by BurstLoader. So we initialize on-demand here. This method doesn't need to | |||
|  |                 // be thread-safe because it's only called from the UI thread. | |||
|  |                 DisasmOptions = new[] | |||
|  |                 { | |||
|  |                     "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.Asm), | |||
|  |                     "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IL), | |||
|  |                     "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IR), | |||
|  |                     "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IROptimized), | |||
|  |                     "\n" + BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDump, NativeDumpFlags.IRPassAnalysis) | |||
|  |                 }; | |||
|  |             } | |||
|  |             return DisasmOptions; | |||
|  |         } | |||
|  | 
 | |||
|  |         private static readonly SplitterState TreeViewSplitterState = new SplitterState(new float[] { 30, 70 }, new int[] { 128, 128 }, null); | |||
|  | 
 | |||
|  |         private static readonly string[] TargetCpuNames = Enum.GetNames(typeof(BurstTargetCpu)); | |||
|  |         private static readonly string[] SIMDSmellTest = { "False", "True" }; | |||
|  | 
 | |||
|  |         private static readonly int[] FontSizes = | |||
|  |         { | |||
|  |             8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20 | |||
|  |         }; | |||
|  | 
 | |||
|  |         private static string[] _fontSizesText; | |||
|  |         internal const int _scrollbarThickness = 14; | |||
|  | 
 | |||
|  |         internal float _buttonOverlapInspectorView = 0; | |||
|  | 
 | |||
|  |         /// <remarks>Used because it's not legal to change layout of GUI in a frame without the users input.</remarks> | |||
|  |         private float _buttonBarWidth = -1; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         internal readonly BurstDisassembler _burstDisassembler; | |||
|  | 
 | |||
|  |         private const string BurstSettingText = "Inspector Settings/"; | |||
|  | 
 | |||
|  |         [SerializeField] private BurstTargetCpu _targetCpu = BurstTargetCpu.Auto; | |||
|  | 
 | |||
|  |         [SerializeField] private DisassemblyKind _disasmKind = DisassemblyKind.Asm; | |||
|  |         [SerializeField] private DisassemblyKind _oldDisasmKind = DisassemblyKind.Asm; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         internal GUIStyle fixedFontStyle; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         internal int fontSizeIndex = -1; | |||
|  | 
 | |||
|  |         [SerializeField] private int _previousTargetIndex = -1; | |||
|  | 
 | |||
|  |         [SerializeField] private bool _safetyChecks = false; | |||
|  |         [SerializeField] private bool _showBranchMarkers = true; | |||
|  |         [SerializeField] private bool _enhancedDisassembly = true; | |||
|  |         [SerializeField] private string _searchFilterJobs; | |||
|  |         [SerializeField] private bool _showUnityNamespaceJobs = false; | |||
|  |         [SerializeField] private bool _showDOTSGeneratedJobs = false; | |||
|  |         [SerializeField] private bool _focusTargetJob = true; | |||
|  |         [SerializeField] private string _searchFilterAssembly = String.Empty; | |||
|  | 
 | |||
|  |         [SerializeField] private bool _sameTargetButDifferentAssemblyKind = false; | |||
|  |         [SerializeField] internal Vector2 _scrollPos; | |||
|  |         internal SearchField _searchFieldJobs; | |||
|  |         internal SearchField _searchFieldAssembly; | |||
|  |         private bool saveSearchFieldFromEvent = false; | |||
|  | 
 | |||
|  |         [SerializeField] private bool _searchBarVisible = true; | |||
|  | 
 | |||
|  |         [SerializeField] private string _selectedItem; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         private BurstCompileTarget _target; | |||
|  |         [NonSerialized] | |||
|  |         private List<BurstCompileTarget> _targets; | |||
|  |         // Used as a serialized representation of _targets: | |||
|  |         [SerializeField] private List<string> targetNames; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         internal LongTextArea _textArea; | |||
|  | 
 | |||
|  |         internal Rect _inspectorView; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         internal Font _font; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         internal BurstMethodTreeView _treeView; | |||
|  |         // Serialized representation of _treeView: | |||
|  |         [SerializeField] private TreeViewState treeViewState; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         internal bool _initialized; | |||
|  | 
 | |||
|  |         [NonSerialized] | |||
|  |         private bool _requiresRepaint; | |||
|  | 
 | |||
|  |         private int FontSize => FontSizes[fontSizeIndex]; | |||
|  | 
 | |||
|  |         private static readonly Regex _rx = new Regex(@"^.*\(\d+,\d+\):\sBurst\serror"); | |||
|  | 
 | |||
|  |         private bool _leftClicked = false; | |||
|  | 
 | |||
|  |         [SerializeField] private bool _isCompileError = false; | |||
|  |         [SerializeField] private bool _prevWasCompileError; | |||
|  | 
 | |||
|  |         [SerializeField] private bool _smellTest = false; | |||
|  | 
 | |||
|  |         // Caching GUIContent and style options for button bar | |||
|  |         private readonly GUIContent _contentShowUnityNamespaceJobs = new GUIContent("Show Unity Namespace"); | |||
|  |         private readonly GUIContent _contentShowDOTSGeneratedJobs = new GUIContent("Show \".Generated\""); | |||
|  |         private readonly GUIContent _contentDisasm = new GUIContent("Enhanced With Minimal Debug Information"); | |||
|  |         private readonly GUIContent _contentCollapseToCode = new GUIContent("Focus on Code"); | |||
|  |         private readonly GUIContent _contentExpandAll = new GUIContent("Expand All"); | |||
|  |         private readonly GUIContent _contentBranchLines = new GUIContent("Show Branch Flow"); | |||
|  |         private readonly GUIContent[] _contentsTarget = | |||
|  |             Enum.GetNames(typeof(BurstTargetCpu)).Select(str => new GUIContent($"Target ({str})")).ToArray(); | |||
|  |         private readonly GUIContent[] _contentsFontSize = | |||
|  |             FontSizes.Select(v => new GUIContent($"Font Size ({v})")).ToArray(); | |||
|  |         private readonly GUIContent[] _contentsSmellTest = | |||
|  |         { | |||
|  |             new GUIContent("Highlight SIMD Scalar vs Packed (False)"), | |||
|  |             new GUIContent("Highlight SIMD Scalar vs Packed (True)") | |||
|  |         }; | |||
|  | 
 | |||
|  |         // content for button search bar | |||
|  |         private readonly GUIContent _ignoreCase = new GUIContent("Match Case"); | |||
|  |         private readonly GUIContent _matchWord = new GUIContent("Whole words"); | |||
|  |         private readonly GUIContent _regexSearch = new GUIContent("Regex"); | |||
|  | 
 | |||
|  |         private readonly GUILayoutOption[] _toolbarStyleOptions = { GUILayout.ExpandWidth(true), GUILayout.MinWidth(5 * 10) }; | |||
|  | 
 | |||
|  |         private readonly string[] _branchMarkerOptions = { "Hide Branch Flow", "Show Branch Flow" }; | |||
|  |         private readonly string[] _safetyCheckOptions = { "Safety Check On", "Safety Check Off" }; | |||
|  | 
 | |||
|  | 
 | |||
|  |         private enum KeyboardOperation | |||
|  |         { | |||
|  |             SelectAll, | |||
|  |             Copy, | |||
|  |             MoveLeft, | |||
|  |             MoveRight, | |||
|  |             MoveUp, | |||
|  |             MoveDown, | |||
|  |             Search, | |||
|  |             Escape, | |||
|  |             Enter, | |||
|  |         } | |||
|  | 
 | |||
|  |         private Dictionary<Event, KeyboardOperation> _keyboardEvents; | |||
|  | 
 | |||
|  |         private void FillKeyboardEvent() | |||
|  |         { | |||
|  |             if (_keyboardEvents != null) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             _keyboardEvents = new Dictionary<Event, KeyboardOperation>(); | |||
|  | 
 | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("#left"), KeyboardOperation.MoveLeft); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("#right"), KeyboardOperation.MoveRight); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("#down"), KeyboardOperation.MoveDown); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("#up"), KeyboardOperation.MoveUp); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("escape"), KeyboardOperation.Escape); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("return"), KeyboardOperation.Enter); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("#return"), KeyboardOperation.Enter); | |||
|  | 
 | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("left"), KeyboardOperation.MoveLeft); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("right"), KeyboardOperation.MoveRight); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("up"), KeyboardOperation.MoveUp); | |||
|  |             _keyboardEvents.Add(Event.KeyboardEvent("down"), KeyboardOperation.MoveDown); | |||
|  | 
 | |||
|  |             if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) | |||
|  |             { | |||
|  |                 _keyboardEvents.Add(Event.KeyboardEvent("%a"), KeyboardOperation.SelectAll); | |||
|  |                 _keyboardEvents.Add(Event.KeyboardEvent("%c"), KeyboardOperation.Copy); | |||
|  |                 _keyboardEvents.Add(Event.KeyboardEvent("%f"), KeyboardOperation.Search); | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 // windows or linux bindings. | |||
|  |                 _keyboardEvents.Add(Event.KeyboardEvent("^a"), KeyboardOperation.SelectAll); | |||
|  |                 _keyboardEvents.Add(Event.KeyboardEvent("^c"), KeyboardOperation.Copy); | |||
|  |                 _keyboardEvents.Add(Event.KeyboardEvent("^f"), KeyboardOperation.Search); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         public BurstInspectorGUI() | |||
|  |         { | |||
|  |             _burstDisassembler = new BurstDisassembler(); | |||
|  |         } | |||
|  | 
 | |||
|  |         private bool DisplayAssemblyKind(Enum assemblyKind) | |||
|  |         { | |||
|  |             var assemblyOption = (AssemblyOptions)assemblyKind; | |||
|  |             if (_disasmKind != DisassemblyKind.Asm || _isCompileError) | |||
|  |             { | |||
|  |                 return assemblyOption == AssemblyOptions.PlainWithoutDebugInformation; | |||
|  |             } | |||
|  |             return true; | |||
|  |         } | |||
|  | 
 | |||
|  |         public void OnEnable() | |||
|  |         { | |||
|  |             EnsureInitialized(); | |||
|  | 
 | |||
|  |             var newTreeState = false; | |||
|  |             if (treeViewState is null) | |||
|  |             { | |||
|  |                 treeViewState = new TreeViewState(); | |||
|  |                 newTreeState = true; | |||
|  |             } | |||
|  |             _treeView ??= _treeView = new BurstMethodTreeView | |||
|  |             ( | |||
|  |                 treeViewState, | |||
|  |                 () => _searchFilterJobs, | |||
|  |                 () => (_showUnityNamespaceJobs, _showDOTSGeneratedJobs) | |||
|  |             ); | |||
|  | 
 | |||
|  |             if (_keyboardEvents == null) FillKeyboardEvent(); | |||
|  | 
 | |||
|  |             var assemblyList = BurstReflection.EditorAssembliesThatCanPossiblyContainJobs; | |||
|  | 
 | |||
|  |             Task.Run( | |||
|  |                     () => | |||
|  |                     { | |||
|  |                         // Do this stuff asynchronously. | |||
|  |                         var result = BurstReflection.FindExecuteMethods(assemblyList, BurstReflectionAssemblyOptions.None); | |||
|  |                         _targets = result.CompileTargets; | |||
|  |                         _targets.Sort((left, right) => string.Compare(left.GetDisplayName(), right.GetDisplayName(), StringComparison.Ordinal)); | |||
|  |                         return result; | |||
|  |                     }) | |||
|  |                 .ContinueWith(t => | |||
|  |                 { | |||
|  |                     // Do this stuff on the main (UI) thread. | |||
|  |                     if (t.Status == TaskStatus.RanToCompletion) | |||
|  |                     { | |||
|  |                         foreach (var logMessage in t.Result.LogMessages) | |||
|  |                         { | |||
|  |                             switch (logMessage.LogType) | |||
|  |                             { | |||
|  |                                 case BurstReflection.LogType.Warning: | |||
|  |                                     Debug.LogWarning(logMessage.Message); | |||
|  |                                     break; | |||
|  |                                 case BurstReflection.LogType.Exception: | |||
|  |                                     Debug.LogException(logMessage.Exception); | |||
|  |                                     break; | |||
|  |                                 default: | |||
|  |                                     throw new InvalidOperationException(); | |||
|  |                             } | |||
|  |                         } | |||
|  | 
 | |||
|  |                         List<string> newNames = _targets.Select(x => x.GetDisplayName()).ToList(); | |||
|  |                         bool identical = !newTreeState && newNames.SequenceEqual(targetNames); | |||
|  |                         targetNames = newNames; | |||
|  |                         _treeView.Initialize(_targets, identical); | |||
|  | 
 | |||
|  |                         if (_selectedItem == null || !_treeView.TrySelectByDisplayName(_selectedItem)) | |||
|  |                         { | |||
|  |                             _previousTargetIndex = -1; | |||
|  |                             _scrollPos = Vector2.zero; | |||
|  |                         } | |||
|  | 
 | |||
|  |                         _requiresRepaint = true; | |||
|  |                         _initialized = true; | |||
|  |                     } | |||
|  |                     else if (t.Exception != null) | |||
|  |                     { | |||
|  |                         Debug.LogError($"Could not load Inspector: {t.Exception}"); | |||
|  |                     } | |||
|  |                 }); | |||
|  |         } | |||
|  | 
 | |||
|  | #if !UNITY_2023_1_OR_NEWER | |||
|  |         private void CleanupFont() | |||
|  |         { | |||
|  |             if (_font != null) | |||
|  |             { | |||
|  |                 DestroyImmediate(_font, true); | |||
|  |                 _font = null; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         public void OnDisable() | |||
|  |         { | |||
|  |             CleanupFont(); | |||
|  |         } | |||
|  | #endif | |||
|  | 
 | |||
|  |         public void Update() | |||
|  |         { | |||
|  |             // Need to do this because if we call Repaint from anywhere else, | |||
|  |             // it doesn't do anything if this window is not currently focused. | |||
|  |             if (_requiresRepaint) | |||
|  |             { | |||
|  |                 Repaint(); | |||
|  |                 _requiresRepaint = false; | |||
|  |             } | |||
|  | 
 | |||
|  |             // Need this because pressing new target, and then not invoking new events, | |||
|  |             // will leave the assembly unrendered. | |||
|  |             // This is not included in above, to minimize needed calls. | |||
|  |             if (_target != null && _target.JustLoaded) | |||
|  |             { | |||
|  |                 Repaint(); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Checks if there is space for given content withs style, and starts new horizontalgroup | |||
|  |         /// if there is no space on this line. | |||
|  |         /// </summary> | |||
|  |         private void FlowToNewLine(ref float remainingWidth, float width, Vector2 size) | |||
|  |         { | |||
|  |             float sizeX = size.x + _scrollbarThickness / 2; | |||
|  |             if (sizeX >= remainingWidth) | |||
|  |             { | |||
|  |                 _buttonOverlapInspectorView += size.y + 2; | |||
|  |                 remainingWidth = width - sizeX; | |||
|  |                 GUILayout.EndHorizontal(); | |||
|  |                 GUILayout.BeginHorizontal(); | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 remainingWidth -= sizeX; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private bool IsRaw(AssemblyOptions kind) | |||
|  |         { | |||
|  |             return kind == AssemblyOptions.PlainWithoutDebugInformation || kind == AssemblyOptions.PlainWithDebugInformation; | |||
|  |         } | |||
|  | 
 | |||
|  |         private bool IsEnhanced(AssemblyOptions kind) | |||
|  |         { | |||
|  |             return !IsRaw(kind); | |||
|  |         } | |||
|  | 
 | |||
|  |         private bool IsColoured(AssemblyOptions kind) | |||
|  |         { | |||
|  |             return kind == AssemblyOptions.ColouredWithMinimalDebugInformation || kind == AssemblyOptions.ColouredWithFullDebugInformation; | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Renders buttons bar, and handles saving/loading of _assemblyKind options when changing in inspector settings | |||
|  |         /// that disable/enables some options for _assemblyKind. | |||
|  |         /// </summary> | |||
|  |         private void HandleButtonBars(BurstCompileTarget target, bool targetChanged, out int fontIndex, out bool collapse, out bool focusCode) | |||
|  |         { | |||
|  |             // We can only make an educated guess for the correct width. | |||
|  |             if (_buttonBarWidth == -1) | |||
|  |             { | |||
|  |                 _buttonBarWidth = (position.width * 2) / 3 - _scrollbarThickness; | |||
|  |             } | |||
|  | 
 | |||
|  |             RenderButtonBars(_buttonBarWidth, target, out fontIndex, out collapse, out focusCode); | |||
|  | 
 | |||
|  |             var disasmKindChanged = _oldDisasmKind != _disasmKind; | |||
|  | 
 | |||
|  |             // Handles saving and loading _assemblyKind option when going between settings, that disable/enable some options for it | |||
|  |             if ((disasmKindChanged && _oldDisasmKind == DisassemblyKind.Asm && !_isCompileError) | |||
|  |                 || (targetChanged && !_prevWasCompileError && _isCompileError && _disasmKind == DisassemblyKind.Asm)) | |||
|  |             { | |||
|  |                 // save when _disasmKind changed from Asm WHEN we are not looking at a burst compile error, | |||
|  |                 // or when target changed from non compile error to compile error and current _disasmKind is Asm. | |||
|  |                 _oldAssemblyKind = (AssemblyOptions)_assemblyKind; | |||
|  |             } | |||
|  |             else if ((disasmKindChanged && _disasmKind == DisassemblyKind.Asm && !_isCompileError) || | |||
|  |                      (targetChanged && _prevWasCompileError && _disasmKind == DisassemblyKind.Asm)) | |||
|  |             { | |||
|  |                 // load when _diasmKind changed to Asm and we are not at burst compile error, | |||
|  |                 // or when target changed from a burst compile error while _disasmKind is Asm. | |||
|  |                 _assemblyKind = _oldAssemblyKind; | |||
|  |             } | |||
|  | 
 | |||
|  |             // if _assemblyKind is something that is not available, force it up to PlainWithoutDebugInformation. | |||
|  |             if ((_disasmKind != DisassemblyKind.Asm && _assemblyKind != AssemblyOptions.PlainWithoutDebugInformation) | |||
|  |                 || _isCompileError) | |||
|  |             { | |||
|  |                 _assemblyKind = AssemblyOptions.PlainWithoutDebugInformation; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private void RenderButtonBars(float width, BurstCompileTarget target, out int fontIndex, out bool collapse, out bool focus) | |||
|  |         { | |||
|  |             var remainingWidth = width; | |||
|  |             GUILayout.BeginHorizontal(); | |||
|  | 
 | |||
|  |             // First button should never call beginHorizontal(). | |||
|  |             remainingWidth -= (EditorStyles.popup.CalcSize(_contentDisasm).x + _scrollbarThickness / 2f); | |||
|  | 
 | |||
|  |             EditorGUI.BeginDisabledGroup(target.DisassemblyKind == DisassemblyKind.IRPassAnalysis); | |||
|  | 
 | |||
|  |             _assemblyKind = (AssemblyOptions)EditorGUILayout.EnumPopup(GUIContent.none, _assemblyKind, DisplayAssemblyKind, true); | |||
|  | 
 | |||
|  |             EditorGUI.EndDisabledGroup(); | |||
|  | 
 | |||
|  |             FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(_contentBranchLines)); | |||
|  |             // Reversed "logic" to match the array of options, which has "positive" case on idx 0. | |||
|  |             _safetyChecks = EditorGUILayout.Popup(_safetyChecks ? 0 : 1, _safetyCheckOptions) == 0; | |||
|  | 
 | |||
|  |             EditorGUI.BeginDisabledGroup(!target.HasRequiredBurstCompileAttributes); | |||
|  | 
 | |||
|  |             GUIContent targetContent = _contentsTarget[(int)_targetCpu]; | |||
|  |             FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(targetContent)); | |||
|  |             _targetCpu = (BurstTargetCpu)LabeledPopup.Popup((int)_targetCpu, targetContent, TargetCpuNames); | |||
|  | 
 | |||
|  |             GUIContent fontSizeContent = _contentsFontSize[fontSizeIndex]; | |||
|  |             FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(fontSizeContent)); | |||
|  |             fontIndex = LabeledPopup.Popup(fontSizeIndex, fontSizeContent, _fontSizesText); | |||
|  | 
 | |||
|  |             EditorGUI.EndDisabledGroup(); | |||
|  | 
 | |||
|  |             EditorGUI.BeginDisabledGroup(!IsEnhanced((AssemblyOptions)_assemblyKind) || !SupportsEnhancedRendering || _isCompileError); | |||
|  | 
 | |||
|  |             FlowToNewLine(ref remainingWidth, width, EditorStyles.miniButton.CalcSize(_contentCollapseToCode)); | |||
|  |             focus = GUILayout.Button(_contentCollapseToCode, EditorStyles.miniButton); | |||
|  | 
 | |||
|  |             FlowToNewLine(ref remainingWidth, width, EditorStyles.miniButton.CalcSize(_contentExpandAll)); | |||
|  |             collapse = GUILayout.Button(_contentExpandAll, EditorStyles.miniButton); | |||
|  | 
 | |||
|  |             FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(_contentBranchLines)); | |||
|  |             _showBranchMarkers = EditorGUILayout.Popup(Convert.ToInt32(_showBranchMarkers), _branchMarkerOptions) == 1; | |||
|  | 
 | |||
|  |             int smellTestIdx = Convert.ToInt32(_smellTest); | |||
|  |             GUIContent smellTestContent = _contentsSmellTest[smellTestIdx]; | |||
|  |             FlowToNewLine(ref remainingWidth, width, EditorStyles.popup.CalcSize(smellTestContent)); | |||
|  |             _smellTest = LabeledPopup.Popup(smellTestIdx, smellTestContent, SIMDSmellTest) == 1; | |||
|  | 
 | |||
|  |             EditorGUI.EndDisabledGroup(); | |||
|  | 
 | |||
|  |             GUILayout.EndHorizontal(); | |||
|  | 
 | |||
|  |             _oldDisasmKind = _disasmKind; | |||
|  |             _disasmKind = (DisassemblyKind)GUILayout.Toolbar((int)_disasmKind, DisassemblyKindNames, _toolbarStyleOptions); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Handles mouse events for selecting text. | |||
|  |         /// </summary> | |||
|  |         /// <remarks> | |||
|  |         /// Must be called after Render(...), as it uses the mouse events, and Render(...) | |||
|  |         /// need mouse events for buttons etc. | |||
|  |         /// </remarks> | |||
|  |         private void HandleMouseEventForSelection(Rect workingArea, int controlID, bool showBranchMarkers) | |||
|  |         { | |||
|  |             var evt = Event.current; | |||
|  |             var mousePos = evt.mousePosition; | |||
|  | 
 | |||
|  |             if (_textArea.MouseOutsideView(workingArea, mousePos, controlID)) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             switch (evt.type) | |||
|  |             { | |||
|  |                 case EventType.MouseDown: | |||
|  |                     // button 0 is left and 1 is right | |||
|  |                     if (evt.button == 0) | |||
|  |                     { | |||
|  |                         _textArea.MouseClicked(showBranchMarkers, evt.shift, mousePos, controlID); | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         _leftClicked = true; | |||
|  |                     } | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  |                 case EventType.MouseDrag: | |||
|  |                     _textArea.DragMouse(mousePos, showBranchMarkers); | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  |                 case EventType.MouseUp: | |||
|  |                     _textArea.MouseReleased(); | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  |                 case EventType.ScrollWheel: | |||
|  |                     _textArea.DoScroll(workingArea, evt.delta.y); | |||
|  |                     // we cannot Use() (consume) scrollWheel events, as they are still needed in EndScrollView. | |||
|  |                     break; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private bool AssemblyFocused() => !((_treeView != null && _treeView.HasFocus()) || (_searchFieldAssembly != null && _searchFieldAssembly.HasFocus())); | |||
|  | 
 | |||
|  |         private void HandleKeyboardEventAssemblyView(Rect workingArea, KeyboardOperation op, Event evt, bool showBranchMarkers) | |||
|  |         { | |||
|  |             switch (op) | |||
|  |             { | |||
|  |                 case KeyboardOperation.SelectAll: | |||
|  |                     _textArea.SelectAll(); | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  | 
 | |||
|  |                 case KeyboardOperation.Copy: | |||
|  |                     _textArea.DoSelectionCopy(); | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  | 
 | |||
|  |                 case KeyboardOperation.MoveLeft: | |||
|  |                     if (evt.shift) | |||
|  |                     { | |||
|  |                         if (_textArea.HasSelection) _textArea.MoveSelectionLeft(workingArea, showBranchMarkers); | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         _textArea.MoveView(LongTextArea.Direction.Left, workingArea); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  | 
 | |||
|  |                 case KeyboardOperation.MoveRight: | |||
|  |                     if (evt.shift) | |||
|  |                     { | |||
|  |                         if (_textArea.HasSelection) _textArea.MoveSelectionRight(workingArea, showBranchMarkers); | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         _textArea.MoveView(LongTextArea.Direction.Right, workingArea); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  | 
 | |||
|  |                 case KeyboardOperation.MoveUp: | |||
|  |                     if (evt.shift) | |||
|  |                     { | |||
|  |                         if (_textArea.HasSelection) _textArea.MoveSelectionUp(workingArea, showBranchMarkers); | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         _textArea.MoveView(LongTextArea.Direction.Up, workingArea); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  | 
 | |||
|  |                 case KeyboardOperation.MoveDown: | |||
|  |                     if (evt.shift) | |||
|  |                     { | |||
|  |                         if (_textArea.HasSelection) _textArea.MoveSelectionDown(workingArea, showBranchMarkers); | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         _textArea.MoveView(LongTextArea.Direction.Down, workingArea); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  |                 case KeyboardOperation.Search: | |||
|  |                     _searchBarVisible = true; | |||
|  |                     _searchFieldAssembly?.SetFocus(); | |||
|  |                     evt.Use(); | |||
|  |                     break; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <remarks> | |||
|  |         /// Must be called after Render(...) because of depenency on LongTextArea.finalAreaSize. | |||
|  |         /// </remarks> | |||
|  |         private void HandleKeyboardEventForSelection(Rect workingArea, bool showBranchMarkers) | |||
|  |         { | |||
|  |             var evt = Event.current; | |||
|  | 
 | |||
|  |             if (!_keyboardEvents.TryGetValue(evt, out var op)) | |||
|  |             { | |||
|  |                 return; | |||
|  |             } | |||
|  | 
 | |||
|  |             if (AssemblyFocused()) | |||
|  |             { | |||
|  |                 // Do input handling for assembly view. | |||
|  |                 HandleKeyboardEventAssemblyView(workingArea, op, evt, showBranchMarkers); | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 // This amounts to logic for all else. | |||
|  |                 switch (op) | |||
|  |                 { | |||
|  |                     case KeyboardOperation.Escape: | |||
|  |                         if (_searchFieldAssembly != null && _searchFieldAssembly.HasFocus() && _searchFilterAssembly == "") | |||
|  |                         { | |||
|  |                             _searchBarVisible = false; | |||
|  |                             evt.Use(); | |||
|  |                         } | |||
|  |                         break; | |||
|  |                     case KeyboardOperation.Enter: | |||
|  |                         if (_searchFieldAssembly != null && _searchFieldAssembly.HasFocus()) | |||
|  |                         { | |||
|  |                             _textArea.NextSearchHit(evt.shift, workingArea); | |||
|  |                             saveSearchFieldFromEvent = true; | |||
|  |                             evt.Use(); | |||
|  |                         } | |||
|  |                         break; | |||
|  |                 } | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private void RenderCompileTargetsFilters(float width) | |||
|  |         { | |||
|  |             GUILayout.BeginHorizontal(); | |||
|  |             // Handle and render filtering toggles: | |||
|  |             var newShowUnityTests = GUILayout.Toggle(_showUnityNamespaceJobs, _contentShowUnityNamespaceJobs); | |||
|  | 
 | |||
|  |             FlowToNewLine(ref width, width, EditorStyles.toggle.CalcSize(_contentShowDOTSGeneratedJobs)); | |||
|  |             var newShowDOTSGeneratedJobs = GUILayout.Toggle(_showDOTSGeneratedJobs, _contentShowDOTSGeneratedJobs); | |||
|  |             GUILayout.EndHorizontal(); | |||
|  | 
 | |||
|  |             if (newShowUnityTests != _showUnityNamespaceJobs || newShowDOTSGeneratedJobs != _showDOTSGeneratedJobs) | |||
|  |             { | |||
|  |                 _showDOTSGeneratedJobs = newShowDOTSGeneratedJobs; | |||
|  |                 _showUnityNamespaceJobs = newShowUnityTests; | |||
|  |                 _treeView.Reload(); | |||
|  |             } | |||
|  | 
 | |||
|  |             // Handle and render search filter: | |||
|  |             var newFilter = _searchFieldJobs.OnGUI(_searchFilterJobs); | |||
|  |             if (newFilter != _searchFilterJobs) | |||
|  |             { | |||
|  |                 _searchFilterJobs = newFilter; | |||
|  |                 _treeView.Reload(); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  | 
 | |||
|  |         private void CompileNewTarget(BurstCompileTarget target, BurstCompilerOptions targetOptions) | |||
|  |         { | |||
|  |             if (target.IsLoading) { return; } | |||
|  | 
 | |||
|  |             target.IsLoading = true; | |||
|  |             target.JustLoaded = false; | |||
|  | 
 | |||
|  |             // Setup target and it's compilation options. | |||
|  |             // This is done here as EditorGUIUtility.isProSkin must be on main thread. | |||
|  |             target.TargetCpu = _targetCpu; | |||
|  |             target.DisassemblyKind = _disasmKind; | |||
|  |             targetOptions.EnableBurstSafetyChecks = _safetyChecks; | |||
|  |             target.IsDarkMode = EditorGUIUtility.isProSkin; | |||
|  |             targetOptions.EnableBurstCompileSynchronously = true; | |||
|  | 
 | |||
|  |             // Don't set debug mode, because it disables optimizations. | |||
|  |             // Instead we set debug level (None, Full, LineOnly) below. | |||
|  |             targetOptions.EnableBurstDebug = false; | |||
|  | 
 | |||
|  |             Task.Run(() => | |||
|  |             { | |||
|  |                 var options = new StringBuilder(); | |||
|  | 
 | |||
|  |                 if (targetOptions.TryGetOptions(target.IsStaticMethod ? (MemberInfo)target.Method : target.JobType, true, out var defaultOptions)) | |||
|  |                 { | |||
|  |                     options.AppendLine(defaultOptions); | |||
|  | 
 | |||
|  |                     // Disables the 2 current warnings generated from code (since they clutter up the inspector display) | |||
|  |                     // BC1370 - throw inside code not guarded with ConditionalSafetyCheck attribute | |||
|  |                     // BC1322 - loop intrinsic on loop that has been optimized away | |||
|  |                     options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDisableWarnings, "BC1370;BC1322")}"); | |||
|  | 
 | |||
|  |                     options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionTarget, TargetCpuNames[(int)_targetCpu])}"); | |||
|  | 
 | |||
|  |                     // For IRPassAnalysis, we always want full debug information. | |||
|  |                     if (_disasmKind != DisassemblyKind.IRPassAnalysis) | |||
|  |                     { | |||
|  |                         switch (_assemblyKind) | |||
|  |                         { | |||
|  |                             case AssemblyOptions.EnhancedWithMinimalDebugInformation: | |||
|  |                             case AssemblyOptions.ColouredWithMinimalDebugInformation: | |||
|  |                                 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "2")}"); | |||
|  |                                 break; | |||
|  |                             case AssemblyOptions.ColouredWithFullDebugInformation: | |||
|  |                             case AssemblyOptions.EnhancedWithFullDebugInformation: | |||
|  |                             case AssemblyOptions.PlainWithDebugInformation: | |||
|  |                                 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "1")}"); | |||
|  |                                 break; | |||
|  |                             default: | |||
|  |                             case AssemblyOptions.PlainWithoutDebugInformation: | |||
|  |                                 options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "0")}"); | |||
|  |                                 break; | |||
|  |                         } | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         options.AppendLine($"{BurstCompilerOptions.GetOption(BurstCompilerOptions.OptionDebug, "1")}"); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     var baseOptions = options.ToString(); | |||
|  | 
 | |||
|  |                     target.RawDisassembly = GetDisassembly(target.Method, baseOptions + GetDisasmOptions()[(int)_disasmKind]); | |||
|  | 
 | |||
|  |                     target.FormattedDisassembly = null; | |||
|  | 
 | |||
|  |                     target.IsBurstError = IsBurstError(target.RawDisassembly); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 target.IsLoading = false; | |||
|  |                 target.JustLoaded = true; | |||
|  |             }); | |||
|  |         } | |||
|  | 
 | |||
|  |         private void RenderBurstJobMenu() | |||
|  |         { | |||
|  |             float width = position.width / 3; | |||
|  |             GUILayout.BeginVertical(GUILayout.Width(width)); | |||
|  | 
 | |||
|  |             // Render Treeview showing burst targets: | |||
|  |             GUILayout.Label("Compile Targets", EditorStyles.boldLabel); | |||
|  |             RenderCompileTargetsFilters(width); | |||
|  | 
 | |||
|  |             // Does not give proper rect during layout event. | |||
|  |             _inspectorView = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); | |||
|  | 
 | |||
|  |             _treeView.OnGUI(_inspectorView); | |||
|  | 
 | |||
|  |             GUILayout.EndVertical(); | |||
|  |         } | |||
|  | 
 | |||
|  |         private void HandleHorizontalFocus(float workingWidth, bool shouldSetupText, bool isTextFormatted) | |||
|  |         { | |||
|  |             if (!shouldSetupText || !isTextFormatted || !_burstDisassembler.IsInitialized) { return; } | |||
|  | 
 | |||
|  |             var branchFiller = _textArea.MaxLineDepth * 10; | |||
|  | 
 | |||
|  |             if (branchFiller < workingWidth / 2f) { return; } | |||
|  | 
 | |||
|  |             // Do horizontal padding: | |||
|  |             _scrollPos.x = _textArea.MaxLineDepth * 10; | |||
|  |         } | |||
|  | 
 | |||
|  | 
 | |||
|  |         private static void RenderLoading() | |||
|  |         { | |||
|  |             GUILayout.BeginHorizontal(); | |||
|  |             GUILayout.FlexibleSpace(); | |||
|  |             GUILayout.BeginVertical(); | |||
|  |             GUILayout.FlexibleSpace(); | |||
|  |             GUILayout.Label("Loading..."); | |||
|  |             GUILayout.FlexibleSpace(); | |||
|  |             GUILayout.EndVertical(); | |||
|  |             GUILayout.FlexibleSpace(); | |||
|  |             GUILayout.EndHorizontal(); | |||
|  |         } | |||
|  | 
 | |||
|  |         public void OnGUI() | |||
|  |         { | |||
|  |             if (!_initialized) | |||
|  |             { | |||
|  |                 RenderLoading(); | |||
|  |                 return; | |||
|  |             } | |||
|  |             // used to give hot control to inspector when a mouseDown event has happened. | |||
|  |             // This way we can register a mouseUp happening outside inspector. | |||
|  |             int controlID = GUIUtility.GetControlID(FocusType.Passive); | |||
|  | 
 | |||
|  |             // Make sure that editor options are synchronized | |||
|  |             BurstEditorOptions.EnsureSynchronized(); | |||
|  | 
 | |||
|  |             if (_fontSizesText == null) | |||
|  |             { | |||
|  |                 _fontSizesText = new string[FontSizes.Length]; | |||
|  |                 for (var i = 0; i < FontSizes.Length; ++i) _fontSizesText[i] = FontSizes[i].ToString(); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (fontSizeIndex == -1) | |||
|  |             { | |||
|  |                 fontSizeIndex = EditorPrefs.GetInt(FontSizeIndexPref, 5); | |||
|  |                 fontSizeIndex = Math.Max(0, fontSizeIndex); | |||
|  |                 fontSizeIndex = Math.Min(fontSizeIndex, FontSizes.Length - 1); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (fixedFontStyle == null || fixedFontStyle.font == null) // also check .font as it's reset somewhere when going out of play mode. | |||
|  |             { | |||
|  |                 fixedFontStyle = new GUIStyle(GUI.skin.label); | |||
|  | 
 | |||
|  | #if UNITY_2023_1_OR_NEWER | |||
|  |                 _font = EditorGUIUtility.Load("Fonts/RobotoMono/RobotoMono-Regular.ttf") as Font; | |||
|  | #else | |||
|  |                 string fontName; | |||
|  |                 if (Application.platform == RuntimePlatform.WindowsEditor) | |||
|  |                 { | |||
|  |                     fontName = "Consolas"; | |||
|  |                 } | |||
|  |                 else | |||
|  |                 { | |||
|  |                     fontName = "Courier"; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 CleanupFont(); | |||
|  | 
 | |||
|  |                 _font = Font.CreateDynamicFontFromOSFont(fontName, FontSize); | |||
|  | #endif | |||
|  | 
 | |||
|  |                 fixedFontStyle.font = _font; | |||
|  |                 fixedFontStyle.fontSize = FontSize; | |||
|  |             } | |||
|  | 
 | |||
|  |             if (_searchFieldJobs == null) _searchFieldJobs = new SearchField(); | |||
|  | 
 | |||
|  |             if (_textArea == null) _textArea = new LongTextArea(); | |||
|  | 
 | |||
|  |             GUILayout.BeginHorizontal(); | |||
|  | 
 | |||
|  |             // SplitterGUILayout.BeginHorizontalSplit is internal in Unity but we don't have much choice | |||
|  |             SplitterGUILayout.BeginHorizontalSplit(TreeViewSplitterState); | |||
|  | 
 | |||
|  |             RenderBurstJobMenu(); | |||
|  | 
 | |||
|  |             GUILayout.BeginVertical(); | |||
|  | 
 | |||
|  |             var selection = _treeView.GetSelection(); | |||
|  |             if (selection.Count == 1) | |||
|  |             { | |||
|  |                 var targetIndex = selection[0]; | |||
|  |                 _target = _targets[targetIndex - 1]; | |||
|  |                 var targetOptions = _target.Options; | |||
|  | 
 | |||
|  |                 var targetChanged = _previousTargetIndex != targetIndex; | |||
|  | 
 | |||
|  |                 _previousTargetIndex = targetIndex; | |||
|  | 
 | |||
|  |                 // Stash selected item name to handle domain reloads more gracefully | |||
|  |                 _selectedItem = _target.GetDisplayName(); | |||
|  | 
 | |||
|  |                 if (_assemblyKind == null) | |||
|  |                 { | |||
|  |                     if (_enhancedDisassembly) | |||
|  |                     { | |||
|  |                         _assemblyKind = AssemblyOptions.ColouredWithMinimalDebugInformation; | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         _assemblyKind = AssemblyOptions.PlainWithoutDebugInformation; | |||
|  |                     } | |||
|  |                     _oldAssemblyKind = (AssemblyOptions)_assemblyKind; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 // We are currently formatting only Asm output | |||
|  |                 var isTextFormatted = IsEnhanced((AssemblyOptions)_assemblyKind) && SupportsEnhancedRendering; | |||
|  | 
 | |||
|  |                 // Depending if we are formatted or not, we don't render the same text | |||
|  |                 var textToRender = _target.RawDisassembly?.TrimStart('\n'); | |||
|  | 
 | |||
|  |                 // Only refresh if we are switching to a new selection that hasn't been disassembled yet | |||
|  |                 // Or we are changing disassembly settings (safety checks / enhanced disassembly) | |||
|  |                 var targetRefresh = textToRender == null | |||
|  |                                     || _target.DisassemblyKind != _disasmKind | |||
|  |                                     || targetOptions.EnableBurstSafetyChecks != _safetyChecks | |||
|  |                                     || _target.TargetCpu != _targetCpu | |||
|  |                                     || _target.IsDarkMode != EditorGUIUtility.isProSkin; | |||
|  | 
 | |||
|  |                 if (_assemblyKindPrior != _assemblyKind) | |||
|  |                 { | |||
|  |                     targetRefresh = true; | |||
|  |                     _assemblyKindPrior = _assemblyKind;  // Needs to be refreshed, as we need to change disassembly options | |||
|  | 
 | |||
|  |                     // If the target did not changed but our assembly kind did, we need to remember this. | |||
|  |                     if (!targetChanged) | |||
|  |                     { | |||
|  |                         _sameTargetButDifferentAssemblyKind = true; | |||
|  |                     } | |||
|  |                 } | |||
|  | 
 | |||
|  |                 // If the previous target changed the assembly kind and we have a target change, we need to | |||
|  |                 // refresh the assembly because we'll have cached the previous assembly kinds output rather | |||
|  |                 // than the one requested. | |||
|  |                 if (_sameTargetButDifferentAssemblyKind && targetChanged) | |||
|  |                 { | |||
|  |                     targetRefresh = true; | |||
|  |                     _sameTargetButDifferentAssemblyKind = false; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (targetRefresh) | |||
|  |                 { | |||
|  |                     CompileNewTarget(_target, targetOptions); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 _prevWasCompileError = _isCompileError; | |||
|  |                 _isCompileError = _target.IsBurstError; | |||
|  | 
 | |||
|  |                 _buttonOverlapInspectorView = 0; | |||
|  |                 var oldSimdSmellTest = _smellTest; | |||
|  |                 HandleButtonBars(_target, targetChanged, out var fontSize, out var expandAllBlocks, out var focusCode); | |||
|  |                 var simdSmellTestChanged = oldSimdSmellTest != _smellTest; | |||
|  | 
 | |||
|  |                 // Guard against _textArea being used, as the assembly isn't ready yet. | |||
|  |                 // Have to test against event so it cannot finish between a Layout event and Repaint event; | |||
|  |                 // this is necessary as we cannot alter GUI between these events. | |||
|  |                 if (_target.HasRequiredBurstCompileAttributes && (_target.IsLoading || (_target.JustLoaded && Event.current.type != EventType.Layout))) | |||
|  |                 { | |||
|  |                     RenderLoading(); | |||
|  | 
 | |||
|  |                     // Need to close the splits we opened. | |||
|  |                     GUILayout.EndVertical(); | |||
|  |                     SplitterGUILayout.EndHorizontalSplit(); | |||
|  |                     GUILayout.EndHorizontal(); | |||
|  |                     return; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 var justLoaded = _target.JustLoaded; | |||
|  |                 _target.JustLoaded = false; | |||
|  | 
 | |||
|  |                 // If ´CompileNewTarget´ finishes before we enter loading screen above `textToRender` might not be set. | |||
|  |                 textToRender ??= _target.RawDisassembly?.TrimStart('\n'); | |||
|  | 
 | |||
|  |                 if (!string.IsNullOrEmpty(textToRender)) | |||
|  |                 { | |||
|  |                     // we should only call SetDisassembler(...) the first time assemblyKind is changed with same target. | |||
|  |                     // Otherwise it will kep re-initializing fields such as _folded, meaning we can no longer fold/unfold. | |||
|  |                     var shouldSetupText = !_textArea.IsTextSet(_selectedItem) | |||
|  |                                           || justLoaded | |||
|  |                                           || simdSmellTestChanged; | |||
|  | 
 | |||
|  |                     if (shouldSetupText) | |||
|  |                     { | |||
|  |                         _textArea.SetText( | |||
|  |                             _selectedItem, | |||
|  |                             textToRender, | |||
|  |                             _target.IsDarkMode, | |||
|  |                             _burstDisassembler, | |||
|  |                             isTextFormatted && _burstDisassembler.Initialize( | |||
|  |                                 textToRender, | |||
|  |                                 FetchAsmKind(_targetCpu, _disasmKind), | |||
|  |                                 _target.IsDarkMode, | |||
|  |                                 IsColoured((AssemblyOptions)_assemblyKind), | |||
|  |                                 _smellTest)); | |||
|  |                     } | |||
|  |                     if (justLoaded) | |||
|  |                     { | |||
|  |                         _scrollPos = Vector2.zero; | |||
|  |                     } | |||
|  | 
 | |||
|  |                     HandleHorizontalFocus( | |||
|  |                         _inspectorView.width == 1f ? _buttonBarWidth : _inspectorView.width, | |||
|  |                         shouldSetupText, | |||
|  |                         isTextFormatted | |||
|  |                     ); | |||
|  | 
 | |||
|  |                     // Fixing lastRectSize to actually be size of scroll view | |||
|  |                     _inspectorView.position = _scrollPos; | |||
|  |                     _inspectorView.width = position.width - (_inspectorView.width + _scrollbarThickness); | |||
|  |                     _inspectorView.height -= (_buttonOverlapInspectorView + 4); //+4 for alignment. | |||
|  |                     if (_searchBarVisible) _inspectorView.height -= EditorStyles.searchField.CalcHeight(GUIContent.none, 2); // 2 is just arbitrary, as the width does not alter height | |||
|  | 
 | |||
|  |                     // repaint indicate end of frame, so we can alter width for menu items to new correct. | |||
|  |                     if (Event.current.type == EventType.Repaint) | |||
|  |                     { | |||
|  |                         _buttonBarWidth = _inspectorView.width - _scrollbarThickness; | |||
|  |                     } | |||
|  | 
 | |||
|  |                     // Do search if we did not try and find assembly and we were actually going to do a search. | |||
|  |                     if (_focusTargetJob && TryFocusJobInAssemblyView(ref _inspectorView, shouldSetupText, _target)) | |||
|  |                     { | |||
|  |                         _scrollPos.y = _inspectorView.y - _textArea.fontHeight*10; | |||
|  |                     } | |||
|  | 
 | |||
|  |                     _scrollPos = GUILayout.BeginScrollView(_scrollPos, true, true); | |||
|  | 
 | |||
|  |                     if (Event.current.type != EventType.Layout) // we always want mouse position feedback | |||
|  |                     { | |||
|  |                         _textArea.Interact(_inspectorView, Event.current.type); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     // Set up search information if it is happening. | |||
|  |                     Regex regx = default; | |||
|  |                     SearchCriteria sc = default; | |||
|  |                     var doSearch = _searchBarVisible && _searchFilterAssembly != ""; | |||
|  |                     var wrongRegx = false; | |||
|  |                     if (doSearch) | |||
|  |                     { | |||
|  |                         sc = new SearchCriteria(_searchFilterAssembly, _doIgnoreCase, _doWholeWordMatch, _doRegex); | |||
|  |                         if (_doRegex) | |||
|  |                         { | |||
|  |                             try | |||
|  |                             { | |||
|  |                                 var opt = RegexOptions.Compiled | RegexOptions.CultureInvariant; | |||
|  |                                 if (!_doIgnoreCase) opt |= RegexOptions.IgnoreCase; | |||
|  | 
 | |||
|  |                                 var filter = _searchFilterAssembly; | |||
|  |                                 if (_doWholeWordMatch) filter = @"\b" + filter + @"\b"; | |||
|  | 
 | |||
|  |                                 regx = new Regex(filter, opt); | |||
|  |                             } | |||
|  |                             catch (Exception) | |||
|  |                             { | |||
|  |                                 // Regex was invalid | |||
|  |                                 wrongRegx = true; | |||
|  |                                 doSearch = false; | |||
|  |                             } | |||
|  |                         } | |||
|  |                     } | |||
|  | 
 | |||
|  |                     var doRepaint = _textArea.Render(fixedFontStyle, _inspectorView, _showBranchMarkers, doSearch, sc, regx); | |||
|  | 
 | |||
|  |                     // A change in the underlying textArea has happened, that requires the GUI to be repainted during this frame. | |||
|  |                     if (doRepaint) Repaint(); | |||
|  | 
 | |||
|  |                     if (Event.current.type != EventType.Layout) | |||
|  |                     { | |||
|  |                         HandleMouseEventForSelection(_inspectorView, controlID, _showBranchMarkers); | |||
|  |                         HandleKeyboardEventForSelection(_inspectorView, _showBranchMarkers); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     if (_leftClicked) | |||
|  |                     { | |||
|  |                         GenericMenu menu = new GenericMenu(); | |||
|  | 
 | |||
|  |                         menu.AddItem(EditorGUIUtility.TrTextContent("Copy Selection"), false, _textArea.DoSelectionCopy); | |||
|  |                         menu.AddItem(EditorGUIUtility.TrTextContent("Copy Color Tags"), _textArea.CopyColorTags, _textArea.ChangeCopyMode); | |||
|  |                         menu.AddItem(EditorGUIUtility.TrTextContent("Select All"), false, _textArea.SelectAll); | |||
|  |                         menu.AddItem(EditorGUIUtility.TrTextContent($"Find in {DisassemblyKindNames[(int)_disasmKind]}"), _searchBarVisible, EnableDisableSearchBar); | |||
|  |                         menu.ShowAsContext(); | |||
|  | 
 | |||
|  |                         _leftClicked = false; | |||
|  |                     } | |||
|  | 
 | |||
|  |                     GUILayout.EndScrollView(); | |||
|  | 
 | |||
|  |                     if (_searchBarVisible) | |||
|  |                     { | |||
|  |                         if (_searchFieldAssembly == null) | |||
|  |                         { | |||
|  |                             _searchFieldAssembly = new SearchField(); | |||
|  |                             _searchFieldAssembly.autoSetFocusOnFindCommand = false; | |||
|  |                         } | |||
|  | 
 | |||
|  |                         int hitnumbers = _textArea.NrSearchHits > 0 ? _textArea.ActiveSearchNr + 1 : 0; | |||
|  |                         var hitNumberContent = new GUIContent("    " + hitnumbers + " of " + _textArea.NrSearchHits + " hits"); | |||
|  | 
 | |||
|  |                         GUILayout.BeginHorizontal(); | |||
|  | 
 | |||
|  |                         // Makes sure that on "enter" keyboard event, the focus is not taken away from searchField. | |||
|  |                         if (saveSearchFieldFromEvent) GUI.FocusControl("BurstInspectorGUI"); | |||
|  | 
 | |||
|  |                         string newFilterAssembly; | |||
|  |                         if (wrongRegx) | |||
|  |                         { | |||
|  |                             var colb = GUI.contentColor; | |||
|  |                             GUI.contentColor = Color.red; | |||
|  |                             newFilterAssembly = _searchFieldAssembly.OnGUI(_searchFilterAssembly); | |||
|  |                             GUI.contentColor = colb; | |||
|  |                         } | |||
|  |                         else | |||
|  |                         { | |||
|  |                             newFilterAssembly = _searchFieldAssembly.OnGUI(_searchFilterAssembly); | |||
|  |                         } | |||
|  |                         // Give back focus to the searchField, if we took it away. | |||
|  |                         if (saveSearchFieldFromEvent) | |||
|  |                         { | |||
|  |                             _searchFieldAssembly.SetFocus(); | |||
|  |                             saveSearchFieldFromEvent = false; | |||
|  |                         } | |||
|  | 
 | |||
|  | 
 | |||
|  |                         if (newFilterAssembly != _searchFilterAssembly) | |||
|  |                         { | |||
|  |                             _searchFilterAssembly = newFilterAssembly; | |||
|  |                             _textArea.StopSearching(); | |||
|  |                         } | |||
|  | 
 | |||
|  |                         _doIgnoreCase = GUILayout.Toggle(_doIgnoreCase, _ignoreCase); | |||
|  |                         _doWholeWordMatch = GUILayout.Toggle(_doWholeWordMatch, _matchWord); | |||
|  |                         _doRegex = GUILayout.Toggle(_doRegex, _regexSearch); | |||
|  |                         GUILayout.Label(hitNumberContent); | |||
|  |                         if (GUILayout.Button(GUIContent.none, EditorStyles.searchFieldCancelButton)) | |||
|  |                         { | |||
|  |                             _searchBarVisible = false; | |||
|  |                             _textArea.StopSearching(); | |||
|  |                         } | |||
|  | 
 | |||
|  |                         GUILayout.EndHorizontal(); | |||
|  |                     } | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (fontSize != fontSizeIndex) | |||
|  |                 { | |||
|  |                     _textArea.Invalidate(); | |||
|  |                     fontSizeIndex = fontSize; | |||
|  |                     EditorPrefs.SetInt(FontSizeIndexPref, fontSize); | |||
|  |                     fixedFontStyle = null; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (expandAllBlocks) | |||
|  |                 { | |||
|  |                     _textArea.ExpandAllBlocks(); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (focusCode) | |||
|  |                 { | |||
|  |                     _textArea.FocusCodeBlocks(); | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             GUILayout.EndVertical(); | |||
|  | 
 | |||
|  |             SplitterGUILayout.EndHorizontalSplit(); | |||
|  | 
 | |||
|  |             GUILayout.EndHorizontal(); | |||
|  |         } | |||
|  | 
 | |||
|  |         public static bool IsBurstError(string disassembly) | |||
|  |         { | |||
|  |             return _rx.IsMatch(disassembly ?? ""); | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <summary> | |||
|  |         /// Focuses the view on the active function if a jump is doable. | |||
|  |         /// </summary> | |||
|  |         /// <param name="workingArea">Current assembly view.</param> | |||
|  |         /// <param name="wasTextSetup">Whether text was set in <see cref="_textArea"/>.</param> | |||
|  |         /// <param name="target">Target job to find function in.</param> | |||
|  |         /// <returns>Whether a focus was attempted or not.</returns> | |||
|  |         private bool TryFocusJobInAssemblyView(ref Rect workingArea, bool wasTextSetup, BurstCompileTarget target) | |||
|  |         { | |||
|  |             bool TryFindByLabel(ref Rect workingArea) | |||
|  |             { | |||
|  |                 var regx = default(Regex); | |||
|  |                 var sb = new StringBuilder(); | |||
|  |                 if (target.IsStaticMethod) | |||
|  |                 { | |||
|  |                     // Search for fullname as label | |||
|  |                     // Null reference not a danger, because of target being a static method | |||
|  |                     sb.Append(target.Method.DeclaringType.ToString().Replace("+", ".")); | |||
|  | 
 | |||
|  |                     // Generic labels will be sorounded by "", while standard static methods won't | |||
|  |                     var genericArguments = target.JobType.GenericTypeArguments; | |||
|  |                     if (genericArguments.Length > 0) | |||
|  |                     { | |||
|  |                         // Need to alter the generic arguments from [] to <> form | |||
|  |                         // Removing [] form | |||
|  |                         var idx = sb.ToString().LastIndexOf('['); | |||
|  |                         sb.Remove(idx, sb.Length - idx); | |||
|  | 
 | |||
|  |                         // Adding <> form | |||
|  |                         sb.Append('<').Append(BurstCompileTarget.Pretty(genericArguments[0])); | |||
|  |                         for (var i = 1; i < genericArguments.Length; i++) | |||
|  |                         { | |||
|  |                             sb.Append(",").Append(BurstCompileTarget.Pretty(genericArguments[i])); | |||
|  |                         } | |||
|  |                         sb.Append('>').Append('.').Append(target.Method.Name); | |||
|  |                     } | |||
|  |                     else | |||
|  |                     { | |||
|  |                         sb.Append('.').Append(target.Method.Name); | |||
|  |                     } | |||
|  | 
 | |||
|  |                     const RegexOptions opt = RegexOptions.Compiled | RegexOptions.CultureInvariant; | |||
|  |                     regx = new Regex(@$"{Regex.Escape(sb.ToString())}[^"":]+"":", opt); | |||
|  |                 } | |||
|  |                 else | |||
|  |                 { | |||
|  |                     // Append full method name. Using display name for simpler access | |||
|  |                     var targetName = target.GetDisplayName(); | |||
|  |                     // Remove part that tells about used interface | |||
|  |                     var idx = 0; | |||
|  |                     // If generic the argument part must also be removed, as they won't match | |||
|  |                     if ((idx = targetName.IndexOf('[')) == -1) idx = targetName.IndexOf('-') - 1; | |||
|  |                     targetName = targetName.Remove(idx); | |||
|  | 
 | |||
|  |                     sb.Append($@""".*<{targetName}.*"":"); | |||
|  | 
 | |||
|  |                     const RegexOptions opt = RegexOptions.Compiled | RegexOptions.CultureInvariant; | |||
|  |                     regx = new Regex(sb.ToString(), opt); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 var sc = new SearchCriteria(sb.ToString(), false, false, true); | |||
|  | 
 | |||
|  |                 return _textArea.SearchText(sc, regx, ref workingArea, true, true); | |||
|  |             } | |||
|  | 
 | |||
|  |             var foundTarget = false; | |||
|  |             // _isTextSetLastEvent used so we call this at the first scroll-able event after text was set. | |||
|  |             // We cannot scroll during used or layout events, and the order of events are: | |||
|  |             //   1. Used event:     text is set in textArea | |||
|  |             //   2. Layout event:   Cannot do the jump yet | |||
|  |             //   3. Repaint event:  Now jump is doable | |||
|  |             // Hence _isTextSetLastEvent is only set on layout events (during phase 2) | |||
|  |             if (wasTextSetup) | |||
|  |             { | |||
|  |                 // Need to call Layout to setup fontsize before searching | |||
|  |                 _textArea.Layout(fixedFontStyle, _textArea.horizontalPad); | |||
|  | 
 | |||
|  |                 foundTarget = TryFindByLabel(ref workingArea); | |||
|  |                 _textArea.StopSearching(); // Clear the internals of _textArea from this search; to avoid highlighting | |||
|  | 
 | |||
|  |                 // Clear other possible search, so it won't interfere with this. | |||
|  |                 _searchFilterAssembly = string.Empty; | |||
|  | 
 | |||
|  |                 // We need to do a Repaint() in order for the view to actually update immediately. | |||
|  |                 if (foundTarget) Repaint(); | |||
|  |             } | |||
|  | 
 | |||
|  |             return foundTarget; | |||
|  |         } | |||
|  | 
 | |||
|  |         private void EnableDisableSearchBar() | |||
|  |         { | |||
|  |             _searchBarVisible = !_searchBarVisible; | |||
|  | 
 | |||
|  |             if (_searchBarVisible && _searchFieldAssembly != null) | |||
|  |             { | |||
|  |                 _searchFieldAssembly.SetFocus(); | |||
|  |             } | |||
|  |             else if (!_searchBarVisible) | |||
|  |             { | |||
|  |                 _textArea.StopSearching(); | |||
|  |             } | |||
|  |         } | |||
|  |         private bool _doIgnoreCase = false; | |||
|  |         private bool _doWholeWordMatch = false; | |||
|  |         private bool _doRegex = false; | |||
|  | 
 | |||
|  |         internal static string GetDisassembly(MethodInfo method, string options) | |||
|  |         { | |||
|  |             try | |||
|  |             { | |||
|  |                 var result = BurstCompilerService.GetDisassembly(method, options); | |||
|  |                 if (result.IndexOf('\t') >= 0) | |||
|  |                 { | |||
|  |                     result = result.Replace("\t", "        "); | |||
|  |                 } | |||
|  | 
 | |||
|  |                 // Workaround to remove timings | |||
|  |                 if (result.Contains("Burst timings")) | |||
|  |                 { | |||
|  |                     var index = result.IndexOf("While compiling", StringComparison.Ordinal); | |||
|  |                     if (index > 0) | |||
|  |                     { | |||
|  |                         result = result.Substring(index); | |||
|  |                     } | |||
|  |                 } | |||
|  | 
 | |||
|  |                 return result; | |||
|  |             } | |||
|  |             catch (Exception e) | |||
|  |             { | |||
|  |                 return "Failed to compile:\n" + e.Message; | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         private static BurstDisassembler.AsmKind FetchAsmKind(BurstTargetCpu cpu, DisassemblyKind kind) | |||
|  |         { | |||
|  |             if (kind == DisassemblyKind.Asm) | |||
|  |             { | |||
|  |                 switch (cpu) | |||
|  |                 { | |||
|  |                     case BurstTargetCpu.ARMV7A_NEON32: | |||
|  |                     case BurstTargetCpu.ARMV8A_AARCH64: | |||
|  |                     case BurstTargetCpu.ARMV8A_AARCH64_HALFFP: | |||
|  |                     case BurstTargetCpu.THUMB2_NEON32: | |||
|  |                         return BurstDisassembler.AsmKind.ARM; | |||
|  |                     case BurstTargetCpu.WASM32: | |||
|  |                         return BurstDisassembler.AsmKind.Wasm; | |||
|  |                 } | |||
|  |                 return BurstDisassembler.AsmKind.Intel; | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 return BurstDisassembler.AsmKind.LLVMIR; | |||
|  |             } | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  |     /// <summary> | |||
|  |     /// Important: id for namespaces are negative, and ids for jobs are positive. | |||
|  |     ///            This lets us use the id for a job as an index directy into <see cref="_targets"/>. | |||
|  |     ///            Hence before going from <see cref="TreeViewItem"/> to <see cref="_targets"/> index, | |||
|  |     ///            One should check whether current item has any children (Only jobs are leafs). | |||
|  |     /// </summary> | |||
|  |     internal class BurstMethodTreeView : TreeView | |||
|  |     { | |||
|  |         private readonly Func<string> _getFilter; | |||
|  |         private readonly Func<(bool,bool)> _getJobListFilterToggles; | |||
|  | 
 | |||
|  |         private List<BurstCompileTarget> _targets; | |||
|  | 
 | |||
|  |         public BurstMethodTreeView(TreeViewState state, Func<string> getFilter, Func<(bool,bool)> getJobListFilterToggles) : base(state) | |||
|  |         { | |||
|  |             _getFilter = getFilter; | |||
|  |             _getJobListFilterToggles = getJobListFilterToggles; | |||
|  |             showBorder = true; | |||
|  |         } | |||
|  | 
 | |||
|  |         public void Initialize(List<BurstCompileTarget> targets, bool identicalTargets) | |||
|  |         { | |||
|  |             _targets = targets; | |||
|  |             Reload(); | |||
|  |             if (!identicalTargets) { ExpandAll(); } | |||
|  |         } | |||
|  | 
 | |||
|  |         /// <remarks> | |||
|  |         /// Assumes that <see cref="str"/> is derived from <see cref="Type"/>.<see cref="Type.FullName"/> | |||
|  |         /// i.e. types are separated by '+'. | |||
|  |         /// </remarks> | |||
|  |         /// <param name="str">Given type name string.</param> | |||
|  |         /// <returns>(List of namespaces/types, index of method name in <see cref="str"/>)</returns> | |||
|  |         internal static (List<StringSlice> ns, int nsEndIdx) ExtractNameSpaces(in string str) | |||
|  |         { | |||
|  |             if (str is null) { throw new ArgumentNullException(nameof(str)); } | |||
|  | 
 | |||
|  |             var nameSpaces = new List<StringSlice>(); | |||
|  |             int len = str.Length; | |||
|  |             int scope = 0; | |||
|  |             int previdx = 0; | |||
|  |             for (int i = 0; i < len; i++) | |||
|  |             { | |||
|  |                 bool stop = false; | |||
|  |                 char c = str[i]; | |||
|  |                 switch (c) | |||
|  |                 { | |||
|  |                     case '(': | |||
|  |                         // Jump out as we just found argument list!!! | |||
|  |                         stop = true; | |||
|  |                         break; | |||
|  |                     // We keep looking, as classes might have these in name: | |||
|  |                     case '{': | |||
|  |                     case '<': | |||
|  |                     case '[': | |||
|  |                         scope++; | |||
|  |                         break; | |||
|  |                     case '}': | |||
|  |                     case '>': | |||
|  |                     case ']': | |||
|  |                         scope--; | |||
|  |                         break; | |||
|  |                     case '+' when scope == 0: | |||
|  |                         nameSpaces.Add(new StringSlice(str, previdx, i - previdx)); | |||
|  |                         previdx = i + 1; | |||
|  |                         break; | |||
|  |                 } | |||
|  | 
 | |||
|  |                 if (stop) { break; } | |||
|  |             } | |||
|  |             return (nameSpaces, previdx); | |||
|  |         } | |||
|  | 
 | |||
|  |         internal static (int idN, List<TreeViewItem> added, List<StringSlice> nameSpace) | |||
|  |             ProcessNewItem(int idN, int idJ, BurstCompileTarget newTarget, List<StringSlice> oldNameSpace) | |||
|  |         { | |||
|  |             // Find all namespaces used for new target: | |||
|  |             string fns = newTarget.JobType.FullName; | |||
|  |             string dn = newTarget.GetDisplayName(); | |||
|  | 
 | |||
|  |             (List<StringSlice> newNameSpaces, int nameSpaceEndIdx) = ExtractNameSpaces(fns); | |||
|  | 
 | |||
|  |             int methodNameIdx = nameSpaceEndIdx; | |||
|  |             if (newTarget.IsStaticMethod) | |||
|  |             { | |||
|  |                 // Static method does not have the function name in fns, so fix methodNameIdx. | |||
|  |                 methodNameIdx = dn.IndexOf(newTarget.Method.Name, StringComparison.InvariantCulture); | |||
|  |                 // Add the last namespace: | |||
|  |                 newNameSpaces.Add(new StringSlice(dn, nameSpaceEndIdx, methodNameIdx-1 - nameSpaceEndIdx)); | |||
|  |             } | |||
|  |             string methodName = dn.Substring(methodNameIdx); | |||
|  | 
 | |||
|  |             int iNewNs = 0; | |||
|  |             int lNewNs = newNameSpaces.Count; | |||
|  |             int iOldNs = 0; | |||
|  |             int lOldNs = oldNameSpace.Count; | |||
|  | 
 | |||
|  |             var added = new List<TreeViewItem>(lNewNs); | |||
|  |             int depth = 0; | |||
|  | 
 | |||
|  |             // Skip all namespaces shared by previous but increase depth accordingly: | |||
|  |             for (; iNewNs < lNewNs && iOldNs < lOldNs && newNameSpaces[iNewNs] == oldNameSpace[iOldNs]; | |||
|  |                  depth++, iNewNs++, iOldNs++) {} | |||
|  | 
 | |||
|  |             // Handle all new namespaces: | |||
|  |             for (; iNewNs < lNewNs; | |||
|  |                  depth++, iNewNs++) | |||
|  |             { | |||
|  |                 added.Add(new TreeViewItem { id = --idN, depth = depth, displayName = newNameSpaces[iNewNs].ToString()}); | |||
|  |             } | |||
|  | 
 | |||
|  |             // Add the function name: | |||
|  |             added.Add(new TreeViewItem { id = idJ, depth = depth, displayName = methodName }); | |||
|  | 
 | |||
|  |             return (idN, added, newNameSpaces); | |||
|  |         } | |||
|  | 
 | |||
|  |         protected override TreeViewItem BuildRoot() | |||
|  |         { | |||
|  |             var root = new TreeViewItem {id = 0, depth = -1, displayName = "Root"}; | |||
|  |             var allItems = new List<TreeViewItem>(); | |||
|  | 
 | |||
|  |             if (_targets != null) | |||
|  |             { | |||
|  |                 var filter = _getFilter(); | |||
|  |                 var (showUnityNamespaceJobs, showDOTSGeneratedJobs) = _getJobListFilterToggles(); | |||
|  |                 // Have two separate ids so "jobs ids == jobs index". | |||
|  |                 int idJ = 0; | |||
|  |                 int idN = 0; | |||
|  |                 var oldNameSpace = new List<StringSlice>(); | |||
|  |                 foreach (BurstCompileTarget target in _targets) | |||
|  |                 { | |||
|  |                     // idJ used as index into _targets, which means it should also take hidden targets into account! | |||
|  |                     idJ++; | |||
|  | 
 | |||
|  |                     string displayName = target.GetDisplayName(); | |||
|  | 
 | |||
|  |                     bool filtered = | |||
|  |                         (!string.IsNullOrEmpty(filter) && | |||
|  |                          displayName.IndexOf(filter, 0, displayName.Length, | |||
|  |                              StringComparison.InvariantCultureIgnoreCase) < 0) | |||
|  |                         || (!showUnityNamespaceJobs && | |||
|  |                             displayName.StartsWith("Unity.", StringComparison.InvariantCultureIgnoreCase)) | |||
|  |                         || (!showDOTSGeneratedJobs && | |||
|  |                             displayName.Contains(".Generated")); | |||
|  | 
 | |||
|  |                     if (filtered) { continue; } | |||
|  | 
 | |||
|  |                     try | |||
|  |                     { | |||
|  |                         var (newIdN, added, nameSpace) = | |||
|  |                             ProcessNewItem(idN, idJ, target, oldNameSpace); | |||
|  | 
 | |||
|  |                         allItems.AddRange(added); | |||
|  |                         idN = newIdN; | |||
|  |                         oldNameSpace = nameSpace; | |||
|  |                     } | |||
|  |                     catch (Exception ex) | |||
|  |                     { | |||
|  |                         Debug.Log($"Internal error: Could not add {displayName}\n  Because: {ex.Message}"); | |||
|  |                     } | |||
|  |                 } | |||
|  |             } | |||
|  |             SetupParentsAndChildrenFromDepths(root, allItems.ToList()); | |||
|  |             return root; | |||
|  |         } | |||
|  | 
 | |||
|  |         public new IList<int> GetSelection() | |||
|  |         { | |||
|  |             IList<int> selection = base.GetSelection(); | |||
|  |             // selection == non-leaf node => no job selected | |||
|  |             if (selection.Any() && selection[0] < 0) { return new List<int>(); } | |||
|  |             return selection; | |||
|  |         } | |||
|  | 
 | |||
|  |         internal bool TrySelectByDisplayName(string name) | |||
|  |         { | |||
|  |             var id = 1; | |||
|  |             foreach (var t in _targets) | |||
|  |             { | |||
|  |                 if (t.GetDisplayName() == name) | |||
|  |                 { | |||
|  |                     try | |||
|  |                     { | |||
|  |                         SetSelection(new[] { id }); | |||
|  |                         FrameItem(id); | |||
|  |                         return true; | |||
|  |                     } | |||
|  |                     catch (ArgumentException) | |||
|  |                     { | |||
|  |                         // When a search is made in the job list, such that the job we search for is filtered away | |||
|  |                         // FrameItem(id) will throw a dictionary error. So we catch this, and tell the caller that | |||
|  |                         // it cannot be selected. | |||
|  |                         return false; | |||
|  |                     } | |||
|  |                 } | |||
|  |                 else | |||
|  |                 { | |||
|  |                     ++id; | |||
|  |                 } | |||
|  |             } | |||
|  |             return false; | |||
|  |         } | |||
|  | 
 | |||
|  |         protected override void RowGUI(RowGUIArgs args) | |||
|  |         { | |||
|  |             if (!args.item.hasChildren) | |||
|  |             { | |||
|  |                 var target = _targets[args.item.id - 1]; | |||
|  |                 var wasEnabled = GUI.enabled; | |||
|  |                 GUI.enabled = target.HasRequiredBurstCompileAttributes; | |||
|  |                 base.RowGUI(args); | |||
|  |                 GUI.enabled = wasEnabled; | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 // Label GUI: | |||
|  |                 base.RowGUI(args); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         protected override void SingleClickedItem(int id) | |||
|  |         { | |||
|  |             // If labeled click try and fold/expand: | |||
|  |             if (id < 0) | |||
|  |             { | |||
|  |                 SetExpanded(id, !IsExpanded(id)); | |||
|  |                 SetSelection(new List<int>()); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         protected override bool CanMultiSelect(TreeViewItem item) | |||
|  |         { | |||
|  |             return false; | |||
|  |         } | |||
|  |     } | |||
|  | } |