BacklinkStore.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using UnityEditor;
  6. using UnityEditorInternal;
  7. using Debug = UnityEngine.Debug;
  8. using Object = UnityEngine.Object;
  9. namespace Asset_Cleaner {
  10. class BacklinkStore {
  11. public bool Initialized { get; private set; }
  12. public Dictionary<string, long> UnusedFiles { get; private set; }
  13. public Dictionary<string, long> UnusedScenes { get; private set; }
  14. public Dictionary<string, BwMeta> Backward { get; private set; }
  15. public Dictionary<string, UnusedQty> FoldersWithQty { get; private set; }
  16. Dictionary<string, FwMeta> _forward;
  17. List<string> Folders { get; set; }
  18. public void Init() {
  19. FoldersWithQty = new Dictionary<string, UnusedQty>();
  20. _forward = new Dictionary<string, FwMeta>();
  21. Backward = new Dictionary<string, BwMeta>();
  22. var defaultAss = typeof(DefaultAsset);
  23. var asmdefAss = typeof(AssemblyDefinitionAsset);
  24. var paths = AssetDatabase.GetAllAssetPaths()
  25. .Distinct()
  26. .Where(s => s.StartsWith("Assets") || s.StartsWith("ProjectSettings"))
  27. .Where(p => {
  28. var t = AssetDatabase.GetMainAssetTypeAtPath(p);
  29. return !t.IsAssignableFromInverse(defaultAss) && !t.IsAssignableFromInverse(asmdefAss);
  30. })
  31. .ToArray();
  32. var i = 0f;
  33. var total = (float) paths.Length;
  34. foreach (var path in paths) {
  35. _FillFwAndBacklinks(path);
  36. var percent = i * 100f / total;
  37. if (Math.Abs(percent % 5f) < 0.01f) {
  38. if (EditorUtility.DisplayCancelableProgressBar(
  39. "Please wait...",
  40. "Building the cache...", percent))
  41. Debug.LogError("Cache build aborted");
  42. }
  43. i++;
  44. }
  45. EditorUtility.ClearProgressBar();
  46. // FillFoldersWithQtyByPaths
  47. List<string> foldersAll = new List<string>();
  48. foreach (var path in paths) {
  49. var folders = GetAllFoldersFromPath(path);
  50. foldersAll.AddRange(folders);
  51. }
  52. Folders = foldersAll.Distinct().OrderBy(p => p).ToList();
  53. UpdateUnusedAssets();
  54. Initialized = true;
  55. }
  56. void _FillFwAndBacklinks(string path) {
  57. var dependencies = _Dependencies(path);
  58. var hs = new FwMeta {Dependencies = new HashSet<string>(dependencies)};
  59. _forward.Add(path, hs);
  60. foreach (var backPath in dependencies) {
  61. if (!Backward.TryGetValue(backPath, out var val)) {
  62. val = new BwMeta();
  63. val.Lookup = new HashSet<string>();
  64. Backward.Add(backPath, val);
  65. }
  66. val.Lookup.Add(path);
  67. }
  68. }
  69. void UpdateFoldersWithQtyByPath(string path) {
  70. var folders = GetAllFoldersFromPath(path);
  71. foreach (var folder in folders) {
  72. if (!Folders.Exists(p => p == folder))
  73. Folders.Add(folder);
  74. }
  75. }
  76. static List<string> GetAllFoldersFromPath(string p) {
  77. var result = new List<string>();
  78. var i = p.IndexOf('/', 0);
  79. while (i > 0) {
  80. var item = p.Substring(0, i);
  81. result.Add(item);
  82. i = p.IndexOf('/', i + 1);
  83. }
  84. return result.Distinct().ToList();
  85. }
  86. public void UpdateUnusedAssets() {
  87. var all = new HashSet<string>(_forward.Keys);
  88. var withBacklinks =
  89. new HashSet<string>(Backward.Where(kv => kv.Value.Lookup.Count > 0).Select(kv => kv.Key));
  90. all.ExceptWith(withBacklinks);
  91. all.RemoveWhere(SearchUtils.IsFileIgrnoredBySettings);
  92. var unusedAssets = all;
  93. var scenes = unusedAssets.Where(s =>
  94. AssetDatabase.GetMainAssetTypeAtPath(s).IsAssignableFromInverse(typeof(SceneAsset))).ToArray();
  95. unusedAssets.ExceptWith(scenes);
  96. var files = unusedAssets;
  97. UnusedFiles = new Dictionary<string, long>();
  98. foreach (var file in files) UnusedFiles[file] = CommonUtils.Size(file);
  99. UnusedScenes = new Dictionary<string, long>();
  100. foreach (var scene in scenes) UnusedScenes[scene] = CommonUtils.Size(scene);
  101. // UpdateFoldersWithQty();
  102. foreach (var folder in Folders) {
  103. var unusedFilesQty = UnusedFiles.Count(p => p.Key.StartsWith(folder));
  104. var unusedScenesQty = UnusedScenes.Count(p => p.Key.StartsWith(folder));
  105. long size = 0;
  106. size = UnusedFiles.Where((p => p.Key.StartsWith(folder))).Sum(p => p.Value);
  107. size += UnusedScenes.Where(p => p.Key.StartsWith(folder)).Sum(p => p.Value);
  108. FoldersWithQty.TryGetValue(folder, out var folderWithQty);
  109. if (folderWithQty == null) {
  110. FoldersWithQty.Add(folder, new UnusedQty(unusedFilesQty, unusedScenesQty, size));
  111. }
  112. else {
  113. folderWithQty.UnusedFilesQty = unusedFilesQty;
  114. folderWithQty.UnusedScenesQty = unusedScenesQty;
  115. folderWithQty.UnusedSize = size;
  116. }
  117. }
  118. }
  119. public void Remove(string path) {
  120. if (!_forward.TryGetValue(path, out var fwMeta))
  121. return;
  122. foreach (var dependency in fwMeta.Dependencies) {
  123. if (!Backward.TryGetValue(dependency, out var dep)) continue;
  124. dep.Lookup.Remove(path);
  125. }
  126. _forward.Remove(path);
  127. UpdateFoldersWithQtyByPath(path);
  128. }
  129. public void Replace(string src, string dest) {
  130. _Upd(_forward);
  131. _Upd(Backward);
  132. UpdateFoldersWithQtyByPath(dest);
  133. void _Upd<T>(Dictionary<string, T> dic) {
  134. if (!dic.TryGetValue(src, out var refs)) return;
  135. dic.Remove(src);
  136. dic.Add(dest, refs);
  137. }
  138. }
  139. public void RebuildFor(string path, bool remove) {
  140. if (!_forward.TryGetValue(path, out var fwMeta)) {
  141. fwMeta = new FwMeta();
  142. _forward.Add(path, fwMeta);
  143. }
  144. else if (remove) {
  145. foreach (var dependency in fwMeta.Dependencies) {
  146. if (!Backward.TryGetValue(dependency, out var backDep)) continue;
  147. backDep.Lookup.Remove(path);
  148. }
  149. fwMeta.Dependencies = null;
  150. }
  151. var dependencies = _Dependencies(path);
  152. fwMeta.Dependencies = new HashSet<string>(dependencies);
  153. foreach (var backPath in dependencies) {
  154. if (!Backward.TryGetValue(backPath, out var bwMeta)) {
  155. bwMeta = new BwMeta {Lookup = new HashSet<string>()};
  156. Backward.Add(backPath, bwMeta);
  157. }
  158. else if (remove)
  159. bwMeta.Lookup.Remove(path);
  160. bwMeta.Lookup.Add(path);
  161. }
  162. if (!remove) {
  163. UpdateFoldersWithQtyByPath(path);
  164. }
  165. }
  166. static string[] _Dependencies(string s) {
  167. if (s[0] == 'A')
  168. return AssetDatabase.GetDependencies(s, false);
  169. var obj = LoadAllOrMain(s)[0];
  170. return GetDependenciesManualPaths().ToArray();
  171. Object[] LoadAllOrMain(string assetPath) {
  172. // prevents error "Do not use readobjectthreaded on scene objects!"
  173. return typeof(SceneAsset) == AssetDatabase.GetMainAssetTypeAtPath(assetPath)
  174. ? new[] {AssetDatabase.LoadMainAssetAtPath(assetPath)}
  175. : AssetDatabase.LoadAllAssetsAtPath(assetPath);
  176. }
  177. IEnumerable<string> GetDependenciesManualPaths() {
  178. if (obj is EditorBuildSettings) {
  179. foreach (var scene in EditorBuildSettings.scenes)
  180. yield return scene.path;
  181. }
  182. using (var so = new SerializedObject(obj)) {
  183. var props = so.GetIterator();
  184. while (props.Next(true)) {
  185. switch (props.propertyType) {
  186. case SerializedPropertyType.ObjectReference:
  187. var propsObjectReferenceValue = props.objectReferenceValue;
  188. if (!propsObjectReferenceValue) continue;
  189. var assetPath = AssetDatabase.GetAssetPath(propsObjectReferenceValue);
  190. yield return assetPath;
  191. break;
  192. #if later
  193. case SerializedPropertyType.Generic:
  194. case SerializedPropertyType.ExposedReference:
  195. case SerializedPropertyType.ManagedReference:
  196. break;
  197. #endif
  198. default:
  199. continue;
  200. }
  201. }
  202. }
  203. }
  204. }
  205. class FwMeta {
  206. public HashSet<string> Dependencies;
  207. }
  208. public class BwMeta {
  209. public HashSet<string> Lookup;
  210. }
  211. public class UnusedQty {
  212. public int UnusedFilesQty;
  213. public int UnusedScenesQty;
  214. public long UnusedSize;
  215. public UnusedQty() {
  216. Init(0, 0, 0);
  217. }
  218. public UnusedQty(int unusedFilesQty, int unusedScenesQty, long unusedSize) {
  219. Init(unusedFilesQty, unusedScenesQty, unusedSize);
  220. }
  221. private void Init(int unusedFilesQty, int unusedScenesQty, long unusedSize) {
  222. UnusedFilesQty = unusedFilesQty;
  223. UnusedScenesQty = unusedScenesQty;
  224. UnusedSize = unusedSize;
  225. }
  226. }
  227. }
  228. }