您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
483 行
18 KiB
483 行
18 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEditor.IMGUI.Controls;
|
|
using UnityEngine;
|
|
using UnityEngine.Assertions;
|
|
|
|
namespace ProfileAnalyser
|
|
{
|
|
class ProfileTreeViewItem: TreeViewItem
|
|
{
|
|
public MarkerData data { get; set; }
|
|
|
|
public ProfileTreeViewItem(int id, int depth, string displayName, MarkerData data) : base(id, depth, displayName)
|
|
{
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
class ProfileTable : TreeView
|
|
{
|
|
ProfileAnalysis m_model;
|
|
ProfileAnalyserWindow m_profileAnalyserWindow;
|
|
float m_maxMedian;
|
|
|
|
const float kRowHeights = 20f;
|
|
readonly List<TreeViewItem> m_Rows = new List<TreeViewItem>(100);
|
|
|
|
// All columns
|
|
public enum MyColumns
|
|
{
|
|
Name,
|
|
Depth,
|
|
Median,
|
|
MedianBar,
|
|
Average,
|
|
Min,
|
|
Max,
|
|
Range,
|
|
Count,
|
|
CountAverage,
|
|
FirstFrame,
|
|
AtMedian,
|
|
}
|
|
|
|
public enum SortOption
|
|
{
|
|
Name,
|
|
Depth,
|
|
Median,
|
|
Average,
|
|
Min,
|
|
Max,
|
|
Range,
|
|
Count,
|
|
FirstFrame,
|
|
AtMedian,
|
|
}
|
|
|
|
// Sort options per column
|
|
SortOption[] m_SortOptions =
|
|
{
|
|
SortOption.Name,
|
|
SortOption.Depth,
|
|
SortOption.Median,
|
|
SortOption.Median,
|
|
SortOption.Average,
|
|
SortOption.Min,
|
|
SortOption.Max,
|
|
SortOption.Range,
|
|
SortOption.Count,
|
|
SortOption.Count,
|
|
SortOption.FirstFrame,
|
|
SortOption.AtMedian,
|
|
};
|
|
|
|
|
|
public ProfileTable(TreeViewState state, MultiColumnHeader multicolumnHeader, ProfileAnalysis model, ProfileAnalyserWindow profileAnalyserWindow) : base(state, multicolumnHeader)
|
|
{
|
|
m_model = model;
|
|
m_profileAnalyserWindow = profileAnalyserWindow;
|
|
|
|
Assert.AreEqual(m_SortOptions.Length, Enum.GetValues(typeof(MyColumns)).Length, "Ensure number of sort options are in sync with number of MyColumns enum values");
|
|
|
|
// Custom setup
|
|
rowHeight = kRowHeights;
|
|
showAlternatingRowBackgrounds = true;
|
|
showBorder = true;
|
|
customFoldoutYOffset = (kRowHeights - EditorGUIUtility.singleLineHeight) * 0.5f; // center foldout in the row since we also center content. See RowGUI
|
|
// extraSpaceBeforeIconAndLabel = 0;
|
|
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;
|
|
int index = 0;
|
|
foreach (var marker in m_model.GetMarkers())
|
|
{
|
|
if (m_profileAnalyserWindow.CheckMarkerValid(marker))
|
|
{
|
|
var item = new ProfileTreeViewItem(index, 0, marker.name, marker);
|
|
root.AddChild(item);
|
|
float ms = item.data.msMedian;
|
|
if (ms > m_maxMedian)
|
|
m_maxMedian = ms;
|
|
}
|
|
// Maintain index to map to main markers
|
|
index += 1;
|
|
}
|
|
|
|
return root;
|
|
}
|
|
|
|
protected override IList<TreeViewItem> BuildRows(TreeViewItem root)
|
|
{
|
|
m_Rows.Clear();
|
|
|
|
if (rootItem!=null && rootItem.children!=null)
|
|
{
|
|
foreach (ProfileTreeViewItem node in rootItem.children)
|
|
{
|
|
if (m_profileAnalyserWindow.CheckMarkerValid(node.data))
|
|
m_Rows.Add(node);
|
|
}
|
|
}
|
|
|
|
SortIfNeeded(m_Rows);
|
|
|
|
return m_Rows;
|
|
}
|
|
|
|
|
|
void OnSortingChanged(MultiColumnHeader _multiColumnHeader)
|
|
{
|
|
SortIfNeeded(GetRows());
|
|
}
|
|
|
|
protected virtual void OnVisibleColumnsChanged(MultiColumnHeader multiColumnHeader)
|
|
{
|
|
m_profileAnalyserWindow.SetMode(Mode.Custom);
|
|
}
|
|
|
|
void SortIfNeeded(IList<TreeViewItem> rows)
|
|
{
|
|
if (rows.Count <= 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (multiColumnHeader.sortedColumnIndex == -1)
|
|
{
|
|
return; // No column to sort for (just use the order the data are in)
|
|
}
|
|
|
|
// Sort the roots of the existing tree items
|
|
SortByMultipleColumns();
|
|
|
|
// Update the data with the sorted content
|
|
rows.Clear();
|
|
foreach (ProfileTreeViewItem node in rootItem.children)
|
|
{
|
|
rows.Add(node);
|
|
}
|
|
|
|
Repaint();
|
|
}
|
|
|
|
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.Depth:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.minDepth, ascending);
|
|
break;
|
|
case SortOption.Average:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msFrameAverage, ascending);
|
|
break;
|
|
case SortOption.Median:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msMedian, 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.FirstFrame:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.firstFrameIndex, ascending);
|
|
break;
|
|
case SortOption.AtMedian:
|
|
orderedQuery = orderedQuery.ThenBy(l => l.data.msAtMedian, 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.Depth:
|
|
return myTypes.Order(l => l.data.minDepth, ascending);
|
|
case SortOption.Average:
|
|
return myTypes.Order(l => l.data.msFrameAverage, ascending);
|
|
case SortOption.Median:
|
|
return myTypes.Order(l => l.data.msMedian, 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.FirstFrame:
|
|
return myTypes.Order(l => l.data.firstFrameIndex, ascending);
|
|
case SortOption.AtMedian:
|
|
return myTypes.Order(l => l.data.msAtMedian, ascending);
|
|
default:
|
|
Assert.IsTrue(false, "Unhandled enum");
|
|
break;
|
|
}
|
|
|
|
// default
|
|
return myTypes.Order(l => l.data.name, ascending);
|
|
}
|
|
|
|
protected override void RowGUI(RowGUIArgs args)
|
|
{
|
|
var item = (ProfileTreeViewItem)args.item;
|
|
|
|
for (int i = 0; i < args.GetNumVisibleColumns(); ++i)
|
|
{
|
|
CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args);
|
|
}
|
|
}
|
|
|
|
void CellGUI(Rect cellRect, ProfileTreeViewItem item, MyColumns column, ref RowGUIArgs args)
|
|
{
|
|
// Center cell rect vertically (makes it easier to place controls, icons etc in the cells)
|
|
CenterRectUsingSingleLineHeight(ref cellRect);
|
|
|
|
switch (column)
|
|
{
|
|
case MyColumns.Name:
|
|
{
|
|
args.rowRect = cellRect;
|
|
base.RowGUI(args);
|
|
}
|
|
break;
|
|
|
|
case MyColumns.Average:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0:f2}", item.data.msFrameAverage));
|
|
break;
|
|
case MyColumns.Depth:
|
|
if (m_profileAnalyserWindow.GetDepthFilter() >= 0 || item.data.minDepth == item.data.maxDepth)
|
|
EditorGUI.LabelField(cellRect, string.Format("{0}", item.data.minDepth));
|
|
else
|
|
EditorGUI.LabelField(cellRect, string.Format("{0}-{1}", item.data.minDepth, item.data.maxDepth));
|
|
break;
|
|
case MyColumns.Median:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0:f2}", item.data.msMedian));
|
|
break;
|
|
case MyColumns.MedianBar:
|
|
{
|
|
float ms = item.data.msMedian;
|
|
if (ms > 0.0f)
|
|
{
|
|
if (m_profileAnalyserWindow.DrawStart(cellRect))
|
|
{
|
|
float w = cellRect.width * ms / m_maxMedian;
|
|
m_profileAnalyserWindow.DrawBar(0, 1, w, cellRect.height - 1, m_profileAnalyserWindow.m_colorBar);
|
|
m_profileAnalyserWindow.DrawEnd();
|
|
}
|
|
}
|
|
GUI.Label(cellRect, new GUIContent("", string.Format("{0:f2}", item.data.msMedian)));
|
|
}
|
|
break;
|
|
case MyColumns.Min:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0:f2}", item.data.msMin));
|
|
break;
|
|
case MyColumns.Max:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0:f2}", item.data.msMax));
|
|
break;
|
|
case MyColumns.Range:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0:f2}", item.data.msMax - item.data.msMin));
|
|
break;
|
|
case MyColumns.Count:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0}", item.data.count));
|
|
break;
|
|
case MyColumns.CountAverage:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0}", item.data.count / m_model.GetFrameSummary().count));
|
|
break;
|
|
case MyColumns.FirstFrame:
|
|
if (!m_profileAnalyserWindow.IsProfilerWindowOpen())
|
|
GUI.enabled = false;
|
|
if (GUI.Button(cellRect, new GUIContent(item.data.firstFrameIndex.ToString())))
|
|
{
|
|
m_profileAnalyserWindow.SelectMarker(item.id);
|
|
m_profileAnalyserWindow.JumpToFrame(item.data.firstFrameIndex);
|
|
}
|
|
|
|
GUI.enabled = true;
|
|
break;
|
|
case MyColumns.AtMedian:
|
|
EditorGUI.LabelField(cellRect, string.Format("{0:f2}", item.data.msAtMedian));
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Misc
|
|
//--------
|
|
|
|
protected override bool CanMultiSelect(TreeViewItem item)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth)
|
|
{
|
|
var columnList = new List<MultiColumnHeaderState.Column>();
|
|
columnList.Add(new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent("Name"),
|
|
headerTextAlignment = TextAlignment.Left,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Left,
|
|
width = 300,
|
|
minWidth = 100,
|
|
autoResize = false,
|
|
allowToggleVisibility = false
|
|
});
|
|
string[] names = {"Depth","Median","Median","Average","Min","Max","Range","Count","Frm Av.", "1st", "At med"};
|
|
foreach (var name in names)
|
|
{
|
|
var column = new MultiColumnHeaderState.Column
|
|
{
|
|
headerContent = new GUIContent(name),
|
|
headerTextAlignment = TextAlignment.Left,
|
|
sortedAscending = true,
|
|
sortingArrowAlignment = TextAlignment.Left,
|
|
width = 50,
|
|
minWidth = 30,
|
|
autoResize = true
|
|
};
|
|
columnList.Add(column);
|
|
};
|
|
var columns = columnList.ToArray();
|
|
|
|
Assert.AreEqual(columns.Length, Enum.GetValues(typeof(MyColumns)).Length, "Number of columns should match number of enum values: You probably forgot to update one of them.");
|
|
|
|
var state = new MultiColumnHeaderState(columns);
|
|
SetMode(Mode.All, state);
|
|
return state;
|
|
}
|
|
|
|
protected override void SelectionChanged(IList<int> selectedIds)
|
|
{
|
|
base.SelectionChanged(selectedIds);
|
|
|
|
if (selectedIds.Count>0)
|
|
m_profileAnalyserWindow.SelectMarker(selectedIds[0]);
|
|
}
|
|
|
|
private static void SetMode(Mode mode, MultiColumnHeaderState state)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case Mode.All:
|
|
state.visibleColumns = new int[] {
|
|
(int)MyColumns.Name,
|
|
(int)MyColumns.Depth,
|
|
(int)MyColumns.Median,
|
|
(int)MyColumns.MedianBar,
|
|
(int)MyColumns.Average,
|
|
(int)MyColumns.Min,
|
|
(int)MyColumns.Max,
|
|
(int)MyColumns.Range,
|
|
//(int)MyColumns.FirstFrame,
|
|
(int)MyColumns.Count,
|
|
(int)MyColumns.CountAverage,
|
|
(int)MyColumns.AtMedian
|
|
};
|
|
break;
|
|
case Mode.Time:
|
|
state.visibleColumns = new int[] {
|
|
(int)MyColumns.Name,
|
|
(int)MyColumns.Depth,
|
|
(int)MyColumns.Median,
|
|
(int)MyColumns.MedianBar,
|
|
//(int)MyColumns.Average,
|
|
(int)MyColumns.Min,
|
|
(int)MyColumns.Max,
|
|
(int)MyColumns.Range,
|
|
//(int)MyColumns.FirstFrame,
|
|
(int)MyColumns.AtMedian
|
|
};
|
|
break;
|
|
case Mode.Count:
|
|
state.visibleColumns = new int[] {
|
|
(int)MyColumns.Name,
|
|
(int)MyColumns.Depth,
|
|
(int)MyColumns.Count,
|
|
(int)MyColumns.CountAverage,
|
|
//(int)MyColumns.FirstFrame,
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void SetMode(Mode mode)
|
|
{
|
|
SetMode(mode, 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);
|
|
}
|
|
}
|
|
}
|
|
}
|