1604 lines
66 KiB
C#
1604 lines
66 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
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;
|
||
private readonly GUIContent[] _contentsFontSize;
|
||
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();
|
||
|
||
string[] names = Enum.GetNames(typeof(BurstTargetCpu));
|
||
int size = names.Length;
|
||
_contentsTarget = new GUIContent[size];
|
||
for (int i = 0; i < size; i++)
|
||
{
|
||
_contentsTarget[i] = new GUIContent($"Target ({names[i]})");
|
||
}
|
||
|
||
size = FontSizes.Length;
|
||
_contentsFontSize = new GUIContent[size];
|
||
for (int i = 0; i < size; i++)
|
||
{
|
||
_contentsFontSize[i] = new GUIContent($"Font Size ({FontSizes[i].ToString()})");
|
||
}
|
||
}
|
||
|
||
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();
|
||
}
|
||
}
|
||
|
||
var newNames = new List<string>(_targets.Count);
|
||
foreach (var target in _targets)
|
||
{
|
||
newNames.Add(target.GetDisplayName());
|
||
}
|
||
|
||
bool identical = !newTreeState && newNames.Count == targetNames.Count;
|
||
int len = newNames.Count;
|
||
int i = 0;
|
||
while (identical && i < len)
|
||
{
|
||
identical = newNames[i] == targetNames[i];
|
||
i++;
|
||
}
|
||
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;
|
||
|
||
// 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, out var defaultOptions, isForCompilerClient: true))
|
||
{
|
||
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($@""".*<{Regex.Escape(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;
|
||
}
|
||
}
|
||
|
||
internal static BurstDisassembler.AsmKind FetchAsmKind(BurstTargetCpu cpu, DisassemblyKind kind)
|
||
{
|
||
if (kind == DisassemblyKind.Asm)
|
||
{
|
||
switch (cpu)
|
||
{
|
||
case BurstTargetCpu.Auto:
|
||
string cpuType = BurstCompiler.GetTargetCpuFromHost();
|
||
if (cpuType.Contains("Arm"))
|
||
{
|
||
return BurstDisassembler.AsmKind.ARM;
|
||
}
|
||
return BurstDisassembler.AsmKind.Intel;
|
||
case BurstTargetCpu.ARMV7A_NEON32:
|
||
case BurstTargetCpu.ARMV8A_AARCH64:
|
||
case BurstTargetCpu.ARMV8A_AARCH64_HALFFP:
|
||
case BurstTargetCpu.THUMB2_NEON32:
|
||
case BurstTargetCpu.ARMV9A:
|
||
return BurstDisassembler.AsmKind.ARM;
|
||
case BurstTargetCpu.WASM32:
|
||
return BurstDisassembler.AsmKind.Wasm;
|
||
default:
|
||
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('(', methodNameIdx) - newTarget.Method.Name.Length;
|
||
// 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);
|
||
return root;
|
||
}
|
||
|
||
public new IList<int> GetSelection()
|
||
{
|
||
IList<int> selection = base.GetSelection();
|
||
// selection == non-leaf node => no job selected
|
||
if (selection.Count > 0 && 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;
|
||
}
|
||
}
|
||
}
|