2021-02-23 02:13:14 +02:00

286 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
namespace Asset_Cleaner {
class BacklinkStore {
public bool Initialized { get; private set; }
public Dictionary<string, long> UnusedFiles { get; private set; }
public Dictionary<string, long> UnusedScenes { get; private set; }
public Dictionary<string, BwMeta> Backward { get; private set; }
public Dictionary<string, UnusedQty> FoldersWithQty { get; private set; }
Dictionary<string, FwMeta> _forward;
List<string> Folders { get; set; }
public void Init() {
FoldersWithQty = new Dictionary<string, UnusedQty>();
_forward = new Dictionary<string, FwMeta>();
Backward = new Dictionary<string, BwMeta>();
var defaultAss = typeof(DefaultAsset);
var asmdefAss = typeof(AssemblyDefinitionAsset);
var paths = AssetDatabase.GetAllAssetPaths()
.Distinct()
.Where(s => s.StartsWith("Assets") || s.StartsWith("ProjectSettings"))
.Where(p => {
var t = AssetDatabase.GetMainAssetTypeAtPath(p);
return !t.IsAssignableFromInverse(defaultAss) && !t.IsAssignableFromInverse(asmdefAss);
})
.ToArray();
var i = 0f;
var total = (float) paths.Length;
foreach (var path in paths) {
_FillFwAndBacklinks(path);
var percent = i * 100f / total;
if (Math.Abs(percent % 5f) < 0.01f) {
if (EditorUtility.DisplayCancelableProgressBar(
"Please wait...",
"Building the cache...", percent))
Debug.LogError("Cache build aborted");
}
i++;
}
EditorUtility.ClearProgressBar();
// FillFoldersWithQtyByPaths
List<string> foldersAll = new List<string>();
foreach (var path in paths) {
var folders = GetAllFoldersFromPath(path);
foldersAll.AddRange(folders);
}
Folders = foldersAll.Distinct().OrderBy(p => p).ToList();
UpdateUnusedAssets();
Initialized = true;
}
void _FillFwAndBacklinks(string path) {
var dependencies = _Dependencies(path);
var hs = new FwMeta {Dependencies = new HashSet<string>(dependencies)};
_forward.Add(path, hs);
foreach (var backPath in dependencies) {
if (!Backward.TryGetValue(backPath, out var val)) {
val = new BwMeta();
val.Lookup = new HashSet<string>();
Backward.Add(backPath, val);
}
val.Lookup.Add(path);
}
}
void UpdateFoldersWithQtyByPath(string path) {
var folders = GetAllFoldersFromPath(path);
foreach (var folder in folders) {
if (!Folders.Exists(p => p == folder))
Folders.Add(folder);
}
}
static List<string> GetAllFoldersFromPath(string p) {
var result = new List<string>();
var i = p.IndexOf('/', 0);
while (i > 0) {
var item = p.Substring(0, i);
result.Add(item);
i = p.IndexOf('/', i + 1);
}
return result.Distinct().ToList();
}
public void UpdateUnusedAssets() {
var all = new HashSet<string>(_forward.Keys);
var withBacklinks =
new HashSet<string>(Backward.Where(kv => kv.Value.Lookup.Count > 0).Select(kv => kv.Key));
all.ExceptWith(withBacklinks);
all.RemoveWhere(SearchUtils.IsFileIgrnoredBySettings);
var unusedAssets = all;
var scenes = unusedAssets.Where(s =>
AssetDatabase.GetMainAssetTypeAtPath(s).IsAssignableFromInverse(typeof(SceneAsset))).ToArray();
unusedAssets.ExceptWith(scenes);
var files = unusedAssets;
UnusedFiles = new Dictionary<string, long>();
foreach (var file in files) UnusedFiles[file] = CommonUtils.Size(file);
UnusedScenes = new Dictionary<string, long>();
foreach (var scene in scenes) UnusedScenes[scene] = CommonUtils.Size(scene);
// UpdateFoldersWithQty();
foreach (var folder in Folders) {
var unusedFilesQty = UnusedFiles.Count(p => p.Key.StartsWith(folder));
var unusedScenesQty = UnusedScenes.Count(p => p.Key.StartsWith(folder));
long size = 0;
size = UnusedFiles.Where((p => p.Key.StartsWith(folder))).Sum(p => p.Value);
size += UnusedScenes.Where(p => p.Key.StartsWith(folder)).Sum(p => p.Value);
FoldersWithQty.TryGetValue(folder, out var folderWithQty);
if (folderWithQty == null) {
FoldersWithQty.Add(folder, new UnusedQty(unusedFilesQty, unusedScenesQty, size));
}
else {
folderWithQty.UnusedFilesQty = unusedFilesQty;
folderWithQty.UnusedScenesQty = unusedScenesQty;
folderWithQty.UnusedSize = size;
}
}
}
public void Remove(string path) {
if (!_forward.TryGetValue(path, out var fwMeta))
return;
foreach (var dependency in fwMeta.Dependencies) {
if (!Backward.TryGetValue(dependency, out var dep)) continue;
dep.Lookup.Remove(path);
}
_forward.Remove(path);
UpdateFoldersWithQtyByPath(path);
}
public void Replace(string src, string dest) {
_Upd(_forward);
_Upd(Backward);
UpdateFoldersWithQtyByPath(dest);
void _Upd<T>(Dictionary<string, T> dic) {
if (!dic.TryGetValue(src, out var refs)) return;
dic.Remove(src);
dic.Add(dest, refs);
}
}
public void RebuildFor(string path, bool remove) {
if (!_forward.TryGetValue(path, out var fwMeta)) {
fwMeta = new FwMeta();
_forward.Add(path, fwMeta);
}
else if (remove) {
foreach (var dependency in fwMeta.Dependencies) {
if (!Backward.TryGetValue(dependency, out var backDep)) continue;
backDep.Lookup.Remove(path);
}
fwMeta.Dependencies = null;
}
var dependencies = _Dependencies(path);
fwMeta.Dependencies = new HashSet<string>(dependencies);
foreach (var backPath in dependencies) {
if (!Backward.TryGetValue(backPath, out var bwMeta)) {
bwMeta = new BwMeta {Lookup = new HashSet<string>()};
Backward.Add(backPath, bwMeta);
}
else if (remove)
bwMeta.Lookup.Remove(path);
bwMeta.Lookup.Add(path);
}
if (!remove) {
UpdateFoldersWithQtyByPath(path);
}
}
static string[] _Dependencies(string s) {
if (s[0] == 'A')
return AssetDatabase.GetDependencies(s, false);
var obj = LoadAllOrMain(s)[0];
return GetDependenciesManualPaths().ToArray();
Object[] LoadAllOrMain(string assetPath) {
// prevents error "Do not use readobjectthreaded on scene objects!"
return typeof(SceneAsset) == AssetDatabase.GetMainAssetTypeAtPath(assetPath)
? new[] {AssetDatabase.LoadMainAssetAtPath(assetPath)}
: AssetDatabase.LoadAllAssetsAtPath(assetPath);
}
IEnumerable<string> GetDependenciesManualPaths() {
if (obj is EditorBuildSettings) {
foreach (var scene in EditorBuildSettings.scenes)
yield return scene.path;
}
using (var so = new SerializedObject(obj)) {
var props = so.GetIterator();
while (props.Next(true)) {
switch (props.propertyType) {
case SerializedPropertyType.ObjectReference:
var propsObjectReferenceValue = props.objectReferenceValue;
if (!propsObjectReferenceValue) continue;
var assetPath = AssetDatabase.GetAssetPath(propsObjectReferenceValue);
yield return assetPath;
break;
#if later
case SerializedPropertyType.Generic:
case SerializedPropertyType.ExposedReference:
case SerializedPropertyType.ManagedReference:
break;
#endif
default:
continue;
}
}
}
}
}
class FwMeta {
public HashSet<string> Dependencies;
}
public class BwMeta {
public HashSet<string> Lookup;
}
public class UnusedQty {
public int UnusedFilesQty;
public int UnusedScenesQty;
public long UnusedSize;
public UnusedQty() {
Init(0, 0, 0);
}
public UnusedQty(int unusedFilesQty, int unusedScenesQty, long unusedSize) {
Init(unusedFilesQty, unusedScenesQty, unusedSize);
}
private void Init(int unusedFilesQty, int unusedScenesQty, long unusedSize) {
UnusedFilesQty = unusedFilesQty;
UnusedScenesQty = unusedScenesQty;
UnusedSize = unusedSize;
}
}
}
}