using System;
|
|
using Assets.Editor.Treemap;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEditor.MemoryProfiler;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
|
|
namespace MemoryProfilerWindow
|
|
{
|
|
public class Inspector
|
|
{
|
|
ThingInMemory _selectedThing;
|
|
private ThingInMemory[] _shortestPath;
|
|
private ShortestPathToRootFinder _shortestPathToRootFinder;
|
|
private static int s_InspectorWidth = 400;
|
|
Vector2 _scrollPosition;
|
|
MemoryProfilerWindow _hostWindow;
|
|
CrawledMemorySnapshot _unpackedCrawl;
|
|
PrimitiveValueReader _primitiveValueReader;
|
|
Dictionary<ulong, ThingInMemory> objectCache = new Dictionary<ulong, ThingInMemory>();
|
|
private Texture2D _textureObject;
|
|
private int _prevInstance;
|
|
private float _textureSize = 128.0f;
|
|
|
|
|
|
static class Styles
|
|
{
|
|
public static GUIStyle entryEven = "OL EntryBackEven";
|
|
public static GUIStyle entryOdd = "OL EntryBackOdd";
|
|
}
|
|
|
|
GUILayoutOption labelWidth = GUILayout.Width(150);
|
|
|
|
public Inspector(MemoryProfilerWindow hostWindow, CrawledMemorySnapshot unpackedCrawl, PackedMemorySnapshot snapshot)
|
|
{
|
|
_unpackedCrawl = unpackedCrawl;
|
|
_hostWindow = hostWindow;
|
|
_shortestPathToRootFinder = new ShortestPathToRootFinder(unpackedCrawl);
|
|
_primitiveValueReader = new PrimitiveValueReader(_unpackedCrawl.virtualMachineInformation, _unpackedCrawl.managedHeap);
|
|
}
|
|
|
|
public float width
|
|
{
|
|
get { return s_InspectorWidth; }
|
|
}
|
|
|
|
public void SelectThing(ThingInMemory thing)
|
|
{
|
|
_selectedThing = thing;
|
|
_shortestPath = _shortestPathToRootFinder.FindFor(thing);
|
|
}
|
|
|
|
public void Draw()
|
|
{
|
|
GUILayout.BeginArea(new Rect(_hostWindow.position.width - s_InspectorWidth, 25, s_InspectorWidth, _hostWindow.position.height - 25f));
|
|
_scrollPosition = GUILayout.BeginScrollView(_scrollPosition);
|
|
|
|
if (_selectedThing == null)
|
|
GUILayout.Label("Select an object to see more info");
|
|
else
|
|
{
|
|
var nativeObject = _selectedThing as NativeUnityEngineObject;
|
|
if (nativeObject != null)
|
|
{
|
|
GUILayout.Label("NativeUnityEngineObject", EditorStyles.boldLabel);
|
|
GUILayout.Space(5);
|
|
EditorGUILayout.LabelField("Name", nativeObject.name);
|
|
EditorGUILayout.LabelField("ClassName", nativeObject.className);
|
|
EditorGUILayout.LabelField("ClassID", nativeObject.classID.ToString());
|
|
EditorGUILayout.LabelField("instanceID", nativeObject.instanceID.ToString());
|
|
EditorGUILayout.LabelField("isDontDestroyOnLoad", nativeObject.isDontDestroyOnLoad.ToString());
|
|
EditorGUILayout.LabelField("isPersistent", nativeObject.isPersistent.ToString());
|
|
EditorGUILayout.LabelField("isManager", nativeObject.isManager.ToString());
|
|
EditorGUILayout.LabelField("hideFlags", nativeObject.hideFlags.ToString());
|
|
EditorGUILayout.LabelField("hideFlags", nativeObject.size.ToString());
|
|
DrawSpecificTexture2D(nativeObject);
|
|
}
|
|
|
|
var managedObject = _selectedThing as ManagedObject;
|
|
if (managedObject != null)
|
|
{
|
|
GUILayout.Label("ManagedObject");
|
|
EditorGUILayout.LabelField("Type", managedObject.typeDescription.name);
|
|
EditorGUILayout.LabelField("Address", managedObject.address.ToString("X"));
|
|
EditorGUILayout.LabelField("size", managedObject.size.ToString());
|
|
|
|
if (managedObject.typeDescription.name == "System.String")
|
|
EditorGUILayout.LabelField("value", StringTools.ReadString(_unpackedCrawl.managedHeap.Find(managedObject.address, _unpackedCrawl.virtualMachineInformation), _unpackedCrawl.virtualMachineInformation));
|
|
DrawFields(managedObject);
|
|
|
|
if (managedObject.typeDescription.isArray)
|
|
{
|
|
DrawArray(managedObject);
|
|
}
|
|
}
|
|
|
|
if (_selectedThing is GCHandle)
|
|
{
|
|
GUILayout.Label("GCHandle");
|
|
EditorGUILayout.LabelField("size", _selectedThing.size.ToString());
|
|
}
|
|
|
|
var staticFields = _selectedThing as StaticFields;
|
|
if (staticFields != null)
|
|
{
|
|
GUILayout.Label("Static Fields");
|
|
GUILayout.Label("Of type: " + staticFields.typeDescription.name);
|
|
GUILayout.Label("size: " + staticFields.size);
|
|
|
|
DrawFields(staticFields.typeDescription, new BytesAndOffset() { bytes = staticFields.typeDescription.staticFieldBytes, offset = 0, pointerSize = _unpackedCrawl.virtualMachineInformation.pointerSize}, true);
|
|
}
|
|
|
|
if (managedObject == null)
|
|
{
|
|
GUILayout.Space(10);
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("References:", labelWidth);
|
|
GUILayout.BeginVertical();
|
|
DrawLinks(_selectedThing.references);
|
|
GUILayout.EndVertical();
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
GUILayout.Space(10);
|
|
GUILayout.Label("Referenced by:");
|
|
DrawLinks(_selectedThing.referencedBy);
|
|
|
|
GUILayout.Space(10);
|
|
if (_shortestPath != null)
|
|
{
|
|
if (_shortestPath.Length > 1)
|
|
{
|
|
GUILayout.Label("ShortestPathToRoot");
|
|
DrawLinks(_shortestPath);
|
|
}
|
|
string reason;
|
|
_shortestPathToRootFinder.IsRoot(_shortestPath.Last(), out reason);
|
|
GUILayout.Label("This is a root because:");
|
|
GUILayout.TextArea(reason);
|
|
}
|
|
else
|
|
{
|
|
GUILayout.TextArea("No root is keeping this object alive. It will be collected next UnloadUnusedAssets() or scene load");
|
|
}
|
|
}
|
|
GUILayout.EndScrollView();
|
|
GUILayout.EndArea();
|
|
}
|
|
|
|
private void DrawSpecificTexture2D(NativeUnityEngineObject nativeObject)
|
|
{
|
|
if (nativeObject.className != "Texture2D")
|
|
{
|
|
_textureObject = null;
|
|
return;
|
|
}
|
|
EditorGUILayout.HelpBox("Watching Texture Detail Data is only for Editor.", MessageType.Warning, true);
|
|
if (_prevInstance != nativeObject.instanceID)
|
|
{
|
|
_textureObject = EditorUtility.InstanceIDToObject(nativeObject.instanceID) as Texture2D;
|
|
_prevInstance = nativeObject.instanceID;
|
|
}
|
|
if (_textureObject != null)
|
|
{
|
|
EditorGUILayout.LabelField("textureInfo: " + _textureObject.width + "x" + _textureObject.height + " " + _textureObject.format);
|
|
EditorGUILayout.ObjectField(_textureObject, typeof(Texture2D));
|
|
_textureSize = EditorGUILayout.Slider(_textureSize, 100.0f, 1024.0f);
|
|
GUILayout.Label(_textureObject, GUILayout.Width(_textureSize), GUILayout.Height(_textureSize * _textureObject.height / _textureObject.width));
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.LabelField("Can't instance texture,maybe it was already released.");
|
|
}
|
|
}
|
|
|
|
private void DrawArray(ManagedObject managedObject)
|
|
{
|
|
var typeDescription = managedObject.typeDescription;
|
|
int elementCount = ArrayTools.ReadArrayLength(_unpackedCrawl.managedHeap, managedObject.address, typeDescription, _unpackedCrawl.virtualMachineInformation);
|
|
GUILayout.Label("element count: " + elementCount);
|
|
int rank = typeDescription.arrayRank;
|
|
GUILayout.Label("arrayRank: " + rank);
|
|
if (_unpackedCrawl.typeDescriptions[typeDescription.baseOrElementTypeIndex].isValueType)
|
|
{
|
|
GUILayout.Label("Cannot yet display elements of value type arrays");
|
|
return;
|
|
}
|
|
if (rank != 1)
|
|
{
|
|
GUILayout.Label("Cannot display non rank=1 arrays yet.");
|
|
return;
|
|
}
|
|
|
|
var pointers = new List<UInt64>();
|
|
for (int i = 0; i != elementCount; i++)
|
|
{
|
|
pointers.Add(_primitiveValueReader.ReadPointer(managedObject.address + (UInt64)_unpackedCrawl.virtualMachineInformation.arrayHeaderSize + (UInt64)(i * _unpackedCrawl.virtualMachineInformation.pointerSize)));
|
|
}
|
|
GUILayout.Label("elements:");
|
|
DrawLinks(pointers);
|
|
}
|
|
|
|
private void DrawFields(TypeDescription typeDescription, BytesAndOffset bytesAndOffset, bool useStatics = false)
|
|
{
|
|
int counter = 0;
|
|
|
|
foreach (var field in TypeTools.AllFieldsOf(typeDescription, _unpackedCrawl.typeDescriptions, useStatics ? TypeTools.FieldFindOptions.OnlyStatic : TypeTools.FieldFindOptions.OnlyInstance))
|
|
{
|
|
counter++;
|
|
var gUIStyle = counter % 2 == 0 ? Styles.entryEven : Styles.entryOdd;
|
|
gUIStyle.margin = new RectOffset(0, 0, 0, 0);
|
|
gUIStyle.overflow = new RectOffset(0, 0, 0, 0);
|
|
gUIStyle.padding = EditorStyles.label.padding;
|
|
GUILayout.BeginHorizontal(gUIStyle);
|
|
GUILayout.Label(field.name, labelWidth);
|
|
GUILayout.BeginVertical();
|
|
DrawValueFor(field, bytesAndOffset.Add(field.offset));
|
|
GUILayout.EndVertical();
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
}
|
|
|
|
private void DrawFields(ManagedObject managedObject)
|
|
{
|
|
if (managedObject.typeDescription.isArray)
|
|
return;
|
|
GUILayout.Space(10);
|
|
GUILayout.Label("Fields:");
|
|
DrawFields(managedObject.typeDescription, _unpackedCrawl.managedHeap.Find(managedObject.address, _unpackedCrawl.virtualMachineInformation));
|
|
}
|
|
|
|
private void DrawValueFor(FieldDescription field, BytesAndOffset bytesAndOffset)
|
|
{
|
|
var typeDescription = _unpackedCrawl.typeDescriptions[field.typeIndex];
|
|
|
|
switch (typeDescription.name)
|
|
{
|
|
case "System.Int32":
|
|
GUILayout.Label(_primitiveValueReader.ReadInt32(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.Int64":
|
|
GUILayout.Label(_primitiveValueReader.ReadInt64(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.UInt32":
|
|
GUILayout.Label(_primitiveValueReader.ReadUInt32(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.UInt64":
|
|
GUILayout.Label(_primitiveValueReader.ReadUInt64(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.Int16":
|
|
GUILayout.Label(_primitiveValueReader.ReadInt16(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.UInt16":
|
|
GUILayout.Label(_primitiveValueReader.ReadUInt16(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.Byte":
|
|
GUILayout.Label(_primitiveValueReader.ReadByte(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.SByte":
|
|
GUILayout.Label(_primitiveValueReader.ReadSByte(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.Char":
|
|
GUILayout.Label(_primitiveValueReader.ReadChar(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.Boolean":
|
|
GUILayout.Label(_primitiveValueReader.ReadBool(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.Single":
|
|
GUILayout.Label(_primitiveValueReader.ReadSingle(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.Double":
|
|
GUILayout.Label(_primitiveValueReader.ReadDouble(bytesAndOffset).ToString());
|
|
break;
|
|
case "System.IntPtr":
|
|
GUILayout.Label(_primitiveValueReader.ReadPointer(bytesAndOffset).ToString("X"));
|
|
break;
|
|
default:
|
|
|
|
if (!typeDescription.isValueType)
|
|
{
|
|
ThingInMemory item = GetThingAt(bytesAndOffset.ReadPointer());
|
|
if (item == null)
|
|
{
|
|
EditorGUI.BeginDisabledGroup(true);
|
|
GUILayout.Button("Null");
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
else
|
|
{
|
|
DrawLinks(new ThingInMemory[] { item });
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DrawFields(typeDescription, bytesAndOffset);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private ThingInMemory GetThingAt(ulong address)
|
|
{
|
|
if (!objectCache.ContainsKey(address))
|
|
{
|
|
objectCache[address] = _unpackedCrawl.allObjects.OfType<ManagedObject>().FirstOrDefault(mo => mo.address == address);
|
|
}
|
|
|
|
return objectCache[address];
|
|
}
|
|
|
|
/*
|
|
private Item FindItemPointedToByManagedFieldAt(BytesAndOffset bytesAndOffset)
|
|
{
|
|
var stringAddress = _primitiveValueReader.ReadPointer(bytesAndOffset);
|
|
return
|
|
_items.FirstOrDefault(i =>
|
|
{
|
|
var m = i._thingInMemory as ManagedObject;
|
|
if (m != null)
|
|
{
|
|
return m.address == stringAddress;
|
|
}
|
|
return false;
|
|
});
|
|
}*/
|
|
|
|
private void DrawLinks(IEnumerable<UInt64> pointers)
|
|
{
|
|
DrawLinks(pointers.Select(p => GetThingAt(p)));
|
|
}
|
|
|
|
private void DrawLinks(IEnumerable<ThingInMemory> thingInMemories)
|
|
{
|
|
var c = GUI.backgroundColor;
|
|
GUI.skin.button.alignment = TextAnchor.UpperLeft;
|
|
foreach (var rb in thingInMemories)
|
|
{
|
|
EditorGUI.BeginDisabledGroup(rb == _selectedThing || rb == null);
|
|
|
|
GUI.backgroundColor = ColorFor(rb);
|
|
|
|
var caption = rb == null ? "null" : rb.caption;
|
|
|
|
var managedObject = rb as ManagedObject;
|
|
if (managedObject != null && managedObject.typeDescription.name == "System.String")
|
|
caption = StringTools.ReadString(_unpackedCrawl.managedHeap.Find(managedObject.address, _unpackedCrawl.virtualMachineInformation), _unpackedCrawl.virtualMachineInformation);
|
|
|
|
if (GUILayout.Button(caption))
|
|
_hostWindow.SelectThing(rb);
|
|
EditorGUI.EndDisabledGroup();
|
|
}
|
|
GUI.backgroundColor = c;
|
|
}
|
|
|
|
private Color ColorFor(ThingInMemory rb)
|
|
{
|
|
if (rb == null)
|
|
return Color.gray;
|
|
if (rb is NativeUnityEngineObject)
|
|
return Color.red;
|
|
if (rb is ManagedObject)
|
|
return Color.Lerp(Color.blue, Color.white, 0.5f);
|
|
if (rb is GCHandle)
|
|
return Color.magenta;
|
|
if (rb is StaticFields)
|
|
return Color.yellow;
|
|
|
|
throw new ArgumentException("Unexpected type: " + rb.GetType());
|
|
}
|
|
}
|
|
}
|