|
using System; |
|
using System.Collections.Generic; |
|
using System.Globalization; |
|
using System.Linq; |
|
using UnityEditor.IMGUI.Controls; |
|
using UnityEngine; |
|
using UnityEngine.Assertions; |
|
|
|
namespace UnityEditor.Performance.ProfileAnalyzer |
|
{ |
|
class ProfileTreeViewItem : TreeViewItem |
|
{ |
|
public MarkerData data { get; set; } |
|
public GUIContent[] cachedRowString; |
|
|
|
public ProfileTreeViewItem(int id, int depth, string displayName, MarkerData data) : base(id, depth, displayName) |
|
{ |
|
this.data = data; |
|
cachedRowString = null; |
|
} |
|
} |
|
|
|
class ProfileTable : TreeView |
|
{ |
|
Draw2D m_2D; |
|
ProfileDataView m_DataView; |
|
bool m_HideRemovedMarkers; |
|
ProfileAnalyzerWindow m_ProfileAnalyzerWindow; |
|
Color m_BarColor; |
|
float m_MaxMedian; |
|
int m_MaxCount; |
|
float m_MaxCountMean; |
|
double m_MaxTotal; |
|
|
|
const float kRowHeights = 20f; |
|
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100); |
|
|
|
|
|
public enum MyColumns |
|
{ |
|
Name, |
|
State, |
|
Depth, |
|
Median, |
|
MedianBar, |
|
Mean, |
|
StandardDeviation, |
|
Min, |
|
Max, |
|
Range, |
|
Count, |
|
CountBar, |
|
CountMean, |
|
CountMeanBar, |
|
CountStandardDeviation, |
|
FirstFrame, |
|
AtMedian, |
|
Total, |
|
TotalBar, |
|
Threads, |
|
} |
|
|
|
static int m_MaxColumns; |
|
|
|
public enum SortOption |
|
{ |
|
Name, |
|
State, |
|
Depth, |
|
Median, |
|
Mean, |
|
Min, |
|
Max, |
|
StandardDeviation, |
|
Range, |
|
Count, |
|
CountMean, |
|
CountStandardDeviation, |
|
FirstFrame, |
|
AtMedian, |
|
Total, |
|
Threads, |
|
} |
|
|
|
|
|
SortOption[] m_SortOptions = |
|
{ |
|
SortOption.Name, |
|
SortOption.State, |
|
SortOption.Depth, |
|
SortOption.Median, |
|
SortOption.Median, |
|
SortOption.Mean, |
|
SortOption.StandardDeviation, |
|
SortOption.Min, |
|
SortOption.Max, |
|
SortOption.Range, |
|
SortOption.Count, |
|
SortOption.Count, |
|
SortOption.CountMean, |
|
SortOption.CountMean, |
|
SortOption.CountStandardDeviation, |
|
SortOption.FirstFrame, |
|
SortOption.AtMedian, |
|
SortOption.Total, |
|
SortOption.Total, |
|
SortOption.Threads, |
|
}; |
|
|
|
internal static class Styles |
|
{ |
|
public static readonly GUIContent menuItemSelectFramesInAll = new GUIContent("Select Frames that contain this marker (within whole data set)", ""); |
|
public static readonly GUIContent menuItemSelectFramesInCurrent = new GUIContent("Select Frames that contain this marker (within current selection)", ""); |
|
|
|
public static readonly GUIContent menuItemSelectFramesAll = new GUIContent("Select All"); |
|
public static readonly GUIContent menuItemAddToIncludeFilter = new GUIContent("Add to Include Filter", ""); |
|
public static readonly GUIContent menuItemAddToExcludeFilter = new GUIContent("Add to Exclude Filter", ""); |
|
public static readonly GUIContent menuItemRemoveFromIncludeFilter = new GUIContent("Remove from Include Filter", ""); |
|
public static readonly GUIContent menuItemRemoveFromExcludeFilter = new GUIContent("Remove from Exclude Filter", ""); |
|
public static readonly GUIContent menuItemSetAsParentMarkerFilter = new GUIContent("Set as Parent Marker Filter", ""); |
|
public static readonly GUIContent menuItemClearParentMarkerFilter = new GUIContent("Clear Parent Marker Filter", ""); |
|
public static readonly GUIContent menuItemSetAsRemoveMarker = new GUIContent("Remove Marker", ""); |
|
public static readonly GUIContent menuItemCopyToClipboard = new GUIContent("Copy to Clipboard", ""); |
|
} |
|
|
|
public ProfileTable(TreeViewState state, MultiColumnHeader multicolumnHeader, ProfileDataView dataView, bool hideRemovedMarkers, ProfileAnalyzerWindow profileAnalyzerWindow, Draw2D draw2D, Color barColor) : base(state, multicolumnHeader) |
|
{ |
|
m_2D = draw2D; |
|
m_DataView = dataView; |
|
m_HideRemovedMarkers = hideRemovedMarkers; |
|
m_ProfileAnalyzerWindow = profileAnalyzerWindow; |
|
m_BarColor = barColor; |
|
|
|
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length; |
|
Assert.AreEqual(m_SortOptions.Length, m_MaxColumns, "Ensure number of sort options are in sync with number of MyColumns enum values"); |
|
|
|
|
|
rowHeight = kRowHeights; |
|
showAlternatingRowBackgrounds = true; |
|
showBorder = true; |
|
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; |
|
|
|
multicolumnHeader.sortingChanged += OnSortingChanged; |
|
multicolumnHeader.visibleColumnsChanged += OnVisibleColumnsChanged; |
|
|
|
Reload(); |
|
} |
|
|
|
protected override TreeViewItem BuildRoot() |
|
{ |
|
int idForhiddenRoot = -1; |
|
int depthForHiddenRoot = -1; |
|
ProfileTreeViewItem root = new ProfileTreeViewItem(idForhiddenRoot, depthForHiddenRoot, "root", null); |
|
|
|
m_MaxMedian = 0.0f; |
|
m_MaxTotal = 0.0; |
|
m_MaxCount = 0; |
|
m_MaxCountMean = 0.0f; |
|
var markers = m_DataView.analysis.GetMarkers(); |
|
for (int index = 0; index < markers.Count; ++index) |
|
{ |
|
var marker = markers[index]; |
|
if (!m_ProfileAnalyzerWindow.DoesMarkerPassFilter(marker.name)) |
|
continue; |
|
|
|
if (m_HideRemovedMarkers && marker.IsFullyIgnored()) |
|
continue; |
|
|
|
var item = new ProfileTreeViewItem(index, 0, marker.name, marker); |
|
root.AddChild(item); |
|
float ms = item.data.msMedian; |
|
if (ms > m_MaxMedian) |
|
m_MaxMedian = ms; |
|
|
|
double msTotal = item.data.msTotal; |
|
if (msTotal > m_MaxTotal) |
|
m_MaxTotal = msTotal; |
|
|
|
int count = item.data.count; |
|
if (count > m_MaxCount) |
|
m_MaxCount = count; |
|
|
|
float countMean = item.data.countMean; |
|
if (countMean > m_MaxCountMean) |
|
m_MaxCountMean = countMean; |
|
} |
|
|
|
return root; |
|
} |
|
|
|
protected override IList<TreeViewItem> BuildRows(TreeViewItem root) |
|
{ |
|
m_Rows.Clear(); |
|
|
|
if (rootItem != null && rootItem.children != null) |
|
{ |
|
foreach (ProfileTreeViewItem node in rootItem.children) |
|
{ |
|
m_Rows.Add(node); |
|
} |
|
} |
|
|
|
SortIfNeeded(m_Rows); |
|
|
|
return m_Rows; |
|
} |
|
|
|
void OnSortingChanged(MultiColumnHeader _multiColumnHeader) |
|
{ |
|
SortIfNeeded(GetRows()); |
|
} |
|
|
|
protected virtual void OnVisibleColumnsChanged(MultiColumnHeader multiColumnHeader) |
|
{ |
|
m_ProfileAnalyzerWindow.SetSingleModeColumns(multiColumnHeader.state.visibleColumns); |
|
multiColumnHeader.ResizeToFit(); |
|
} |
|
|
|
void SortIfNeeded(IList<TreeViewItem> rows) |
|
{ |
|
if (rows.Count <= 1) |
|
{ |
|
return; |
|
} |
|
|
|
if (multiColumnHeader.sortedColumnIndex == -1) |
|
{ |
|
return; |
|
} |
|
|
|
|
|
SortByMultipleColumns(); |
|
|
|
|
|
rows.Clear(); |
|
foreach (ProfileTreeViewItem node in rootItem.children) |
|
{ |
|
rows.Add(node); |
|
} |
|
|
|
Repaint(); |
|
} |
|
|
|
string GetThreadName(ProfileTreeViewItem item) |
|
{ |
|
return m_ProfileAnalyzerWindow.GetUIThreadName(item.data.threads[0]); |
|
} |
|
|
|
string GetThreadNames(ProfileTreeViewItem item) |
|
{ |
|
var uiNames = new List<string>(); |
|
foreach (string threadNameWithIndex in item.data.threads) |
|
{ |
|
string uiName = m_ProfileAnalyzerWindow.GetUIThreadName(threadNameWithIndex); |
|
|
|
uiNames.Add(uiName); |
|
} |
|
uiNames.Sort(m_ProfileAnalyzerWindow.CompareUINames); |
|
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder(); |
|
bool first = true; |
|
foreach (var uiName in uiNames) |
|
{ |
|
if (first) |
|
first = false; |
|
else |
|
sb.Append(", "); |
|
sb.Append(uiName); |
|
} |
|
|
|
return sb.ToString(); |
|
} |
|
|
|
void SortByMultipleColumns() |
|
{ |
|
var sortedColumns = multiColumnHeader.state.sortedColumns; |
|
|
|
if (sortedColumns.Length == 0) |
|
{ |
|
return; |
|
} |
|
|
|
var myTypes = rootItem.children.Cast<ProfileTreeViewItem>(); |
|
var orderedQuery = InitialOrder(myTypes, sortedColumns); |
|
for (int i = 1; i < sortedColumns.Length; i++) |
|
{ |
|
SortOption sortOption = m_SortOptions[sortedColumns[i]]; |
|
bool ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]); |
|
|
|
switch (sortOption) |
|
{ |
|
case SortOption.Name: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.name, ascending); |
|
break; |
|
case SortOption.State: |
|
orderedQuery = orderedQuery.ThenBy(l => State(l), ascending); |
|
break; |
|
case SortOption.Depth: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.minDepth, ascending); |
|
break; |
|
case SortOption.Mean: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msMean, ascending); |
|
break; |
|
case SortOption.Median: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msMedian, ascending); |
|
break; |
|
case SortOption.StandardDeviation: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msStandardDeviation, ascending); |
|
break; |
|
case SortOption.Min: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msMin, ascending); |
|
break; |
|
case SortOption.Max: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msMax, ascending); |
|
break; |
|
case SortOption.Range: |
|
orderedQuery = orderedQuery.ThenBy(l => (l.data.msMax - l.data.msMin), ascending); |
|
break; |
|
case SortOption.Count: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.count, ascending); |
|
break; |
|
case SortOption.CountMean: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.countMean, ascending); |
|
break; |
|
case SortOption.CountStandardDeviation: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.countStandardDeviation, ascending); |
|
break; |
|
case SortOption.FirstFrame: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.firstFrameIndex, ascending); |
|
break; |
|
case SortOption.AtMedian: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msAtMedian, ascending); |
|
break; |
|
case SortOption.Total: |
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msTotal, ascending); |
|
break; |
|
case SortOption.Threads: |
|
orderedQuery = orderedQuery.ThenBy(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending); |
|
break; |
|
} |
|
} |
|
|
|
rootItem.children = orderedQuery.Cast<TreeViewItem>().ToList(); |
|
} |
|
|
|
IOrderedEnumerable<ProfileTreeViewItem> InitialOrder(IEnumerable<ProfileTreeViewItem> myTypes, int[] history) |
|
{ |
|
SortOption sortOption = m_SortOptions[history[0]]; |
|
bool ascending = multiColumnHeader.IsSortedAscending(history[0]); |
|
switch (sortOption) |
|
{ |
|
case SortOption.Name: |
|
return myTypes.Order(l => l.data.name, ascending); |
|
case SortOption.State: |
|
return myTypes.Order(l => State(l), ascending); |
|
case SortOption.Depth: |
|
return myTypes.Order(l => l.data.minDepth, ascending); |
|
case SortOption.Mean: |
|
return myTypes.Order(l => l.data.msMean, ascending); |
|
case SortOption.Median: |
|
return myTypes.Order(l => l.data.msMedian, ascending); |
|
case SortOption.StandardDeviation: |
|
return myTypes.Order(l => l.data.msStandardDeviation, ascending); |
|
case SortOption.Min: |
|
return myTypes.Order(l => l.data.msMin, ascending); |
|
case SortOption.Max: |
|
return myTypes.Order(l => l.data.msMax, ascending); |
|
case SortOption.Range: |
|
return myTypes.Order(l => (l.data.msMax - l.data.msMin), ascending); |
|
case SortOption.Count: |
|
return myTypes.Order(l => l.data.count, ascending); |
|
case SortOption.CountMean: |
|
return myTypes.Order(l => l.data.countMean, ascending); |
|
case SortOption.CountStandardDeviation: |
|
return myTypes.Order(l => l.data.countStandardDeviation, ascending); |
|
case SortOption.FirstFrame: |
|
return myTypes.Order(l => l.data.firstFrameIndex, ascending); |
|
case SortOption.AtMedian: |
|
return myTypes.Order(l => l.data.msAtMedian, ascending); |
|
case SortOption.Total: |
|
return myTypes.Order(l => l.data.msTotal, ascending); |
|
case SortOption.Threads: |
|
return myTypes.Order(l => l.cachedRowString != null ? l.cachedRowString[(int)MyColumns.Threads].text : GetThreadNames(l), ascending); |
|
default: |
|
Assert.IsTrue(false, "Unhandled enum"); |
|
break; |
|
} |
|
|
|
|
|
return myTypes.Order(l => l.data.name, ascending); |
|
} |
|
|
|
int State(ProfileTreeViewItem item) |
|
{ |
|
if (item.data.timeRemoved > 0.0) |
|
{ |
|
return -3; |
|
} |
|
if (item.data.timeIgnored > 0.0) |
|
{ |
|
if (item.data.IsFullyIgnored()) |
|
return -2; |
|
else |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
public bool ShowingHorizontalScroll |
|
{ |
|
get |
|
{ |
|
return showingHorizontalScrollBar; |
|
} |
|
} |
|
|
|
protected override void RowGUI(RowGUIArgs args) |
|
{ |
|
var item = (ProfileTreeViewItem)args.item; |
|
|
|
var clipRect = m_2D.GetClipRect(); |
|
clipRect.y = state.scrollPos.y; |
|
clipRect.x = state.scrollPos.x; |
|
m_2D.SetClipRect(clipRect); |
|
|
|
if (item.cachedRowString == null) |
|
{ |
|
GenerateStrings(item); |
|
} |
|
|
|
for (int i = 0; i < args.GetNumVisibleColumns(); ++i) |
|
{ |
|
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args); |
|
} |
|
m_2D.ClearClipRect(); |
|
} |
|
|
|
string ToDisplayUnits(float ms, bool showUnits = false, bool showFullValueWhenBelowZero = false) |
|
{ |
|
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero); |
|
} |
|
|
|
string ToDisplayUnits(double ms, bool showUnits = false, bool showFullValueWhenBelowZero = false) |
|
{ |
|
return m_ProfileAnalyzerWindow.ToDisplayUnits(ms, showUnits, 0, showFullValueWhenBelowZero); |
|
} |
|
|
|
string ToTooltipDisplayUnits(float ms, bool showUnits = false, int onFrame = -1) |
|
{ |
|
return m_ProfileAnalyzerWindow.ToTooltipDisplayUnits(ms, showUnits, onFrame); |
|
} |
|
|
|
string ToTooltipDisplayUnits(double ms, bool showUnits = false, int onFrame = -1) |
|
{ |
|
return ToTooltipDisplayUnits((float)ms, showUnits, onFrame); |
|
} |
|
|
|
GUIContent ToDisplayUnitsWithTooltips(float ms, bool showUnits = false, int onFrame = -1) |
|
{ |
|
return m_ProfileAnalyzerWindow.ToDisplayUnitsWithTooltips(ms, showUnits, onFrame); |
|
} |
|
|
|
GUIContent ToDisplayUnitsWithTooltips(double ms, bool showUnits = false, int onFrame = -1) |
|
{ |
|
return ToDisplayUnitsWithTooltips((float)ms, showUnits, onFrame); |
|
} |
|
|
|
void CopyToClipboard(Event current, string text) |
|
{ |
|
EditorGUIUtility.systemCopyBuffer = text; |
|
} |
|
|
|
GenericMenu GenerateActiveContextMenu(string markerName, Event evt, GUIContent content) |
|
{ |
|
GenericMenu menu = new GenericMenu(); |
|
|
|
menu.AddItem(Styles.menuItemSelectFramesInAll, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, false)); |
|
menu.AddItem(Styles.menuItemSelectFramesInCurrent, false, () => m_ProfileAnalyzerWindow.SelectFramesContainingMarker(markerName, true)); |
|
|
|
if (m_ProfileAnalyzerWindow.AllSelected()) |
|
menu.AddDisabledItem(Styles.menuItemSelectFramesAll); |
|
else |
|
menu.AddItem(Styles.menuItemSelectFramesAll, false, () => m_ProfileAnalyzerWindow.SelectAllFrames()); |
|
|
|
menu.AddSeparator(""); |
|
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase)) |
|
menu.AddItem(Styles.menuItemAddToIncludeFilter, false, () => m_ProfileAnalyzerWindow.AddToIncludeFilter(markerName)); |
|
else |
|
menu.AddItem(Styles.menuItemRemoveFromIncludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromIncludeFilter(markerName)); |
|
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase)) |
|
menu.AddItem(Styles.menuItemAddToExcludeFilter, false, () => m_ProfileAnalyzerWindow.AddToExcludeFilter(markerName)); |
|
else |
|
menu.AddItem(Styles.menuItemRemoveFromExcludeFilter, false, () => m_ProfileAnalyzerWindow.RemoveFromExcludeFilter(markerName)); |
|
menu.AddSeparator(""); |
|
menu.AddItem(Styles.menuItemSetAsParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter(markerName)); |
|
menu.AddItem(Styles.menuItemClearParentMarkerFilter, false, () => m_ProfileAnalyzerWindow.SetAsParentMarkerFilter("")); |
|
menu.AddSeparator(""); |
|
menu.AddItem(Styles.menuItemSetAsRemoveMarker, false, () => m_ProfileAnalyzerWindow.SetAsRemoveMarker(markerName)); |
|
menu.AddSeparator(""); |
|
if (markerName != null && !string.IsNullOrEmpty(markerName)) |
|
menu.AddItem(Styles.menuItemCopyToClipboard, false, () => CopyToClipboard(evt, markerName)); |
|
|
|
return menu; |
|
} |
|
|
|
GenericMenu GenerateDisabledContextMenu(string markerName, GUIContent content) |
|
{ |
|
GenericMenu menu = new GenericMenu(); |
|
|
|
menu.AddDisabledItem(Styles.menuItemSelectFramesInAll); |
|
menu.AddDisabledItem(Styles.menuItemSelectFramesInCurrent); |
|
menu.AddDisabledItem(Styles.menuItemSelectFramesAll); |
|
|
|
menu.AddSeparator(""); |
|
if (!m_ProfileAnalyzerWindow.GetNameFilters().Contains(markerName, StringComparer.OrdinalIgnoreCase)) |
|
menu.AddDisabledItem(Styles.menuItemAddToIncludeFilter); |
|
else |
|
menu.AddDisabledItem(Styles.menuItemRemoveFromIncludeFilter); |
|
if (!m_ProfileAnalyzerWindow.GetNameExcludes().Contains(markerName, StringComparer.OrdinalIgnoreCase)) |
|
menu.AddDisabledItem(Styles.menuItemAddToExcludeFilter); |
|
else |
|
menu.AddDisabledItem(Styles.menuItemRemoveFromExcludeFilter); |
|
menu.AddSeparator(""); |
|
menu.AddDisabledItem(Styles.menuItemSetAsParentMarkerFilter); |
|
menu.AddDisabledItem(Styles.menuItemClearParentMarkerFilter); |
|
menu.AddSeparator(""); |
|
menu.AddDisabledItem(Styles.menuItemSetAsRemoveMarker); |
|
menu.AddSeparator(""); |
|
if (content != null && !string.IsNullOrEmpty(content.text)) |
|
menu.AddDisabledItem(Styles.menuItemCopyToClipboard); |
|
|
|
return menu; |
|
} |
|
|
|
|
|
void ShowContextMenu(Rect cellRect, string markerName, GUIContent content) |
|
{ |
|
Event current = Event.current; |
|
if (cellRect.Contains(current.mousePosition) && current.type == EventType.ContextClick) |
|
{ |
|
GenericMenu menu; |
|
if (!m_ProfileAnalyzerWindow.IsAnalysisRunning()) |
|
menu = GenerateActiveContextMenu(markerName, current, content); |
|
else |
|
menu = GenerateDisabledContextMenu(markerName, content); |
|
|
|
menu.ShowAsContext(); |
|
|
|
current.Use(); |
|
} |
|
} |
|
|
|
void ShowText(Rect rect, string text) |
|
{ |
|
EditorGUI.LabelField(rect, text); |
|
|
|
} |
|
|
|
void ShowText(Rect rect, GUIContent content) |
|
{ |
|
EditorGUI.LabelField(rect, content); |
|
|
|
} |
|
|
|
void GenerateStrings(ProfileTreeViewItem item) |
|
{ |
|
item.cachedRowString = new GUIContent[m_MaxColumns]; |
|
|
|
int medianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.medianFrameIndex, m_DataView); |
|
int minFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.minFrameIndex, m_DataView); |
|
int maxFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.maxFrameIndex, m_DataView); |
|
int firstFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(item.data.firstFrameIndex, m_DataView); |
|
int frameSummaryMedianFrameIndex = m_ProfileAnalyzerWindow.GetRemappedUIFrameIndex(m_DataView.analysis.GetFrameSummary().medianFrameIndex, m_DataView); |
|
|
|
if (item.data.timeRemoved > 0.0) |
|
{ |
|
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Modified]", item.data.name + "\n\nTime reduced by removing child marker time"); |
|
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Modified", "Time reduced by removing child marker time"); |
|
} |
|
else if (item.data.timeIgnored > 0.0) |
|
{ |
|
if (item.data.IsFullyIgnored()) |
|
{ |
|
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Removed]", item.data.name + "\n\nAll marker time removed"); |
|
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Removed", "All marker time removed"); |
|
} |
|
else |
|
{ |
|
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name + " [Partial Removal]", item.data.name + "\n\nSome marker time removed (some instances)"); |
|
item.cachedRowString[(int)MyColumns.State] = new GUIContent("Partial Removal", "Some marker time removed (some instances)"); |
|
} |
|
} |
|
else |
|
{ |
|
item.cachedRowString[(int)MyColumns.Name] = new GUIContent(item.data.name, item.data.name); |
|
item.cachedRowString[(int)MyColumns.State] = new GUIContent("", ""); |
|
} |
|
item.cachedRowString[(int)MyColumns.Mean] = ToDisplayUnitsWithTooltips(item.data.msMean, false); |
|
item.cachedRowString[(int)MyColumns.Depth] = (item.data.minDepth == item.data.maxDepth) ? new GUIContent(string.Format("{0}", item.data.minDepth), "") : new GUIContent(string.Format("{0}-{1}", item.data.minDepth, item.data.maxDepth), ""); |
|
item.cachedRowString[(int)MyColumns.Median] = ToDisplayUnitsWithTooltips(item.data.msMedian, false, medianFrameIndex); |
|
string tooltip = ToTooltipDisplayUnits(item.data.msMedian, true, medianFrameIndex); |
|
item.cachedRowString[(int)MyColumns.MedianBar] = new GUIContent("", tooltip); |
|
item.cachedRowString[(int)MyColumns.StandardDeviation] = ToDisplayUnitsWithTooltips(item.data.msStandardDeviation, false); |
|
item.cachedRowString[(int)MyColumns.Min] = ToDisplayUnitsWithTooltips(item.data.msMin, false, minFrameIndex); |
|
item.cachedRowString[(int)MyColumns.Max] = ToDisplayUnitsWithTooltips(item.data.msMax, false, maxFrameIndex); |
|
item.cachedRowString[(int)MyColumns.Range] = ToDisplayUnitsWithTooltips(item.data.msMax - item.data.msMin); |
|
item.cachedRowString[(int)MyColumns.Count] = new GUIContent(string.Format("{0}", item.data.count), ""); |
|
item.cachedRowString[(int)MyColumns.CountBar] = new GUIContent("", string.Format("{0}", item.data.count)); |
|
item.cachedRowString[(int)MyColumns.CountMean] = new GUIContent(string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countMean), ""); |
|
item.cachedRowString[(int)MyColumns.CountMeanBar] = new GUIContent("", string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countMean)); |
|
item.cachedRowString[(int)MyColumns.CountStandardDeviation] = new GUIContent(string.Format(CultureInfo.InvariantCulture, "{0:f0}", item.data.countStandardDeviation), string.Format(CultureInfo.InvariantCulture, "{0}", item.data.countStandardDeviation)); |
|
item.cachedRowString[(int)MyColumns.FirstFrame] = new GUIContent(firstFrameIndex.ToString()); |
|
item.cachedRowString[(int)MyColumns.AtMedian] = ToDisplayUnitsWithTooltips(item.data.msAtMedian, false, frameSummaryMedianFrameIndex); |
|
item.cachedRowString[(int)MyColumns.Total] = ToDisplayUnitsWithTooltips(item.data.msTotal); |
|
tooltip = ToTooltipDisplayUnits(item.data.msTotal, true, medianFrameIndex); |
|
item.cachedRowString[(int)MyColumns.TotalBar] = new GUIContent("", tooltip); |
|
|
|
string threadNames = GetThreadNames(item); |
|
item.cachedRowString[(int)MyColumns.Threads] = new GUIContent(threadNames, threadNames); |
|
} |
|
|
|
void ShowBar(Rect rect, float ms, float range, GUIContent content) |
|
{ |
|
if (ms > 0.0f) |
|
{ |
|
if (m_2D.DrawStart(rect)) |
|
{ |
|
float w = Math.Max(1.0f, rect.width * ms / range); |
|
m_2D.DrawFilledBox(0, 1, w, rect.height - 1, m_BarColor); |
|
m_2D.DrawEnd(); |
|
} |
|
} |
|
GUI.Label(rect, content); |
|
} |
|
|
|
void CellGUI(Rect cellRect, ProfileTreeViewItem item, MyColumns column, ref RowGUIArgs args) |
|
{ |
|
|
|
CenterRectUsingSingleLineHeight(ref cellRect); |
|
|
|
GUIContent content = item.cachedRowString[(int)column]; |
|
switch (column) |
|
{ |
|
case MyColumns.Name: |
|
{ |
|
args.rowRect = cellRect; |
|
|
|
|
|
ShowText(cellRect, content); |
|
} |
|
break; |
|
|
|
case MyColumns.State: |
|
case MyColumns.Mean: |
|
case MyColumns.Depth: |
|
case MyColumns.Median: |
|
case MyColumns.StandardDeviation: |
|
case MyColumns.Min: |
|
case MyColumns.Max: |
|
case MyColumns.Range: |
|
case MyColumns.Count: |
|
case MyColumns.CountMean: |
|
case MyColumns.CountStandardDeviation: |
|
case MyColumns.AtMedian: |
|
case MyColumns.Total: |
|
case MyColumns.Threads: |
|
ShowText(cellRect, content); |
|
break; |
|
case MyColumns.MedianBar: |
|
ShowBar(cellRect, item.data.msMedian, m_MaxMedian, content); |
|
break; |
|
case MyColumns.TotalBar: |
|
ShowBar(cellRect, (float)item.data.msTotal, (float)m_MaxTotal, content); |
|
break; |
|
case MyColumns.CountBar: |
|
ShowBar(cellRect, item.data.count, m_MaxCount, content); |
|
break; |
|
case MyColumns.CountMeanBar: |
|
ShowBar(cellRect, item.data.countMean, m_MaxCountMean, content); |
|
break; |
|
case MyColumns.FirstFrame: |
|
if (!m_ProfileAnalyzerWindow.IsProfilerWindowOpen() || !m_DataView.inSyncWithProfilerData) |
|
GUI.enabled = false; |
|
if (GUI.Button(cellRect, content)) |
|
{ |
|
m_ProfileAnalyzerWindow.SelectMarkerByIndex(item.id); |
|
m_ProfileAnalyzerWindow.JumpToFrame(item.data.firstFrameIndex, m_DataView.data); |
|
} |
|
|
|
GUI.enabled = true; |
|
break; |
|
} |
|
|
|
ShowContextMenu(cellRect, item.data.name, content); |
|
} |
|
|
|
|
|
|
|
|
|
protected override bool CanMultiSelect(TreeViewItem item) |
|
{ |
|
return false; |
|
} |
|
|
|
struct HeaderData |
|
{ |
|
public readonly GUIContent content; |
|
public readonly float width; |
|
public readonly float minWidth; |
|
public readonly bool autoResize; |
|
public readonly bool allowToggleVisibility; |
|
public readonly bool ascending; |
|
|
|
public HeaderData(string name, string tooltip = "", float width = 50, float minWidth = 30, bool autoResize = true, bool allowToggleVisibility = true, bool ascending = false) |
|
{ |
|
content = new GUIContent(name, tooltip); |
|
this.width = width; |
|
this.minWidth = minWidth; |
|
this.autoResize = autoResize; |
|
this.allowToggleVisibility = allowToggleVisibility; |
|
this.ascending = ascending; |
|
} |
|
} |
|
|
|
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(MarkerColumnFilter modeFilter) |
|
{ |
|
var columnList = new List<MultiColumnHeaderState.Column>(); |
|
HeaderData[] headerData = new HeaderData[] |
|
{ |
|
new HeaderData("Marker Name", "Marker Name\n\nFrame marker time is total of all instances in frame", width: 300, minWidth: 100, autoResize: false, allowToggleVisibility: false, ascending: true), |
|
new HeaderData("State", "Status of marker entry (if modified or removed from frame time due to 'Remove' filter)"), |
|
new HeaderData("Depth", "Marker depth in marker hierarchy\n\nMay appear at multiple levels"), |
|
new HeaderData("Median", "Central marker time over all selected frames\n\nAlways present in data set\n1st of 2 central values for even frame count"), |
|
new HeaderData("Median Bar", "Central marker time over all selected frames", width: 50), |
|
new HeaderData("Mean", "Per frame marker time / number of non zero frames"), |
|
new HeaderData("SD", "Standard deviation in marker times"), |
|
new HeaderData("Min", "Minimum marker time"), |
|
new HeaderData("Max", "Maximum marker time"), |
|
new HeaderData("Range", "Difference between maximum and minimum"), |
|
new HeaderData("Count", "Marker count over all selected frames\n\nMultiple can occur per frame"), |
|
new HeaderData("Count Bar", "Marker count over all selected frames\n\nMultiple can occur per frame"), |
|
new HeaderData("Count Frame", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50), |
|
new HeaderData("Count Frame Bar", "Average number of markers per frame\n\ntotal count / number of non zero frames", width: 70, minWidth: 50), |
|
new HeaderData("Count SD", "Standard deviation in marker per frame counts"), |
|
new HeaderData("1st", "First frame index that the marker appears on"), |
|
new HeaderData("At Median Frame", "Marker time on the median frame\n\nI.e. Marker total duration on the average frame", width: 90, minWidth: 50), |
|
new HeaderData("Total", "Marker total time over all selected frames"), |
|
new HeaderData("Total Bar", "Marker total time over all selected frames"), |
|
new HeaderData("Threads", "Threads the marker occurs on (with filtering applied)"), |
|
}; |
|
foreach (var header in headerData) |
|
{ |
|
columnList.Add(new MultiColumnHeaderState.Column |
|
{ |
|
headerContent = header.content, |
|
headerTextAlignment = TextAlignment.Left, |
|
sortedAscending = header.ascending, |
|
sortingArrowAlignment = TextAlignment.Left, |
|
width = header.width, |
|
minWidth = header.minWidth, |
|
autoResize = header.autoResize, |
|
allowToggleVisibility = header.allowToggleVisibility |
|
}); |
|
} |
|
; |
|
var columns = columnList.ToArray(); |
|
|
|
m_MaxColumns = Enum.GetValues(typeof(MyColumns)).Length; |
|
Assert.AreEqual(columns.Length, m_MaxColumns, "Number of columns should match number of enum values: You probably forgot to update one of them."); |
|
|
|
var state = new MultiColumnHeaderState(columns); |
|
SetMode(modeFilter, state); |
|
return state; |
|
} |
|
|
|
protected override void SelectionChanged(IList<int> selectedIds) |
|
{ |
|
base.SelectionChanged(selectedIds); |
|
|
|
if (selectedIds.Count > 0) |
|
{ |
|
m_ProfileAnalyzerWindow.SelectMarkerByIndex(selectedIds[0]); |
|
|
|
GUIUtility.ExitGUI(); |
|
} |
|
} |
|
|
|
static int[] GetDefaultVisibleColumns(MarkerColumnFilter.Mode mode) |
|
{ |
|
int[] visibleColumns; |
|
|
|
switch (mode) |
|
{ |
|
default: |
|
case MarkerColumnFilter.Mode.Custom: |
|
case MarkerColumnFilter.Mode.TimeAndCount: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Depth, |
|
(int)MyColumns.Median, |
|
(int)MyColumns.MedianBar, |
|
(int)MyColumns.Mean, |
|
(int)MyColumns.Min, |
|
(int)MyColumns.Max, |
|
(int)MyColumns.Range, |
|
(int)MyColumns.Count, |
|
(int)MyColumns.CountMean, |
|
(int)MyColumns.AtMedian, |
|
}; |
|
break; |
|
case MarkerColumnFilter.Mode.Time: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Depth, |
|
(int)MyColumns.Median, |
|
(int)MyColumns.MedianBar, |
|
(int)MyColumns.Min, |
|
(int)MyColumns.Max, |
|
(int)MyColumns.Range, |
|
(int)MyColumns.AtMedian, |
|
}; |
|
break; |
|
case MarkerColumnFilter.Mode.Totals: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Depth, |
|
(int)MyColumns.Total, |
|
(int)MyColumns.TotalBar, |
|
}; |
|
break; |
|
case MarkerColumnFilter.Mode.TimeWithTotals: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Depth, |
|
(int)MyColumns.Median, |
|
(int)MyColumns.MedianBar, |
|
(int)MyColumns.Min, |
|
(int)MyColumns.Max, |
|
(int)MyColumns.Range, |
|
(int)MyColumns.AtMedian, |
|
(int)MyColumns.Total, |
|
(int)MyColumns.TotalBar, |
|
}; |
|
break; |
|
case MarkerColumnFilter.Mode.CountTotals: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Depth, |
|
(int)MyColumns.Count, |
|
(int)MyColumns.CountBar, |
|
}; |
|
break; |
|
case MarkerColumnFilter.Mode.CountPerFrame: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Depth, |
|
(int)MyColumns.CountMean, |
|
(int)MyColumns.CountMeanBar, |
|
}; |
|
break; |
|
case MarkerColumnFilter.Mode.Depth: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Depth, |
|
}; |
|
break; |
|
case MarkerColumnFilter.Mode.Threads: |
|
visibleColumns = new int[] |
|
{ |
|
(int)MyColumns.Name, |
|
(int)MyColumns.Threads, |
|
}; |
|
break; |
|
} |
|
|
|
return visibleColumns; |
|
} |
|
|
|
static void SetMode(MarkerColumnFilter modeFilter, MultiColumnHeaderState state) |
|
{ |
|
switch (modeFilter.mode) |
|
{ |
|
case MarkerColumnFilter.Mode.Custom: |
|
if (modeFilter.visibleColumns == null) |
|
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode); |
|
else |
|
state.visibleColumns = modeFilter.visibleColumns; |
|
break; |
|
default: |
|
state.visibleColumns = GetDefaultVisibleColumns(modeFilter.mode); |
|
break; |
|
} |
|
|
|
if (modeFilter.visibleColumns == null) |
|
modeFilter.visibleColumns = state.visibleColumns; |
|
} |
|
|
|
public void SetMode(MarkerColumnFilter modeFilter) |
|
{ |
|
SetMode(modeFilter, multiColumnHeader.state); |
|
multiColumnHeader.ResizeToFit(); |
|
} |
|
} |
|
|
|
static class MyExtensionMethods |
|
{ |
|
public static IOrderedEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending) |
|
{ |
|
if (ascending) |
|
{ |
|
return source.OrderBy(selector); |
|
} |
|
else |
|
{ |
|
return source.OrderByDescending(selector); |
|
} |
|
} |
|
|
|
public static IOrderedEnumerable<T> ThenBy<T, TKey>(this IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending) |
|
{ |
|
if (ascending) |
|
{ |
|
return source.ThenBy(selector); |
|
} |
|
else |
|
{ |
|
return source.ThenByDescending(selector); |
|
} |
|
} |
|
} |
|
} |
|
|