/* * ============================================================================== * 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 objectPool = new ObjectPool(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 childs = new List(); 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 roots = new List(); #endregion #region field private int m_showRootItemId = 0; public bool m_seaching = false; private List m_expandIds = new List(); private readonly LuaProfilerTreeViewItem root; private readonly List treeViewItems = new List(); public readonly List history = new List(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(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(); m_expandIds.Add(m_showRootItemId); needRebuild = true; } public static Dictionary m_nodeDict = new Dictionary(); 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 samplesQueue = new Queue(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 rootList = new List(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 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 }