From 32c1edd0a518447581c82b7278bee5bb3655bbde Mon Sep 17 00:00:00 2001 From: Molth Nevin Date: Mon, 26 May 2025 15:07:50 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BC=95=E7=94=A8=E5=88=86?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Assets/Editor/ReferenceFinder.meta | 8 + .../Editor/ReferenceFinder/AssetTreeView.cs | 174 ++++++++ .../ReferenceFinder/AssetTreeView.cs.meta | 11 + .../Editor/ReferenceFinder/AssetViewItem.cs | 9 + .../ReferenceFinder/AssetViewItem.cs.meta | 11 + .../Editor/ReferenceFinder/ClickColumn.cs | 34 ++ .../ReferenceFinder/ClickColumn.cs.meta | 11 + .../ReferenceFinder/DragAreaGetObject.cs | 29 ++ .../ReferenceFinder/DragAreaGetObject.cs.meta | 11 + .../Assets/Editor/ReferenceFinder/ListInfo.cs | 9 + .../Editor/ReferenceFinder/ListInfo.cs.meta | 11 + .../ReferenceFinder/ReferenceFinderData.cs | 417 ++++++++++++++++++ .../ReferenceFinderData.cs.meta | 11 + .../ReferenceFinder/ResourceReferenceInfo.cs | 307 +++++++++++++ .../ResourceReferenceInfo.cs.meta | 11 + .../Editor/ReferenceFinder/SortConfig.cs | 33 ++ .../Editor/ReferenceFinder/SortConfig.cs.meta | 11 + .../Editor/ReferenceFinder/SortHelper.cs | 111 +++++ .../Editor/ReferenceFinder/SortHelper.cs.meta | 11 + .../Assets/Editor/ReferenceFinder/SortType.cs | 11 + .../Editor/ReferenceFinder/SortType.cs.meta | 11 + 21 files changed, 1252 insertions(+) create mode 100644 UnityProject/Assets/Editor/ReferenceFinder.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs.meta create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/SortType.cs create mode 100644 UnityProject/Assets/Editor/ReferenceFinder/SortType.cs.meta diff --git a/UnityProject/Assets/Editor/ReferenceFinder.meta b/UnityProject/Assets/Editor/ReferenceFinder.meta new file mode 100644 index 00000000..2ff9d24f --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e11095ce128a7214a88c291ea368df9a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs b/UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs new file mode 100644 index 00000000..c2dd1033 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs @@ -0,0 +1,174 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace TEngine.Editor +{ + internal sealed class AssetTreeView : TreeView + { + private const float K_ICON_WIDTH = 18f; + private const float K_ROW_HEIGHTS = 20f; + private readonly GUIStyle _stateGuiStyle = new GUIStyle { richText = true, alignment = TextAnchor.MiddleCenter }; + public AssetViewItem assetRoot; + + public AssetTreeView(TreeViewState state, MultiColumnHeader multicolumnHeader) : base(state, multicolumnHeader) + { + rowHeight = K_ROW_HEIGHTS; + columnIndexForTreeFoldouts = 0; + showAlternatingRowBackgrounds = true; + showBorder = false; + customFoldoutYOffset = (K_ROW_HEIGHTS - EditorGUIUtility.singleLineHeight) * 0.5f; + extraSpaceBeforeIconAndLabel = K_ICON_WIDTH; + } + + protected override void DoubleClickedItem(int id) + { + AssetViewItem item = (AssetViewItem)FindItem(id, rootItem); + if (item != null) + { + Object assetObject = AssetDatabase.LoadAssetAtPath(item.data.path, typeof(Object)); + EditorUtility.FocusProjectWindow(); + Selection.activeObject = assetObject; + EditorGUIUtility.PingObject(assetObject); + } + } + + protected override void ExpandedStateChanged() => SortExpandItem(); + + public void SortExpandItem() + { + if (SortHelper.CurSortType == SortType.None) return; + IList expandItemList = GetExpanded(); + foreach (int i in expandItemList) + { + AssetViewItem item = (AssetViewItem)FindItem(i, rootItem); + SortHelper.SortChild(item.data); + } + + ResourceReferenceInfo curWindow = EditorWindow.GetWindow(); + curWindow.needUpdateAssetTree = true; + } + + public static MultiColumnHeaderState CreateDefaultMultiColumnHeaderState(float treeViewWidth, bool isDepend) + { + List columns = new List + { + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("名称"), + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + width = 200, + minWidth = 60, + autoResize = false, + allowToggleVisibility = false, + canSort = true, + sortingArrowAlignment = TextAlignment.Center + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("路径"), + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + width = 360, + minWidth = 60, + autoResize = false, + allowToggleVisibility = false, + canSort = true, + sortingArrowAlignment = TextAlignment.Center + }, + new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("状态"), + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + width = 60, + minWidth = 60, + autoResize = false, + allowToggleVisibility = true, + canSort = false + } + }; + if (!isDepend) + { + columns.Add(new MultiColumnHeaderState.Column + { + headerContent = new GUIContent("引用数量"), + headerTextAlignment = TextAlignment.Center, + sortedAscending = false, + width = 60, + minWidth = 60, + autoResize = true, + allowToggleVisibility = true, + canSort = false + }); + } + + MultiColumnHeaderState state = new MultiColumnHeaderState(columns.ToArray()); + return state; + } + + protected override TreeViewItem BuildRoot() => assetRoot; + + protected override void RowGUI(RowGUIArgs args) + { + AssetViewItem item = (AssetViewItem)args.item; + for (int i = 0; i < args.GetNumVisibleColumns(); ++i) + CellGUI(args.GetCellRect(i), item, (MyColumns)args.GetColumn(i), ref args); + } + + private void CellGUI(Rect cellRect, AssetViewItem item, MyColumns column, ref RowGUIArgs args) + { + CenterRectUsingSingleLineHeight(ref cellRect); + switch (column) + { + case MyColumns.Name: + Rect iconRect = cellRect; + iconRect.x += GetContentIndent(item); + iconRect.width = K_ICON_WIDTH; + if (iconRect.x < cellRect.xMax) + { + Texture2D icon = GetIcon(item.data.path); + if (icon != null) + GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit); + } + + args.rowRect = cellRect; + base.RowGUI(args); + break; + case MyColumns.Path: + GUI.Label(cellRect, item.data.path); + break; + case MyColumns.State: + GUI.Label(cellRect, ReferenceFinderData.GetInfoByState(item.data.state), _stateGuiStyle); + break; + case MyColumns.RefCount: + GUI.Label(cellRect, ResourceReferenceInfo.Data.GetRefCount(item.data, (item.parent as AssetViewItem)?.data), _stateGuiStyle); + break; + } + } + + private Texture2D GetIcon(string path) + { + Object obj = AssetDatabase.LoadAssetAtPath(path, typeof(Object)); + if (obj) + { + Texture2D icon = AssetPreview.GetMiniThumbnail(obj); + if (!icon) + icon = AssetPreview.GetMiniTypeThumbnail(obj.GetType()); + return icon; + } + + return null; + } + + private enum MyColumns + { + Name, + Path, + State, + RefCount + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs.meta new file mode 100644 index 00000000..f941e115 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/AssetTreeView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd079ed03e211ca4698770fc925dcf0a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs b/UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs new file mode 100644 index 00000000..b70ea2e9 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs @@ -0,0 +1,9 @@ +using UnityEditor.IMGUI.Controls; + +namespace TEngine.Editor +{ + internal sealed class AssetViewItem : TreeViewItem + { + public ReferenceFinderData.AssetDescription data; + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs.meta new file mode 100644 index 00000000..196fe990 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/AssetViewItem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62d7d07cebb3f9b4998845a11799e12c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs b/UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs new file mode 100644 index 00000000..00921a29 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.IMGUI.Controls; + +namespace TEngine.Editor +{ + internal sealed class ClickColumn : MultiColumnHeader + { + public delegate void SortInColumn(); + + public static Dictionary SortWithIndex = new Dictionary + { + { 0, SortByName }, + { 1, SortByPath } + }; + + public ClickColumn(MultiColumnHeaderState state) : base(state) => canSort = true; + + protected override void ColumnHeaderClicked(MultiColumnHeaderState.Column column, int columnIndex) + { + base.ColumnHeaderClicked(column, columnIndex); + if (SortWithIndex.ContainsKey(columnIndex)) + { + SortWithIndex[columnIndex].Invoke(); + ResourceReferenceInfo curWindow = EditorWindow.GetWindow(); + curWindow.mAssetTreeView.SortExpandItem(); + } + } + + public static void SortByName() => SortHelper.SortByName(); + + public static void SortByPath() => SortHelper.SortByPath(); + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs.meta new file mode 100644 index 00000000..cbd9d0e4 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ClickColumn.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4047444d841109d46b15817bbe887281 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs b/UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs new file mode 100644 index 00000000..f4ccfa17 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs @@ -0,0 +1,29 @@ +using UnityEditor; +using UnityEngine; + +namespace TEngine.Editor +{ + internal sealed class DragAreaGetObject + { + public static Object[] GetObjects(string meg = null) + { + Event aEvent = Event.current; + GUI.contentColor = Color.white; + if (aEvent.type is EventType.DragUpdated or EventType.DragPerform) + { + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + bool needReturn = false; + if (aEvent.type == EventType.DragPerform) + { + DragAndDrop.AcceptDrag(); + needReturn = true; + } + + Event.current.Use(); + if (needReturn) return DragAndDrop.objectReferences; + } + + return null; + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs.meta new file mode 100644 index 00000000..3c21b2d0 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/DragAreaGetObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 59773aa6a03462c488a3e18ea3de8660 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs b/UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs new file mode 100644 index 00000000..a7c019ff --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs @@ -0,0 +1,9 @@ +namespace TEngine.Editor +{ + internal sealed class ListInfo + { + public int Count; + public string Name; + public string Type; + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs.meta new file mode 100644 index 00000000..90a92d6f --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ListInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36df700a4102b0c4db5f7ec3c485cf0c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs b/UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs new file mode 100644 index 00000000..68ed72ab --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using UnityEditor; +using UnityEngine; + +// ReSharper disable InconsistentNaming + +namespace TEngine.Editor +{ + internal sealed class ReferenceFinderData + { + public enum AssetState : byte + { + Normal, + Changed, + Missing, + Invalid + } + + private const string CachePath = "Library/ReferenceFinderCache"; + public const int MinThreadCount = 8; + private const int SingleThreadReadCount = 100; + private static readonly int ThreadCount = Math.Max(MinThreadCount, Environment.ProcessorCount); + private static string _basePath; + + private static readonly HashSet FileExtension = new HashSet + { + ".prefab", + ".unity", + ".mat", + ".asset", + ".anim", + ".controller" + }; + + private static readonly Regex GuidRegex = new Regex("guid: ([a-z0-9]{32})", RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Dictionary<(AssetDescription, AssetDescription), int> _dictCache = new Dictionary<(AssetDescription, AssetDescription), int>(); + private readonly List> _threadAssetDict = new List>(); + private readonly List _threadList = new List(); + private int _curReadAssetCount; + private int _totalCount; + public string[] allAssets; + public Dictionary assetDict = new Dictionary(); + + public void CollectDependenciesInfo() + { + try + { + _basePath = Application.dataPath.Replace("/Assets", ""); + ReadFromCache(); + allAssets = AssetDatabase.GetAllAssetPaths(); + _totalCount = allAssets.Length; + _threadList.Clear(); + _curReadAssetCount = 0; + foreach (Dictionary i in _threadAssetDict) + i.Clear(); + _threadAssetDict.Clear(); + for (int i = 0; i < ThreadCount; i++) _threadAssetDict.Add(new Dictionary()); + bool allThreadFinish = false; + for (int i = 0; i < ThreadCount; i++) + { + ThreadStart method = ReadAssetInfo; + Thread readThread = new Thread(method); + _threadList.Add(readThread); + readThread.Start(); + } + + while (!allThreadFinish) + { + if (_curReadAssetCount % 500 == 0 && + EditorUtility.DisplayCancelableProgressBar("Updating", $"Handle {_curReadAssetCount}", (float)_curReadAssetCount / _totalCount)) + { + EditorUtility.ClearProgressBar(); + foreach (Thread i in _threadList) + i.Abort(); + return; + } + + allThreadFinish = true; + foreach (Thread i in _threadList) + { + if (i.IsAlive) + { + allThreadFinish = false; + break; + } + } + } + + foreach (Dictionary dict in _threadAssetDict) + { + foreach (KeyValuePair j in dict) + assetDict[j.Key] = j.Value; + } + + EditorUtility.DisplayCancelableProgressBar("Updating", "Write cache", 1f); + WriteToChache(); + EditorUtility.DisplayCancelableProgressBar("Updating", "Generate reference data", 1f); + UpdateResourceReferenceInfo(); + EditorUtility.ClearProgressBar(); + } + catch (Exception e) + { + Debug.LogError(e); + EditorUtility.ClearProgressBar(); + } + } + + public void ReadAssetInfo() + { + int index = Thread.CurrentThread.ManagedThreadId % ThreadCount; + int intervalLength = _totalCount / ThreadCount; + int start = intervalLength * index; + int end = start + intervalLength; + if (_totalCount - end < intervalLength) + end = _totalCount; + int readAssetCount = 0; + for (int i = start; i < end; i++) + { + if (readAssetCount % SingleThreadReadCount == 0) + { + _curReadAssetCount += readAssetCount; + readAssetCount = 0; + } + + GetAsset(_basePath, allAssets[i]); + readAssetCount++; + } + } + + public void GetAsset(string dataPath, string assetPath) + { + string extLowerStr = Path.GetExtension(assetPath).ToLower(); + bool needReadFile = FileExtension.Contains(extLowerStr); + string fileName = $"{dataPath}/{assetPath}"; + string metaFile = $"{dataPath}/{assetPath}.meta"; + if (File.Exists(fileName) && File.Exists(metaFile)) + { + string metaText = File.ReadAllText(metaFile, Encoding.UTF8); + MatchCollection matchRs = GuidRegex.Matches(metaText); + string selfGuid = matchRs[0].Groups[1].Value.ToLower(); + string lastModifyTime = File.GetLastWriteTime(fileName).ToString(CultureInfo.InvariantCulture); + MatchCollection guids = null; + List depend = new List(); + if (needReadFile) + { + string fileStr = File.ReadAllText(fileName, Encoding.UTF8); + guids = GuidRegex.Matches(fileStr); + } + + int curListIndex = Thread.CurrentThread.ManagedThreadId % ThreadCount; + Dictionary curDict = _threadAssetDict[curListIndex]; + if (!curDict.ContainsKey(selfGuid) || curDict[selfGuid].assetDependencyHashString != lastModifyTime) + { + if (guids != null) + { + for (int index = 0; index < guids.Count; ++index) + { + Match i = guids[index]; + depend.Add(i.Groups[1].Value.ToLower()); + } + } + + AssetDescription ad = new AssetDescription + { + name = Path.GetFileNameWithoutExtension(assetPath), + path = assetPath, + assetDependencyHashString = lastModifyTime, + dependencies = depend + }; + + if (_threadAssetDict[curListIndex].ContainsKey(selfGuid)) + _threadAssetDict[curListIndex][selfGuid] = ad; + else + _threadAssetDict[curListIndex].Add(selfGuid, ad); + } + } + } + + private void UpdateResourceReferenceInfo() + { + foreach (KeyValuePair asset in assetDict) + { + foreach (string assetGuid in asset.Value.dependencies) + { + if (assetDict.ContainsKey(assetGuid)) + assetDict[assetGuid].references.Add(asset.Key); + } + } + } + + public bool ReadFromCache() + { + assetDict.Clear(); + ClearCache(); + if (File.Exists(CachePath)) + { + List serializedGuid; + List serializedDependencyHash; + List serializedDenpendencies; + using (FileStream fs = File.OpenRead(CachePath)) + { + BinaryFormatter bf = new BinaryFormatter(); + if (EditorUtility.DisplayCancelableProgressBar("Import Cache", "Reading Cache", 0)) + { + EditorUtility.ClearProgressBar(); + return false; + } + + serializedGuid = (List)bf.Deserialize(fs); + serializedDependencyHash = (List)bf.Deserialize(fs); + serializedDenpendencies = (List)bf.Deserialize(fs); + EditorUtility.ClearProgressBar(); + } + + for (int i = 0; i < serializedGuid.Count; ++i) + { + string path = AssetDatabase.GUIDToAssetPath(serializedGuid[i]); + if (string.IsNullOrEmpty(path)) + { + AssetDescription ad = new AssetDescription + { + name = Path.GetFileNameWithoutExtension(path), + path = path, + assetDependencyHashString = serializedDependencyHash[i] + }; + assetDict.Add(serializedGuid[i], ad); + } + } + + for (int i = 0; i < serializedGuid.Count; ++i) + { + string guid = serializedGuid[i]; + if (assetDict.ContainsKey(guid)) + { + List guids = new List(); + foreach (int index in serializedDenpendencies[i]) + { + string g = serializedGuid[index]; + if (assetDict.ContainsKey(g)) + guids.Add(g); + } + + assetDict[guid].dependencies = guids; + } + } + + UpdateResourceReferenceInfo(); + return true; + } + + return false; + } + + private void WriteToChache() + { + if (File.Exists(CachePath)) + File.Delete(CachePath); + List serializedGuid = new List(); + List serializedDependencyHash = new List(); + List serializedDenpendencies = new List(); + Dictionary guidIndex = new Dictionary(); + using FileStream fs = File.OpenWrite(CachePath); + foreach (KeyValuePair pair in assetDict) + { + guidIndex.Add(pair.Key, guidIndex.Count); + serializedGuid.Add(pair.Key); + serializedDependencyHash.Add(pair.Value.assetDependencyHashString); + } + + foreach (string guid in serializedGuid) + { + List res = new List(); + foreach (string i in assetDict[guid].dependencies) + { + if (guidIndex.TryGetValue(i, out var value)) + res.Add(value); + } + + int[] indexes = res.ToArray(); + serializedDenpendencies.Add(indexes); + } + + BinaryFormatter bf = new BinaryFormatter(); + bf.Serialize(fs, serializedGuid); + bf.Serialize(fs, serializedDependencyHash); + bf.Serialize(fs, serializedDenpendencies); + } + + public void UpdateAssetState(string guid) + { + if (assetDict.TryGetValue(guid, out AssetDescription ad) && ad.state != AssetState.Invalid) + { + if (File.Exists(ad.path)) + ad.state = ad.assetDependencyHashString != File.GetLastWriteTime(ad.path).ToString(CultureInfo.InvariantCulture) ? AssetState.Changed : AssetState.Normal; + else + ad.state = AssetState.Missing; + } + else if (!assetDict.TryGetValue(guid, out ad)) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + ad = new AssetDescription + { + name = Path.GetFileNameWithoutExtension(path), + path = path, + state = AssetState.Invalid + }; + assetDict.Add(guid, ad); + } + } + + public static string GetInfoByState(AssetState state) + { + if (state == AssetState.Changed) + return "缓存不匹配"; + if (state == AssetState.Missing) + return "缓存丢失"; + if (state == AssetState.Invalid) + return "缓存无效"; + return "缓存正常"; + } + + private int GetRefCount(string assetGUID, AssetDescription desc, List guidStack) + { + if (guidStack.Contains(assetGUID)) + { + Debug.Log("有循环引用, 计数可能不准确"); + return 0; + } + + guidStack.Add(assetGUID); + int total = 0; + if (assetDict.TryGetValue(assetGUID, out AssetDescription value)) + { + if (value.references.Count > 0) + { + Dictionary cachedRefCount = new Dictionary(); + foreach (string refs in value.references) + { + if (!cachedRefCount.ContainsKey(refs)) + { + int refCount = GetRefCount(refs, value, guidStack); + cachedRefCount[refs] = refCount; + total += refCount; + } + } + } + else + { + total = 0; + if (desc != null) + { + string guid = AssetDatabase.AssetPathToGUID(desc.path); + foreach (string deps in value.dependencies) + { + if (guid == deps) + total++; + } + } + } + } + + guidStack.RemoveAt(guidStack.Count - 1); + return total; + } + + public void ClearCache() => _dictCache.Clear(); + + public string GetRefCount(AssetDescription desc, AssetDescription parentDesc) + { + if (_dictCache.TryGetValue((desc, parentDesc), out int total)) + return total.ToString(); + string rootGUID = AssetDatabase.AssetPathToGUID(desc.path); + List guidInStack = new List { rootGUID }; + Dictionary cachedRefCount = new Dictionary(); + foreach (string refs in desc.references) + { + if (!cachedRefCount.ContainsKey(refs)) + { + int refCount = GetRefCount(refs, desc, guidInStack); + cachedRefCount[refs] = refCount; + total += refCount; + } + } + + if (desc.references.Count == 0 && parentDesc != null) + { + string guid = AssetDatabase.AssetPathToGUID(desc.path); + foreach (string refs in parentDesc.references) + { + if (refs == guid) + total++; + } + } + + guidInStack.RemoveAt(guidInStack.Count - 1); + _dictCache.Add((desc, parentDesc), total); + return total.ToString(); + } + + internal sealed class AssetDescription + { + public string assetDependencyHashString; + public List dependencies = new List(); + public string name = ""; + public string path = ""; + public List references = new List(); + public AssetState state = AssetState.Normal; + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs.meta new file mode 100644 index 00000000..22d7ba13 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ReferenceFinderData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b1fd14799f035fc4b817d8e78253548e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs b/UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs new file mode 100644 index 00000000..abc1c545 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs @@ -0,0 +1,307 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace TEngine.Editor +{ + internal sealed class ResourceReferenceInfo : EditorWindow + { + private const string IS_DEPEND_PREF_KEY = "ReferenceFinderData_IsDepend"; + public static readonly ReferenceFinderData Data = new ReferenceFinderData(); + private static bool _initializedData; + + [SerializeField] + private TreeViewState _treeViewState; + + public bool needUpdateAssetTree; + public bool needUpdateState = true; + public List selectedAssetGuid = new List(); + private readonly HashSet _brotherAssetIsAdd = new HashSet(); + private readonly HashSet _parentAssetIsAdd = new HashSet(); + private readonly HashSet _updatedAssetSet = new HashSet(); + private Dictionary _artInfo = new Dictionary(); + private bool _initializedGUIStyle; + private bool _isDepend; + private GUIStyle _toolbarButtonGUIStyle; + private GUIStyle _toolbarGUIStyle; + public AssetTreeView mAssetTreeView; + + private void OnEnable() => _isDepend = PlayerPrefs.GetInt(IS_DEPEND_PREF_KEY, 0) == 1; + + private void OnGUI() + { + UpdateDragAssets(); + InitGUIStyleIfNeeded(); + DrawOptionBar(); + UpdateAssetTree(); + mAssetTreeView?.OnGUI(new Rect(0, _toolbarGUIStyle.fixedHeight, position.width, position.height - _toolbarGUIStyle.fixedHeight)); + } + + [MenuItem("TEngine/查找资产引用", false, 100)] + public static void FindRef() + { + InitDataIfNeeded(); + OpenWindow(); + ResourceReferenceInfo window = GetWindow(); + window.UpdateSelectedAssets(); + } + + private static void OpenWindow() + { + ResourceReferenceInfo window = GetWindow(); + window.wantsMouseMove = false; + window.titleContent = new GUIContent("查找资产引用"); + window.Show(); + window.Focus(); + SortHelper.Init(); + } + + private static void InitDataIfNeeded() + { + if (!_initializedData) + { + if (!Data.ReadFromCache()) + Data.CollectDependenciesInfo(); + _initializedData = true; + } + } + + private void InitGUIStyleIfNeeded() + { + if (!_initializedGUIStyle) + { + _toolbarButtonGUIStyle = new GUIStyle("ToolbarButton"); + _toolbarGUIStyle = new GUIStyle("Toolbar"); + _initializedGUIStyle = true; + } + } + + private void UpdateSelectedAssets() + { + _artInfo = new Dictionary(); + selectedAssetGuid.Clear(); + foreach (Object obj in Selection.objects) + { + string path = AssetDatabase.GetAssetPath(obj); + if (Directory.Exists(path)) + { + string[] folder = { path }; + string[] guids = AssetDatabase.FindAssets(null, folder); + foreach (string guid in guids) + { + if (!selectedAssetGuid.Contains(guid) && !Directory.Exists(AssetDatabase.GUIDToAssetPath(guid))) + selectedAssetGuid.Add(guid); + } + } + else + { + string guid = AssetDatabase.AssetPathToGUID(path); + selectedAssetGuid.Add(guid); + } + } + + needUpdateAssetTree = true; + } + + private void UpdateDragAssets() + { + if (mouseOverWindow) + { + Object[] tempObj = DragAreaGetObject.GetObjects(); + if (tempObj != null) + { + InitDataIfNeeded(); + selectedAssetGuid.Clear(); + foreach (Object obj in tempObj) + { + string path = AssetDatabase.GetAssetPath(obj); + if (Directory.Exists(path)) + { + string[] folder = { path }; + string[] guids = AssetDatabase.FindAssets(null, folder); + foreach (string guid in guids) + { + if (!selectedAssetGuid.Contains(guid) && !Directory.Exists(AssetDatabase.GUIDToAssetPath(guid))) + selectedAssetGuid.Add(guid); + } + } + else + { + string guid = AssetDatabase.AssetPathToGUID(path); + selectedAssetGuid.Add(guid); + } + } + + needUpdateAssetTree = true; + } + } + } + + private void UpdateAssetTree() + { + if (needUpdateAssetTree && selectedAssetGuid.Count != 0) + { + AssetViewItem root = SelectedAssetGuidToRootItem(selectedAssetGuid); + if (mAssetTreeView == null) + { + if (_treeViewState == null) + _treeViewState = new TreeViewState(); + MultiColumnHeaderState headerState = AssetTreeView.CreateDefaultMultiColumnHeaderState(position.width, _isDepend); + ClickColumn multiColumnHeader = new ClickColumn(headerState); + mAssetTreeView = new AssetTreeView(_treeViewState, multiColumnHeader); + } + else + { + MultiColumnHeaderState headerState = AssetTreeView.CreateDefaultMultiColumnHeaderState(position.width, _isDepend); + ClickColumn multiColumnHeader = new ClickColumn(headerState); + mAssetTreeView.multiColumnHeader = multiColumnHeader; + } + + mAssetTreeView.assetRoot = root; + mAssetTreeView.Reload(); + needUpdateAssetTree = false; + int totalPrefab = 0; + int totalMat = 0; + string prefabName = ""; + string matName = ""; + StringBuilder sb = new StringBuilder(); + if (_artInfo.Count > 0) + { + foreach (KeyValuePair kv in _artInfo) + { + if (kv.Value.Type == "prefab") + { + totalPrefab += kv.Value.Count; + prefabName += kv.Value.Name + "<--->"; + } + + if (kv.Value.Type == "mat") + { + totalMat += kv.Value.Count; + matName += kv.Value.Name + "<--->"; + } + + string tempInfo = $"name [{kv.Key}], type: [{kv.Value.Type}], count: [{kv.Value.Count}]"; + sb.AppendLine(tempInfo); + } + } + + if (totalPrefab > 0) + sb.Insert(0, $"预制体总数 [{totalPrefab}] 预制体详情 [{prefabName}] \r\n"); + if (totalMat > 0) + sb.Insert(0, $"材质总数 [{totalMat}] 材质详情 [{matName}] \r\n"); + string str = sb.ToString(); + if (!string.IsNullOrEmpty(str)) + Debug.Log(str); + } + } + + public void DrawOptionBar() + { + EditorGUILayout.BeginHorizontal(_toolbarGUIStyle); + if (GUILayout.Button("点击更新本地缓存", _toolbarButtonGUIStyle)) + { + Data.CollectDependenciesInfo(); + needUpdateAssetTree = true; + GUIUtility.ExitGUI(); + } + + bool preIsDepend = _isDepend; + _isDepend = GUILayout.Toggle(_isDepend, _isDepend ? "依赖模式" : "引用模式", _toolbarButtonGUIStyle, GUILayout.Width(100)); + if (preIsDepend != _isDepend) + OnModelSelect(); + if (GUILayout.Button("展开", _toolbarButtonGUIStyle)) + mAssetTreeView?.ExpandAll(); + if (GUILayout.Button("折叠", _toolbarButtonGUIStyle)) + mAssetTreeView?.CollapseAll(); + EditorGUILayout.EndHorizontal(); + } + + private void OnModelSelect() + { + needUpdateAssetTree = true; + PlayerPrefs.SetInt(IS_DEPEND_PREF_KEY, _isDepend ? 1 : 0); + UpdateAssetTree(); + } + + private AssetViewItem SelectedAssetGuidToRootItem(List inputSelectedAssetGuid) + { + _updatedAssetSet.Clear(); + _parentAssetIsAdd.Clear(); + _brotherAssetIsAdd.Clear(); + int elementCount = 0; + AssetViewItem root = new AssetViewItem { id = elementCount, depth = -1, displayName = "Root", data = null }; + const int depth = 0; + foreach (string childGuid in inputSelectedAssetGuid) + { + AssetViewItem rs = CreateTree(childGuid, ref elementCount, depth); + root.AddChild(rs); + } + + _updatedAssetSet.Clear(); + return root; + } + + private AssetViewItem CreateTree(string guid, ref int elementCount, int depth) + { + if (_parentAssetIsAdd.Contains(guid)) + return null; + if (needUpdateState && !_updatedAssetSet.Contains(guid)) + { + Data.UpdateAssetState(guid); + _updatedAssetSet.Add(guid); + } + + ++elementCount; + ReferenceFinderData.AssetDescription referenceData = Data.assetDict[guid]; + AssetViewItem root = new AssetViewItem { id = elementCount, displayName = referenceData.name, data = referenceData, depth = depth }; + List childGuids = _isDepend ? referenceData.dependencies : referenceData.references; + _parentAssetIsAdd.Add(guid); + foreach (string childGuid in childGuids) + { + if (_brotherAssetIsAdd.Contains(childGuid)) continue; + ListInfo listInfo = new ListInfo(); + if (AssetDatabase.GUIDToAssetPath(childGuid).EndsWith(".mat") && depth < 2) + { + listInfo.Type = "mat"; + listInfo.Count = 1; + listInfo.Name = Path.GetFileName(AssetDatabase.GUIDToAssetPath(childGuid)); + if (!_artInfo.TryAdd(root.displayName, listInfo)) + { + _artInfo[root.displayName].Count += 1; + _artInfo[root.displayName].Name += "<<==>>" + listInfo.Name; + } + } + + if (AssetDatabase.GUIDToAssetPath(childGuid).EndsWith(".prefab") && !AssetDatabase.GUIDToAssetPath(childGuid).Contains("_gen_render") && depth < 2) + { + listInfo.Type = "prefab"; + listInfo.Count = 1; + listInfo.Name = Path.GetFileName(AssetDatabase.GUIDToAssetPath(childGuid)); + if (!_artInfo.TryAdd(root.displayName, listInfo)) + { + _artInfo[root.displayName].Count += 1; + _artInfo[root.displayName].Name += "<<==>>" + listInfo.Name; + } + } + + _brotherAssetIsAdd.Add(childGuid); + AssetViewItem rs = CreateTree(childGuid, ref elementCount, depth + 1); + if (rs != null) + root.AddChild(rs); + } + + foreach (string childGuid in childGuids) + { + if (_brotherAssetIsAdd.Contains(childGuid)) + _brotherAssetIsAdd.Remove(childGuid); + } + + _parentAssetIsAdd.Remove(guid); + return root; + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs.meta new file mode 100644 index 00000000..8f80795e --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/ResourceReferenceInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40f81b3f7b523ee46ab72ec79c7a61af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs b/UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs new file mode 100644 index 00000000..ddc0f795 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace TEngine.Editor +{ + internal sealed class SortConfig + { + public static readonly Dictionary SortTypeChangeByNameHandler = new Dictionary + { + { SortType.None, SortType.AscByName }, + { SortType.AscByName, SortType.DescByName }, + { SortType.DescByName, SortType.AscByName } + }; + + public static readonly Dictionary SortTypeChangeByPathHandler = new Dictionary + { + { SortType.None, SortType.AscByPath }, + { SortType.AscByPath, SortType.DescByPath }, + { SortType.DescByPath, SortType.AscByPath } + }; + + public static readonly Dictionary SortTypeGroup = new Dictionary + { + { SortType.None, 0 }, + { SortType.AscByPath, 1 }, + { SortType.DescByPath, 1 }, + { SortType.AscByName, 2 }, + { SortType.DescByName, 2 } + }; + + public const short TYPE_BY_NAME_GROUP = 2; + public const short TYPE_BY_PATH_GROUP = 1; + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs.meta new file mode 100644 index 00000000..89d9a45a --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/SortConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d7b92d1055376e4dbabd77de5a0e728 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs b/UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs new file mode 100644 index 00000000..830fda5c --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; + +namespace TEngine.Editor +{ + internal sealed class SortHelper + { + public delegate int SortCompare(string lString, string rString); + + public static readonly HashSet SortedGuid = new HashSet(); + public static readonly Dictionary SortedAsset = new Dictionary(); + public static SortType CurSortType = SortType.None; + public static SortType PathType = SortType.None; + public static SortType NameType = SortType.None; + + public static readonly Dictionary CompareFunction = new Dictionary + { + { SortType.AscByPath, CompareWithPath }, + { SortType.DescByPath, CompareWithPathDesc }, + { SortType.AscByName, CompareWithName }, + { SortType.DescByName, CompareWithNameDesc } + }; + + public static void Init() + { + SortedGuid.Clear(); + SortedAsset.Clear(); + } + + public static void ChangeSortType(short sortGroup, Dictionary handler, ref SortType recoverType) + { + if (SortConfig.SortTypeGroup[CurSortType] == sortGroup) + { + CurSortType = handler[CurSortType]; + } + else + { + CurSortType = recoverType; + if (CurSortType == SortType.None) CurSortType = handler[CurSortType]; + } + + recoverType = CurSortType; + } + + public static void SortByName() => ChangeSortType(SortConfig.TYPE_BY_NAME_GROUP, SortConfig.SortTypeChangeByNameHandler, ref NameType); + + public static void SortByPath() => ChangeSortType(SortConfig.TYPE_BY_PATH_GROUP, SortConfig.SortTypeChangeByPathHandler, ref PathType); + + public static void SortChild(ReferenceFinderData.AssetDescription data) + { + if (data == null) return; + if (SortedAsset.ContainsKey(data.path)) + { + if (SortedAsset[data.path] == CurSortType) return; + SortType oldSortType = SortedAsset[data.path]; + if (SortConfig.SortTypeGroup[oldSortType] == SortConfig.SortTypeGroup[CurSortType]) + { + FastSort(data.dependencies); + FastSort(data.references); + } + else + { + NormalSort(data.dependencies); + NormalSort(data.references); + } + + SortedAsset[data.path] = CurSortType; + } + else + { + NormalSort(data.dependencies); + NormalSort(data.references); + SortedAsset.Add(data.path, CurSortType); + } + } + + public static void NormalSort(List strList) + { + SortCompare curCompare = CompareFunction[CurSortType]; + strList.Sort((l, r) => curCompare(l, r)); + } + + public static void FastSort(List strList) + { + int i = 0; + int j = strList.Count - 1; + while (i < j) + { + (strList[i], strList[j]) = (strList[j], strList[i]); + i++; + j--; + } + } + + public static int CompareWithName(string lString, string rString) + { + Dictionary asset = ResourceReferenceInfo.Data.assetDict; + return string.Compare(asset[lString].name, asset[rString].name, StringComparison.Ordinal); + } + + public static int CompareWithNameDesc(string lString, string rString) => 0 - CompareWithName(lString, rString); + + public static int CompareWithPath(string lString, string rString) + { + Dictionary asset = ResourceReferenceInfo.Data.assetDict; + return string.Compare(asset[lString].path, asset[rString].path, StringComparison.Ordinal); + } + + public static int CompareWithPathDesc(string lString, string rString) => 0 - CompareWithPath(lString, rString); + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs.meta new file mode 100644 index 00000000..7cdf38b7 --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/SortHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2a386b95a697364abaac53ed1cac83e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Editor/ReferenceFinder/SortType.cs b/UnityProject/Assets/Editor/ReferenceFinder/SortType.cs new file mode 100644 index 00000000..4a48219c --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/SortType.cs @@ -0,0 +1,11 @@ +namespace TEngine.Editor +{ + public enum SortType + { + None, + AscByName, + DescByName, + AscByPath, + DescByPath + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Editor/ReferenceFinder/SortType.cs.meta b/UnityProject/Assets/Editor/ReferenceFinder/SortType.cs.meta new file mode 100644 index 00000000..8bd22a2a --- /dev/null +++ b/UnityProject/Assets/Editor/ReferenceFinder/SortType.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 86a3dbcbc8c8c7145bab4093920461da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: