Last active
June 5, 2024 08:14
-
-
Save RevenantX/41193128d413fc7f7ba9e7e06bfd43ca to your computer and use it in GitHub Desktop.
Find asset usages in prefabs and scenes (Unity3d)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Globalization; | |
using System.IO; | |
using System.Text; | |
using System.Threading; | |
using UnityEditor; | |
using UnityEngine; | |
using Debug = UnityEngine.Debug; | |
namespace Code.Editor | |
{ | |
public class FindUsages : EditorWindow | |
{ | |
private struct SearchResult | |
{ | |
public string Name; | |
public string Path; | |
} | |
private class DatabaseRefreshListener : AssetPostprocessor | |
{ | |
private static void OnPostprocessAllAssets( | |
string[] importedAssets, | |
string[] deletedAssets, | |
string[] movedAssets, | |
string[] movedFromAssetPaths) | |
{ | |
NeedCacheRefresh = true; | |
} | |
} | |
private static readonly List<SearchResult> PrefabNames = new List<SearchResult>(); | |
private static readonly List<SearchResult> SceneNames = new List<SearchResult>(); | |
private static readonly List<string> FilesWithAsset = new List<string>(); | |
private const string MenuItemName = "Assets/- Find usages"; | |
private const string YAMLHeader = "%YAML"; | |
private static readonly string[] SearchTypes = { ".prefab", ".asset", ".unity" }; | |
private static readonly Encoding AsciiEncoding = Encoding.ASCII; | |
private static bool NeedCacheRefresh = true; | |
private static string[] FilesCache; | |
private static long ProcessedFiles; | |
private static int ThreadsActive; | |
private static byte[] TargetAsciiGuid; | |
private static byte[] TargetHexGuid; | |
private Vector2 _scrollPosition; | |
private GUIStyle _buttonStyle; | |
[MenuItem(MenuItemName, true, 10)] | |
private static bool MenuItemCheck() | |
{ | |
if (Selection.activeObject == null) | |
return false; | |
//skip directories | |
return !Directory.Exists(AssetDatabase.GetAssetPath(Selection.activeObject)); | |
} | |
private static void SearchThread(object _) | |
{ | |
byte[] binaryData = new byte[1024]; | |
while (true) | |
{ | |
long i = Interlocked.Increment(ref ProcessedFiles) - 1; | |
if (i >= FilesCache.Length) | |
break; | |
using (var fileStream = new FileStream(FilesCache[i], FileMode.Open)) | |
{ | |
int fileLength = (int)fileStream.Length; | |
if (binaryData.Length < fileLength) | |
binaryData = new byte[fileLength]; | |
int readCount = fileStream.Read(binaryData, 0, fileLength); | |
if (readCount < YAMLHeader.Length) | |
continue; | |
var byteSequence = AsciiEncoding.GetString(binaryData, 0, YAMLHeader.Length) == YAMLHeader | |
? TargetAsciiGuid | |
: TargetHexGuid; | |
for (int j = YAMLHeader.Length; j < readCount - byteSequence.Length; j++) | |
{ | |
int k; | |
for (k = 0; k < byteSequence.Length; k++) | |
{ | |
if (binaryData[j + k] != byteSequence[k]) | |
break; | |
} | |
if (k == byteSequence.Length) | |
{ | |
lock (FilesWithAsset) | |
FilesWithAsset.Add(FilesCache[i]); | |
break; | |
} | |
} | |
} | |
} | |
Interlocked.Decrement(ref ThreadsActive); | |
} | |
[MenuItem(MenuItemName, false, 30)] | |
static void MenuItem() | |
{ | |
string guid = Selection.assetGUIDs[0]; | |
TargetAsciiGuid = AsciiEncoding.GetBytes($"guid: {guid}"); | |
TargetHexGuid = new byte[guid.Length / 2]; | |
for (int i = 0; i < guid.Length; i += 2) | |
TargetHexGuid[i/2] = byte.Parse(new string(new [] { guid[i+1], guid[i] }), NumberStyles.HexNumber); | |
if (NeedCacheRefresh || FilesCache == null) | |
{ | |
var filesList = new List<string>(); | |
foreach (string file in Directory.EnumerateFiles("Assets", "*.*", SearchOption.AllDirectories)) | |
{ | |
for (int i = 0; i < SearchTypes.Length; i++) | |
{ | |
if (file.EndsWith(SearchTypes[i])) | |
{ | |
filesList.Add(file); | |
break; | |
} | |
} | |
} | |
FilesCache = filesList.ToArray(); | |
} | |
ProcessedFiles = 0; | |
FilesWithAsset.Clear(); | |
PrefabNames.Clear(); | |
SceneNames.Clear(); | |
int processorCount = Environment.ProcessorCount; | |
ThreadsActive = processorCount; | |
Debug.Log($"FindUsages. GUID: {guid} Cores: {processorCount}, Files: {FilesCache.Length}"); | |
for (int i = 0; i < processorCount; i++) | |
ThreadPool.QueueUserWorkItem(SearchThread); | |
var sw = new Stopwatch(); | |
sw.Start(); | |
while (ThreadsActive > 0) | |
{ | |
EditorUtility.DisplayProgressBar("Find usages", $"File: {Selection.activeObject.name}", (float)ProcessedFiles / FilesCache.Length); | |
Thread.Sleep(1); | |
} | |
EditorUtility.ClearProgressBar(); | |
sw.Stop(); | |
Debug.Log($"Search time: {sw.ElapsedMilliseconds} ms"); | |
for (int i = 0; i < FilesWithAsset.Count; i++) | |
{ | |
var prefabPath = FilesWithAsset[i]; | |
var strWihtouPath = prefabPath.Substring(prefabPath.LastIndexOf(Path.DirectorySeparatorChar) + 1); | |
var targetList = prefabPath.EndsWith(".unity") ? SceneNames : PrefabNames; | |
targetList.Add(new SearchResult | |
{ | |
Name = strWihtouPath.Substring(0, strWihtouPath.IndexOf(".", StringComparison.InvariantCulture)) + " ", | |
Path = prefabPath | |
}); | |
} | |
if (PrefabNames.Count > 0 || SceneNames.Count > 0) | |
GetWindow<FindUsages>().Show(); | |
else | |
EditorUtility.DisplayDialog("Find usages", "Nothing found", "OK"); | |
} | |
private void OnGUI() | |
{ | |
_buttonStyle ??= new(GUI.skin.button) { alignment = TextAnchor.MiddleLeft }; | |
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); | |
GUILayout.Label("Prefabs"); | |
DrawSearchResults(PrefabNames); | |
GUILayout.Label("Scenes"); | |
DrawSearchResults(SceneNames); | |
EditorGUILayout.EndScrollView(); | |
return; | |
void DrawSearchResults(List<SearchResult> searchResults) | |
{ | |
for (int i = 0; i < searchResults.Count; i++) | |
{ | |
if (GUILayout.Button($"{searchResults[i].Name} ({searchResults[i].Path})", _buttonStyle, GUILayout.Height(20), GUILayout.ExpandWidth(true))) | |
{ | |
Selection.activeObject = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(searchResults[i].Path); | |
EditorGUIUtility.PingObject(Selection.activeObject); | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Very cool script!
Little addition: you could add
EditorGUIUtility.PingObject(Selection.activeObject);
after line 171 for even more convenience :)