using System; using System.Collections.Generic; using System.Linq; using System.Text; using MemoryProfilerWindow; using UnityEditor.MemoryProfiler; using UnityEngine; namespace MemoryProfilerWindow { class ShortestPathToRootFinder { private readonly CrawledMemorySnapshot _snapshot; public ShortestPathToRootFinder(CrawledMemorySnapshot snapshot) { _snapshot = snapshot; } public ThingInMemory[] FindFor(ThingInMemory thing) { var seen = new HashSet(); var queue = new Queue>(); queue.Enqueue(new List { thing}); while (queue.Any()) { var pop = queue.Dequeue(); var tip = pop.Last(); string reason; if (IsRoot(tip, out reason)) return pop.ToArray(); foreach (var next in tip.referencedBy) { if (seen.Contains(next)) continue; seen.Add(next); var dupe = new List(pop) {next}; queue.Enqueue(dupe); } } return null; } public bool IsRoot(ThingInMemory thing, out string reason) { reason = null; if (thing is StaticFields) { reason = "Static fields are global variables. Anything they reference will not be unloaded."; return true; } if (thing is ManagedObject) return false; if (thing is GCHandle) return false; var nativeObject = thing as NativeUnityEngineObject; if (nativeObject == null) throw new ArgumentException("Unknown type: " + thing.GetType()); if (nativeObject.isManager) { reason = "this is an internal unity'manager' style object, which is a global object that will never be unloaded"; return true; } if (nativeObject.isDontDestroyOnLoad) { reason = "DontDestroyOnLoad() was called on this object, so it will never be unloaded"; return true; } if ((nativeObject.hideFlags & HideFlags.DontUnloadUnusedAsset) != 0) { reason = "the DontUnloadUnusedAsset hideflag is set on this object. Unity's builtin resources set this flag. Users can also set the flag themselves"; return true; } if (nativeObject.isPersistent) return false; if (IsComponent(nativeObject.classID)) { reason = "this is a component, living on a gameobject, that is either part of the loaded scene, or was generated by script. It will be unloaded on next scene load."; return true; } if (IsGameObject(nativeObject.classID)) { reason = "this is a gameobject, that is either part of the loaded scene, or was generated by script. It will be unloaded on next scene load if nobody is referencing it"; return true; } if (IsAssetBundle(nativeObject.classID)) { reason = "this object is an assetbundle, which is never unloaded automatically, but only through an explicit .Unload() call."; return true; } reason = "This object is a root, but the memory profiler UI does not yet understand why"; return true; } private bool IsGameObject(int classID) { return _snapshot.nativeTypes[classID].name == "GameObject"; } private bool IsAssetBundle(int classID) { return _snapshot.nativeTypes[classID].name == "AssetBundle"; } private bool IsComponent(int classID) { var packedNativeType = _snapshot.nativeTypes[classID]; if (packedNativeType.name == "Component") return true; var baseClassID = packedNativeType.nativeBaseTypeArrayIndex; return baseClassID != -1 && IsComponent(baseClassID); } } }