123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.CompilerServices;
- using Leopotam.Ecs;
- using UnityEditor;
- using UnityEditor.Animations;
- using UnityEditor.SceneManagement;
- using UnityEngine;
- using UnityEngine.SceneManagement;
- using static Asset_Cleaner.AufCtx;
- using Object = UnityEngine.Object;
- namespace Asset_Cleaner {
- static class SearchUtils {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsAssignableFromInverse(this Type lhs, Type rhs) {
- if (lhs == null || rhs == null)
- return false;
- return rhs.IsAssignableFrom(lhs);
- }
- static Queue<SerializedProperty> _tmp = new Queue<SerializedProperty>();
- public static void Upd(SearchArg arg) {
- if (arg.Target is DefaultAsset folder) {
- var path = AssetDatabase.GetAssetPath(folder);
- var store = Globals<BacklinkStore>.Value;
- arg.UnusedAssetsFiltered = store.UnusedFiles.Where(p => p.Key.StartsWith(path)).Select(p => p.Key).ToList();
- arg.UnusedScenesFiltered = store.UnusedScenes.Where(p => p.Key.StartsWith(path)).Select(p => p.Key).ToList();
- }
- }
- public static void Init(SearchArg arg, Object target, Scene scene = default) {
- Asr.IsNotNull(target, "Asset you're trying to search is corrupted");
- arg.Target = target;
- arg.FilePath = AssetDatabase.GetAssetPath(arg.Target);
- if (!scene.IsValid()) {
- Upd(arg);
- arg.Main = AssetDatabase.LoadMainAssetAtPath(arg.FilePath);
- if (AssetDatabase.IsSubAsset(arg.Target)) { }
- else {
- switch (target) {
- case SceneAsset _:
- // todo support cross-scene references?
- // nested = all assets
- break;
- default:
- // AssetDatabase.IsMainAssetAtPathLoaded()
- var subAssets = AssetDatabase.LoadAllAssetsAtPath(arg.FilePath).Where(Predicate).ToArray();
- arg.SubAssets = subAssets.Length == 0 ? default(Option<Object[]>) : subAssets;
- bool Predicate(Object s) {
- if (!s)
- return false;
- return s.GetInstanceID() != arg.Target.GetInstanceID();
- }
- break;
- }
- }
- }
- else {
- switch (arg.Target) {
- case GameObject gg:
- arg.Main = gg;
- arg.Scene = scene;
- arg.SubAssets = gg.GetComponents<Component>().OfType<Object>().ToArray();
- break;
- case Component component: {
- // treat like subAsset
- arg.Main = component.gameObject;
- arg.Scene = scene;
- break;
- }
- default:
- // project asset such as Material
- arg.Main = arg.Target;
- arg.Scene = scene;
- break;
- }
- }
- }
- static bool SearchInChildProperties(SearchArg arg, Object suspect, bool scene, out EcsEntity entity) {
- if (IsTargetOrNested(arg, suspect)) {
- entity = default;
- return false;
- }
- if (!suspect) {
- entity = default;
- return false;
- }
- var so = new SerializedObject(suspect);
- _tmp.Clear();
- var queue = _tmp;
- var propIterator = so.GetIterator();
- var prefabInstance = false;
- if (scene && !string.IsNullOrEmpty(arg.FilePath) && PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(suspect) == arg.FilePath) {
- prefabInstance = true;
- while (propIterator.NextVisible(true)) {
- if (propIterator.propertyType != SerializedPropertyType.ObjectReference)
- continue;
- if (!IsTargetOrNested(arg, propIterator.objectReferenceValue))
- continue;
- queue.Enqueue(propIterator.Copy());
- }
- }
- else {
- while (propIterator.Next(true)) {
- if (propIterator.propertyType != SerializedPropertyType.ObjectReference)
- continue;
- if (!IsTargetOrNested(arg, propIterator.objectReferenceValue))
- continue;
- queue.Enqueue(propIterator.Copy());
- }
- }
- if (queue.Count == 0 && !prefabInstance) {
- entity = default;
- return false;
- }
- entity = World.NewEntityWith(out Result data);
- var gui = entity.Set<SearchResultGui>();
- gui.Properties = new List<SearchResultGui.PropertyData>();
- gui.SerializedObject = so;
- gui.Label = new GUIContent();
- // init header
- Texture2D miniTypeThumbnail = null;
- if (scene) {
- switch (suspect) {
- case Component component:
- data.RootGo = component.gameObject;
- gui.TransformPath = AnimationUtility.CalculateTransformPath(component.transform, null);
- gui.Label.image = AssetPreview.GetMiniThumbnail(data.RootGo);
- gui.Label.text = gui.TransformPath;
- break;
- case GameObject go:
- data.RootGo = go;
- gui.Label.image = AssetPreview.GetMiniThumbnail(data.RootGo);
- gui.TransformPath = AnimationUtility.CalculateTransformPath(go.transform, null);
- gui.Label.text = gui.TransformPath;
- break;
- default:
- throw new NotImplementedException();
- }
- miniTypeThumbnail = data.RootGo.GetInstanceID() == suspect.GetInstanceID()
- ? null
- : AssetPreview.GetMiniThumbnail(suspect);
- }
- else {
- data.File = suspect;
- data.FilePath = AssetDatabase.GetAssetPath(data.File);
- data.MainFile = AssetDatabase.LoadMainAssetAtPath(data.FilePath);
- // todo
- var prefabInstanceStatus = PrefabUtility.GetPrefabInstanceStatus(data.MainFile);
- switch (prefabInstanceStatus) {
- case PrefabInstanceStatus.Connected:
- case PrefabInstanceStatus.Disconnected:
- switch (data.File) {
- case Component comp:
- // transformPath = $"{AnimationUtility.CalculateTransformPath(comp.transform, null)}/".Replace("/", "/\n");
- gui.TransformPath =
- $"{AnimationUtility.CalculateTransformPath(comp.transform, null)}";
- break;
- case GameObject go:
- // transformPath = $"{AnimationUtility.CalculateTransformPath(go.transform, null)}/".Replace("/", "/\n");
- gui.TransformPath =
- $"{AnimationUtility.CalculateTransformPath(go.transform, null)}";
- break;
- default:
- // Assert.Fail("Not a component"); //todo
- break;
- }
- break;
- case PrefabInstanceStatus.NotAPrefab:
- case PrefabInstanceStatus.MissingAsset:
- if (!AssetDatabase.IsMainAsset(data.File)) {
- // {row.Main.name}
- gui.TransformPath = $"/{data.File.name}";
- }
- break;
- }
- gui.Label.text = data.FilePath.Replace(AssetsRootPath, string.Empty);
- gui.Label.image = AssetDatabase.GetCachedIcon(data.FilePath);
- }
- gui.Label.tooltip = gui.TransformPath;
- // init properties (footer)
- while (queue.Count > 0) {
- var prop = queue.Dequeue();
- var targetObject = prop.serializedObject.targetObject;
- var item = new SearchResultGui.PropertyData {
- Property = prop,
- Content = new GUIContent()
- };
- item.Content.image = miniTypeThumbnail;
- item.Content.text = Nicify(prop, targetObject, gui.TransformPath);
- item.Content.tooltip = gui.TransformPath;
- var typeName = targetObject.GetType().Name;
- if (StringComparer.Ordinal.Equals(typeName, targetObject.name))
- item.Content.tooltip = $"{gui.TransformPath}.{prop.propertyPath}";
- else
- item.Content.tooltip = $"{gui.TransformPath}({typeName}).{prop.propertyPath}";
- gui.Properties.Add(item: item);
- }
- return true;
- }
- public static bool IsFileIgrnoredBySettings(string path) {
- if (IgnoreTypes.Check(path, out _)) return true;
- if (IgnoredNonAssets(path)) return true;
- if (IgnoredPaths(path, out _)) return true;
- return false;
- }
- public static bool IgnoredPaths(string path, out string str) {
- var conf = Globals<Config>.Value;
- foreach (var substr in conf.IgnorePathContains) {
- Asr.IsNotNull(path);
- Asr.IsNotNull(substr);
- if (!path.Contains(substr)) continue;
- str = substr;
- return true;
- }
- str = default;
- return false;
- }
- public static bool IgnoredNonAssets(string path) {
- return !path.Contains("Assets/");
- }
- #region Project
- public static bool IsUnused(string path) {
- if (IsFileIgrnoredBySettings(path))
- return false;
- return !AnyDependencies(path);
- }
- static bool AnyDependencies(string path) {
- var store = Globals<BacklinkStore>.Value;
- if (store.UnusedFiles.Select(p => p.Key).Contains(path))
- return false;
- if (store.UnusedScenes.Select(p => p.Key).Contains(path))
- return false;
- return true;
- }
- public static void FilesThatReference(SearchArg arg) {
- var store = Globals<BacklinkStore>.Value;
- var path1 = AssetDatabase.GetAssetPath(arg.Target);
- if (!store.Backward.TryGetValue(path1, out var dep))
- return;
- foreach (var path in dep.Lookup) {
- var mainAsset = AssetDatabase.GetMainAssetTypeAtPath(path);
- if (mainAsset.IsAssignableFromInverse(typeof(SceneAsset)))
- continue;
- var any = false;
- if (mainAsset.IsAssignableFromInverse(typeof(GameObject))) { }
- else {
- var allAssetsAtPath = AssetDatabase.LoadAllAssetsAtPath(path);
- foreach (var suspect in allAssetsAtPath) {
- if (suspect is DefaultAsset || suspect is Transform || !suspect) continue;
- if (!SearchInChildProperties(arg, suspect, false, out var entity))
- continue;
- entity.Set<FileResultTag>();
- any = true;
- }
- }
- if (any) continue;
- // failed to find any property - just show main asset
- var e = World.NewEntity();
- var gui = e.Set<SearchResultGui>();
- gui.Properties = new List<SearchResultGui.PropertyData>();
- var main = AssetDatabase.LoadMainAssetAtPath(path);
- gui.Label = new GUIContent() {
- image = AssetPreview.GetMiniThumbnail(main),
- text = path.Replace(AssetsRootPath, string.Empty)
- };
- var res = e.Set<Result>();
- res.MainFile = main;
- e.Set<FileResultTag>();
- }
- }
- public static void ScenesThatContain(Object activeObject) {
- var store = Globals<BacklinkStore>.Value;
- var path1 = AssetDatabase.GetAssetPath(activeObject);
- if (!store.Backward.TryGetValue(path1, out var dep))
- return;
- foreach (var path in dep.Lookup) {
- if (!AssetDatabase.GetMainAssetTypeAtPath(path).IsAssignableFromInverse(typeof(SceneAsset)))
- continue;
- World.NewEntityWith(out SceneResult sp, out SceneDetails sceneDetails);
- sp.PathNicified = path.Replace("Assets/", string.Empty);
- // heavy
- sceneDetails.Path = path;
- var alreadyOpened = false;
- for (var i = 0; i < EditorSceneManager.loadedSceneCount; i++) {
- var cur = SceneManager.GetSceneAt(i);
- if (!cur.path.Eq(sceneDetails.Path)) continue;
- alreadyOpened = true;
- sceneDetails.Scene = cur;
- }
- sceneDetails.WasOpened = alreadyOpened;
- }
- }
- #endregion
- #region Scene
- static Pool<List<Component>> ComponentListPool = new Pool<List<Component>>(() => new List<Component>(), list => list.Clear());
- // todo provide explicit scene arg
- public static void InScene(SearchArg arg, Scene currentScene) {
- var rootGameObjects = currentScene.GetRootGameObjects();
- foreach (var suspect in Traverse(rootGameObjects)) {
- if (!SearchInChildProperties(arg, suspect, scene: true, out var entity))
- continue;
- var s = entity.Set<InSceneResult>();
- s.ScenePath = arg.Scene.path;
- }
- IEnumerable<Object> Traverse(GameObject[] roots) {
- foreach (var rootGo in roots)
- foreach (var comp in GoAndChildComps(rootGo)) {
- yield return comp;
- }
- }
- IEnumerable<Object> GoAndChildComps(GameObject rr) {
- using (ComponentListPool.GetScoped(out var pooled)) {
- rr.GetComponents(pooled);
- foreach (var component in pooled) {
- if (component is Transform)
- continue;
- yield return component;
- }
- }
- var transform = rr.transform;
- var childCount = transform.childCount;
- for (int i = 0; i < childCount; i++) {
- var g = transform.GetChild(i).gameObject;
- foreach (var res in GoAndChildComps(g))
- yield return res;
- }
- }
- }
- #if backup
- static void InScene(SearchArg arg, Scene currentScene) {
- var allObjects = currentScene
- .GetRootGameObjects()
- .SelectMany(g => g.GetComponentsInChildren<Component>(true)
- .Where(c => c && !(c is Transform))
- .Union(Enumerable.Repeat(g as Object, 1))
- )
- .ToArray();
- var total = allObjects.Length;
- for (var i = 0; i < total; i++) {
- var suspect = allObjects[i];
- if (SearchInChildProperties(arg, suspect, true, out var entity)) {
- var s = entity.Set<InSceneResult>();
- s.ScenePath = arg.Scene.path;
- }
- }
- }
- #endif
- static bool IsTargetOrNested(SearchArg target, Object suspect) {
- if (!suspect)
- return false;
- if (target.Target.GetInstanceID() == suspect.GetInstanceID() || target.Main.GetInstanceID() == (suspect).GetInstanceID())
- return true;
- if (target.SubAssets.TryGet(out var subassets))
- foreach (var asset in subassets) {
- if (asset.GetInstanceID() == (suspect).GetInstanceID())
- return true;
- }
- return false;
- }
- static string Nicify(SerializedProperty sp, Object o, string transformPath) {
- // return sp.propertyPath;
- string nice;
- switch (o) {
- case AnimatorState animatorState:
- return animatorState.name;
- case Material material:
- nice = material.name;
- break;
- default: {
- nice = sp.propertyPath.Replace(".Array.data", string.Empty);
- if (nice.IndexOf(".m_PersistentCalls.m_Calls", StringComparison.Ordinal) > 0) {
- nice = nice.Replace(".m_PersistentCalls.m_Calls", string.Empty)
- .Replace(".m_Target", string.Empty);
- }
- nice = nice.Split('.').Select(t => ObjectNames.NicifyVariableName(t).Replace(" ", string.Empty))
- .Aggregate((a, b) => a + "." + b);
- break;
- }
- }
- // nice = $"{transformPath}({o.GetType().Name}).{nice}";
- nice = $"({o.GetType().Name}).{nice}";
- return nice;
- }
- const string AssetsRootPath = "Assets/";
- #endregion
- }
- }
|