679 lines
23 KiB
C#
679 lines
23 KiB
C#
/*
|
||
* ==============================================================================
|
||
* Filename: LuaExport
|
||
* Created: 2018/7/13 14:29:22
|
||
* Author: エル・プサイ・コングリィ
|
||
* Purpose:
|
||
* ==============================================================================
|
||
*/
|
||
|
||
namespace MikuLuaProfiler
|
||
{
|
||
#if UNITY_5_6_OR_NEWER
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Reflection;
|
||
using UnityEditor;
|
||
using UnityEditor.IMGUI.Controls;
|
||
using UnityEngine;
|
||
|
||
|
||
#region item
|
||
//The TreeElement data class is extended to hold extra data, which you can show and edit in the front-end TreeView.
|
||
public class LuaProfilerTreeViewItem : TreeViewItem
|
||
{
|
||
private static ObjectPool<LuaProfilerTreeViewItem> objectPool = new ObjectPool<LuaProfilerTreeViewItem>(30);
|
||
public static LuaProfilerTreeViewItem Create(Sample sample, int depth, LuaProfilerTreeViewItem father)
|
||
{
|
||
var dict = LuaProfilerTreeView.m_nodeDict;
|
||
|
||
LuaProfilerTreeViewItem mt;
|
||
mt = objectPool.GetObject();
|
||
mt.ResetBySample(sample, depth, father);
|
||
dict.Add(mt.fullName, mt);
|
||
|
||
return mt;
|
||
}
|
||
public void Restore()
|
||
{
|
||
objectPool.Store(this);
|
||
}
|
||
|
||
public int frameCalls { private set; get; }
|
||
//没什么意义,因为Lua 执行代码的同时 异步GC,所以导致GC的数字一直闪烁,用上这个去闪烁
|
||
private long _showGC = 0;
|
||
public long showGC
|
||
{
|
||
private set
|
||
{
|
||
_showGC = value;
|
||
}
|
||
get
|
||
{
|
||
return _showGC;
|
||
//if (!UnityEditor.EditorApplication.isPlaying)
|
||
//{
|
||
// return _showGC;
|
||
//}
|
||
|
||
//if (Time.frameCount == _frameCount)
|
||
//{
|
||
// return _showGC;
|
||
//}
|
||
//else { return 0; }
|
||
}
|
||
}
|
||
public long totalMemory { private set; get; }
|
||
public long totalTime { private set; get; }
|
||
public long averageTime { private set; get; }
|
||
public float currentTime { private set; get; }
|
||
public int totalCallTime { private set; get; }
|
||
public string fullName
|
||
{
|
||
get;
|
||
private set;
|
||
}
|
||
public readonly List<LuaProfilerTreeViewItem> childs = new List<LuaProfilerTreeViewItem>();
|
||
public LuaProfilerTreeViewItem rootFather { private set; get; }
|
||
public LuaProfilerTreeViewItem father { private set; get; }
|
||
private int _frameCount;
|
||
public LuaProfilerTreeViewItem()
|
||
{
|
||
}
|
||
|
||
public void ResetBySample(Sample sample, int depth, LuaProfilerTreeViewItem father)
|
||
{
|
||
if (sample != null)
|
||
{
|
||
totalMemory = sample.costGC;
|
||
_showGC = sample.costGC;
|
||
totalTime = (long)(sample.costTime * 1000000);
|
||
displayName = sample.name;
|
||
fullName = sample.fullName;
|
||
}
|
||
else
|
||
{
|
||
totalMemory = 0;
|
||
totalTime = 0;
|
||
displayName = "root";
|
||
fullName = "root";
|
||
}
|
||
totalCallTime = 1;
|
||
averageTime = totalTime / totalCallTime;
|
||
|
||
this.id = LuaProfilerTreeView.GetUniqueId();
|
||
this.depth = depth;
|
||
|
||
|
||
childs.Clear();
|
||
if (sample != null)
|
||
{
|
||
for (int i = 0, imax = sample.childs.Count; i < imax; i++)
|
||
{
|
||
var dict = LuaProfilerTreeView.m_nodeDict;
|
||
|
||
LuaProfilerTreeViewItem mt;
|
||
var childSample = sample.childs[i];
|
||
if (dict.TryGetValue(childSample.fullName, out mt))
|
||
{
|
||
mt.AddSample(childSample);
|
||
}
|
||
else
|
||
{
|
||
var item = Create(sample.childs[i], depth + 1, this);
|
||
childs.Add(item);
|
||
}
|
||
}
|
||
}
|
||
this.father = father;
|
||
|
||
rootFather = this;
|
||
while (true)
|
||
{
|
||
if (rootFather.father == null)
|
||
{
|
||
break;
|
||
}
|
||
rootFather = rootFather.father;
|
||
}
|
||
|
||
_frameCount = Time.frameCount;
|
||
}
|
||
public void AddSample(Sample sample)
|
||
{
|
||
if (_frameCount == sample.frameCount)
|
||
{
|
||
_showGC += sample.costGC;
|
||
frameCalls += sample.calls;
|
||
currentTime += sample.costTime;
|
||
}
|
||
else
|
||
{
|
||
_showGC = sample.costGC;
|
||
frameCalls = sample.calls;
|
||
currentTime = sample.costTime;
|
||
}
|
||
totalMemory += sample.costGC;
|
||
|
||
totalTime += (long)(sample.costTime * 1000000);
|
||
totalCallTime += sample.calls;
|
||
averageTime = totalTime / totalCallTime;
|
||
for (int i = 0, imax = sample.childs.Count; i < imax; i++)
|
||
{
|
||
LuaProfilerTreeViewItem childItem = null;
|
||
var sampleChild = sample.childs[i];
|
||
if (LuaProfilerTreeView.m_nodeDict.TryGetValue(sampleChild.fullName, out childItem))
|
||
{
|
||
childItem.AddSample(sampleChild);
|
||
}
|
||
else
|
||
{
|
||
var treeItem = Create(sampleChild, depth + 1, this);
|
||
childs.Add(treeItem);
|
||
}
|
||
}
|
||
_frameCount = Time.frameCount;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
public class LuaProfilerTreeView : TreeView
|
||
{
|
||
#region pool
|
||
private static int _uniqueId = 0;
|
||
public static int GetUniqueId()
|
||
{
|
||
return _uniqueId++;
|
||
}
|
||
|
||
private readonly List<LuaProfilerTreeViewItem> roots = new List<LuaProfilerTreeViewItem>();
|
||
#endregion
|
||
|
||
#region field
|
||
private int m_showRootItemId = 0;
|
||
public bool m_seaching = false;
|
||
|
||
private List<int> m_expandIds = new List<int>();
|
||
private readonly LuaProfilerTreeViewItem root;
|
||
private readonly List<TreeViewItem> treeViewItems = new List<TreeViewItem>();
|
||
public readonly List<Sample> history = new List<Sample>(216000);
|
||
private GUIStyle gs;
|
||
public bool needRebuild = true;
|
||
|
||
public string startUrl = null;
|
||
public string endUrl = null;
|
||
#endregion
|
||
public LuaProfilerTreeView(TreeViewState treeViewState, float width)
|
||
: base(treeViewState, CreateDefaultMultiColumnHeaderState(width))
|
||
{
|
||
LuaProfiler.SetSampleEnd(LoadRootSample);
|
||
root = LuaProfilerTreeViewItem.Create(null, -1, null);
|
||
needRebuild = true;
|
||
multiColumnHeader.sortingChanged += (multiColumnHeader) => { needRebuild = true; };
|
||
history.Clear();
|
||
Reload();
|
||
}
|
||
|
||
private static MultiColumnHeader CreateDefaultMultiColumnHeaderState(float treeViewWidth)
|
||
{
|
||
var columns = new[]
|
||
{
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("Overview"),
|
||
contextMenuText = "Overview",
|
||
headerTextAlignment = TextAlignment.Left,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 500,
|
||
minWidth = 200,
|
||
maxWidth = 2000,
|
||
autoResize = true,
|
||
canSort = false,
|
||
allowToggleVisibility = true
|
||
},
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("totalMemory"),
|
||
contextMenuText = "totalMemory",
|
||
headerTextAlignment = TextAlignment.Center,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 80,
|
||
minWidth = 80,
|
||
maxWidth = 80,
|
||
autoResize = true,
|
||
canSort = true,
|
||
allowToggleVisibility = false
|
||
},
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("currentTime"),
|
||
contextMenuText = "currentTime",
|
||
headerTextAlignment = TextAlignment.Center,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 120,
|
||
minWidth = 120,
|
||
maxWidth = 120,
|
||
autoResize = true,
|
||
canSort = true,
|
||
allowToggleVisibility = false
|
||
},
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("averageTime"),
|
||
contextMenuText = "averageTime",
|
||
headerTextAlignment = TextAlignment.Center,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 120,
|
||
minWidth = 120,
|
||
maxWidth = 120,
|
||
autoResize = true,
|
||
canSort = true,
|
||
allowToggleVisibility = false
|
||
},
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("totalTime"),
|
||
contextMenuText = "totalTime",
|
||
headerTextAlignment = TextAlignment.Center,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 120,
|
||
minWidth = 120,
|
||
maxWidth = 120,
|
||
autoResize = true,
|
||
canSort = true,
|
||
allowToggleVisibility = false
|
||
},
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("GC"),
|
||
contextMenuText = "GC",
|
||
headerTextAlignment = TextAlignment.Center,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 60,
|
||
minWidth = 60,
|
||
maxWidth = 60,
|
||
autoResize = true,
|
||
canSort = true,
|
||
allowToggleVisibility = false
|
||
},
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("totalCalls"),
|
||
contextMenuText = "totalCalls",
|
||
headerTextAlignment = TextAlignment.Center,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 120,
|
||
minWidth = 120,
|
||
maxWidth = 120,
|
||
autoResize = true,
|
||
canSort = true,
|
||
allowToggleVisibility = false
|
||
},
|
||
new MultiColumnHeaderState.Column
|
||
{
|
||
headerContent = new GUIContent("Calls"),
|
||
contextMenuText = "Calls",
|
||
headerTextAlignment = TextAlignment.Center,
|
||
sortedAscending = false,
|
||
sortingArrowAlignment = TextAlignment.Right,
|
||
width = 80,
|
||
minWidth = 80,
|
||
maxWidth = 80,
|
||
autoResize = true,
|
||
canSort = true,
|
||
allowToggleVisibility = false
|
||
},
|
||
};
|
||
|
||
var state = new MultiColumnHeaderState(columns);
|
||
return new MultiColumnHeader(state);
|
||
}
|
||
|
||
public void Clear(bool includeHistory)
|
||
{
|
||
roots.Clear();
|
||
m_nodeDict.Clear();
|
||
treeViewItems.Clear();
|
||
m_showRootItemId = -2;
|
||
m_expandIds.Clear();
|
||
if (includeHistory)
|
||
{
|
||
history.Clear();
|
||
startUrl = null;
|
||
endUrl = null;
|
||
}
|
||
needRebuild = true;
|
||
}
|
||
|
||
protected override void DoubleClickedItem(int id)
|
||
{
|
||
/*
|
||
base.DoubleClickedItem(id);
|
||
var selectItem = FindItem(id, BuildRoot());
|
||
string fileName = "/" + selectItem.displayName.Split(new char[] { ',' }, 2)[0].Replace(".", "/").Replace("/lua", ".lua").Trim();
|
||
try
|
||
{
|
||
int line = 0;
|
||
int.TryParse(Regex.Match(selectItem.displayName, @"(?<=(line:))\d*(?=( ))").Value, out line);
|
||
//LocalToLuaIDE.OnOpenAsset(fileName, line);
|
||
}
|
||
catch
|
||
{
|
||
}*/
|
||
}
|
||
|
||
private bool CheckIsRootId(int id)
|
||
{
|
||
bool result = false;
|
||
|
||
foreach (var item in roots)
|
||
{
|
||
if (item.id == id)
|
||
{
|
||
result = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
protected override void ExpandedStateChanged()
|
||
{
|
||
var ids = GetExpanded();
|
||
if (setting)
|
||
{
|
||
m_expandIds = new List<int>(ids);
|
||
return;
|
||
}
|
||
base.ExpandedStateChanged();
|
||
|
||
foreach (var id in m_expandIds)
|
||
{
|
||
if (!ids.Contains(id))
|
||
{
|
||
if (m_showRootItemId == id)
|
||
{
|
||
SetExpanded(id, false);
|
||
m_showRootItemId = -2;
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach (var id in ids)
|
||
{
|
||
if (!m_expandIds.Contains(id) && CheckIsRootId(id))
|
||
{
|
||
m_showRootItemId = id;
|
||
break;
|
||
}
|
||
}
|
||
m_expandIds = new List<int>();
|
||
m_expandIds.Add(m_showRootItemId);
|
||
needRebuild = true;
|
||
}
|
||
|
||
public static Dictionary<string, LuaProfilerTreeViewItem> m_nodeDict = new Dictionary<string, LuaProfilerTreeViewItem>();
|
||
|
||
public void ReLoadSamples(int start, int end)
|
||
{
|
||
if (history.Count == 0) return;
|
||
Clear(false);
|
||
end = Mathf.Max(Mathf.Min(end, history.Count - 1), 0);
|
||
start = Mathf.Max(Mathf.Min(start, history.Count - 1), 0);
|
||
|
||
startUrl = history[start].captureUrl;
|
||
endUrl = history[end].captureUrl;
|
||
|
||
end = Mathf.Min(history.Count - 1, end);
|
||
for (int i = start; i <= end; i++)
|
||
{
|
||
LoadRootSample(history[i], false);
|
||
}
|
||
}
|
||
|
||
public void SaveHisotry()
|
||
{
|
||
string path = EditorUtility.SaveFilePanel("Save Sample", "", DateTime.Now.ToString("yyyy-MM-dd-HH-MM-ss"), "sample");
|
||
File.WriteAllBytes(path, Sample.SerializeList(history));
|
||
}
|
||
|
||
public void LoadHistory()
|
||
{
|
||
string path = EditorUtility.OpenFilePanel("Load Sample", "", "sample");
|
||
byte[] datas = File.ReadAllBytes(path);
|
||
history.Clear();
|
||
history.AddRange(Sample.DeserializeList(datas));
|
||
ReLoadSamples(0, history.Count);
|
||
}
|
||
|
||
private Queue<Sample> samplesQueue = new Queue<Sample>(256);
|
||
private void LoadRootSample(Sample sample)
|
||
{
|
||
samplesQueue.Enqueue(sample);
|
||
}
|
||
|
||
public int GetNextProgramFrame(int start)
|
||
{
|
||
int ret = start + 1;
|
||
|
||
for (int i = start + 1, imax = history.Count; i < imax; i++)
|
||
{
|
||
ret = i;
|
||
var s = history[i];
|
||
if (s.costGC > LuaDeepProfilerSetting.Instance.captureGC)
|
||
{
|
||
break;
|
||
}
|
||
else if (s.costTime >= 1 / 30.0f)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
return Mathf.Max(Mathf.Min(ret, history.Count - 1), 0);
|
||
}
|
||
|
||
public int GetPreProgramFrame(int start)
|
||
{
|
||
int ret = start - 1;
|
||
|
||
for (int i = start - 1; i >= 0; i--)
|
||
{
|
||
ret = i;
|
||
var s = history[i];
|
||
if (s.costGC > LuaDeepProfilerSetting.Instance.captureGC)
|
||
{
|
||
break;
|
||
}
|
||
else if (s.costTime >= 1 / 30.0f)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
return Mathf.Max(Mathf.Min(ret, history.Count - 1), 0);
|
||
}
|
||
|
||
private void LoadRootSample(Sample sample, bool needRecord)
|
||
{
|
||
if (needRecord)
|
||
{
|
||
//迟钝了
|
||
if (sample.costTime >= 1 / 30.0f)
|
||
{
|
||
sample.captureUrl = Sample.Capture();
|
||
}
|
||
else if (sample.costGC > LuaDeepProfilerSetting.Instance.captureGC)
|
||
{
|
||
sample.captureUrl = Sample.Capture();
|
||
}
|
||
else
|
||
{
|
||
sample.captureUrl = null;
|
||
}
|
||
}
|
||
LuaProfilerTreeViewItem item;
|
||
string f = sample.fullName;
|
||
if (m_nodeDict.TryGetValue(f, out item))
|
||
{
|
||
item.AddSample(sample);
|
||
}
|
||
else
|
||
{
|
||
item = LuaProfilerTreeViewItem.Create(sample, 0, null);
|
||
roots.Add(item);
|
||
needRebuild = true;
|
||
}
|
||
if (needRecord)
|
||
{
|
||
history.Add(sample.Clone());
|
||
}
|
||
}
|
||
|
||
private void ReLoadTreeItems()
|
||
{
|
||
treeViewItems.Clear();
|
||
List<LuaProfilerTreeViewItem> rootList = new List<LuaProfilerTreeViewItem>(roots);
|
||
int sortIndex = multiColumnHeader.sortedColumnIndex;
|
||
int sign = 0;
|
||
if (sortIndex > 0)
|
||
{
|
||
sign = multiColumnHeader.IsSortedAscending(sortIndex) ? -1 : 1;
|
||
}
|
||
switch (sortIndex)
|
||
{
|
||
case 1: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalMemory - b.totalMemory); }); break;
|
||
case 2: rootList.Sort((a, b) => { return sign * Math.Sign(a.currentTime - b.currentTime); }); break;
|
||
case 3: rootList.Sort((a, b) => { return sign * Math.Sign(a.averageTime - b.averageTime); }); break;
|
||
case 4: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalTime - b.totalTime); }); break;
|
||
case 5: rootList.Sort((a, b) => { return sign * Math.Sign(a.showGC - b.showGC); }); break;
|
||
case 6: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalCallTime - b.totalCallTime); }); break;
|
||
case 7: rootList.Sort((a, b) => { return sign * Math.Sign(a.frameCalls - b.frameCalls); }); break;
|
||
}
|
||
foreach (var item in rootList)
|
||
{
|
||
AddOneNode(item, false);
|
||
SortChildren(sortIndex, sign, item);
|
||
}
|
||
}
|
||
|
||
private void SortChildren(int sortIndex, int sign, LuaProfilerTreeViewItem item)
|
||
{
|
||
if (item.childs != null && item.childs.Count > 0)
|
||
{
|
||
List<LuaProfilerTreeViewItem> rootList = item.childs;
|
||
switch (sortIndex)
|
||
{
|
||
case 1: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalMemory - b.totalMemory); }); break;
|
||
case 2: rootList.Sort((a, b) => { return sign * Math.Sign(a.currentTime - b.currentTime); }); break;
|
||
case 3: rootList.Sort((a, b) => { return sign * Math.Sign(a.averageTime - b.averageTime); }); break;
|
||
case 4: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalTime - b.totalTime); }); break;
|
||
case 5: rootList.Sort((a, b) => { return sign * Math.Sign(a.showGC - b.showGC); }); break;
|
||
case 6: rootList.Sort((a, b) => { return sign * Math.Sign(a.totalCallTime - b.totalCallTime); }); break;
|
||
case 7: rootList.Sort((a, b) => { return sign * Math.Sign(a.frameCalls - b.frameCalls); }); break;
|
||
}
|
||
foreach (var t in rootList)
|
||
{
|
||
SortChildren(sortIndex, sign, t);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void AddOneNode(LuaProfilerTreeViewItem root, bool r)
|
||
{
|
||
treeViewItems.Add(root);
|
||
m_nodeDict[root.fullName] = root;
|
||
|
||
if (root.children != null)
|
||
{
|
||
root.children.Clear();
|
||
}
|
||
if (r) return;
|
||
foreach (var item in root.childs)
|
||
{
|
||
AddOneNode(item, true);
|
||
if (m_showRootItemId != root.rootFather.id && !m_seaching)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
bool setting = false;
|
||
protected override TreeViewItem BuildRoot()
|
||
{
|
||
while (samplesQueue.Count > 0)
|
||
{
|
||
Sample s = samplesQueue.Dequeue();
|
||
LoadRootSample(s, LuaDeepProfilerSetting.Instance.isRecord);
|
||
s.Restore();
|
||
}
|
||
|
||
if (!needRebuild)
|
||
{
|
||
return root;
|
||
}
|
||
ReLoadTreeItems();
|
||
// Utility method that initializes the TreeViewItem.children and -parent for all items.
|
||
SetupParentsAndChildrenFromDepths(root, treeViewItems);
|
||
|
||
setting = true;
|
||
foreach (var t in roots)
|
||
{
|
||
SetExpanded(t.id, t.id == m_showRootItemId);
|
||
}
|
||
setting = false;
|
||
needRebuild = false;
|
||
// Return root of the tree
|
||
return root;
|
||
}
|
||
|
||
protected override void RowGUI(RowGUIArgs args)
|
||
{
|
||
var item = (LuaProfilerTreeViewItem)args.item;
|
||
if (gs == null)
|
||
{
|
||
gs = new GUIStyle(GUI.skin.label);
|
||
gs.alignment = TextAnchor.MiddleCenter;
|
||
}
|
||
Rect r = args.GetCellRect(0);
|
||
args.rowRect = r;
|
||
base.RowGUI(args);
|
||
|
||
r = args.GetCellRect(1);
|
||
|
||
GUI.Label(r, LuaProfiler.GetMemoryString(item.totalMemory), gs);
|
||
|
||
r = args.GetCellRect(2);
|
||
GUI.Label(r, item.currentTime.ToString("f6") + "s", gs);
|
||
|
||
r = args.GetCellRect(3);
|
||
GUI.Label(r, ((float)item.averageTime / 1000000).ToString("f6") + "s", gs);
|
||
|
||
r = args.GetCellRect(4);
|
||
GUI.Label(r, ((float)item.totalTime / 1000000).ToString("f6") + "s", gs);
|
||
|
||
r = args.GetCellRect(5);
|
||
GUI.Label(r, LuaProfiler.GetMemoryString(item.showGC), gs);
|
||
|
||
r = args.GetCellRect(6);
|
||
GUI.Label(r, LuaProfiler.GetMemoryString(item.totalCallTime, ""), gs);
|
||
|
||
r = args.GetCellRect(7);
|
||
GUI.Label(r, item.frameCalls.ToString(), gs);
|
||
|
||
}
|
||
|
||
}
|
||
#endif
|
||
}
|