using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading.Tasks; using UnityEditor; using Object = UnityEngine.Object; namespace AssetDependencyGraph { public static class FileExtensionHelper { public static string GetTypeByExtension(string ext) { switch (ext.ToLowerInvariant()) { case ".a": case ".dll": case ".so": case ".exe": case ".dynlib": return "Executable"; case ".asmdef": case ".asmref": return "UnityAssembly"; case ".cs": case ".lua": case ".js": case ".ts": case ".java": case ".h": case ".cpp": case ".cxx": case ".mm": case ".py": case ".bat": case ".jar": case ".arr": case ".jslib": return "SourceFile"; case ".gradle": return "MakeFile"; case ".dat": case ".data": return "DatFile"; case ".mp3": case ".ogg": case ".wav": return "AudioClip"; case ".mp4": case ".webm": return "VideoClip"; case ".mat": return "Material"; case ".rendertexture": case ".dds": case ".exr": case ".hdr": case ".png": case ".jpg": case ".gif": case ".psd": case ".bmp": case ".tiff": case ".tga": case ".gradient": case ".spriteatlas": return "Texture"; case ".obj": case ".fbx": case ".mesh": return "Mesh"; case ".shader": case ".surfshader": case ".shadergraph": return "Shader"; case ".compute": return "ComputeShader"; case ".hlsl": case ".cginc": case ".shadersubgraph": return "ShaderHeader"; case ".otf": case ".ttf": return "Font"; case ".byte": case ".bytes": case ".bin": return "Binary"; case ".txt": case ".md": case ".chm": case ".yml": case ".url": case ".json": case ".json5": case ".xml": case ".uxml": case ".nson": case ".config": case ".pdf": return "TextFile"; case ".xlsx": case ".xls": return "Excel"; default: return "UnknowFileType"; } } public static bool IsPackage(string ext) { switch (ext.ToLowerInvariant()) { case ".prefab": case ".unity": return true; default: return false; } } public static bool NeedAnalyzeDepend(string ext) { switch (ext.ToLowerInvariant()) { case ".prefab": case ".unity": case ".asset": return true; default: return false; } } public static bool Exclude(string path) => path.EndsWith(".meta") || path.EndsWith(".unitypackage") || path.EndsWith(".preset") || path.EndsWith(".backup") || path.EndsWith(".tmp") || path.EndsWith(".editor") || path.EndsWith(".zip") || path.EndsWith(".scenetemplate"); } public interface IDependencyAnalysis { void Analyze(string path, ConcurrentDictionary result); public (AssetIdentify id, AssetNode node) GetOrCreateFolderNode(string path, ConcurrentDictionary result) { AssetIdentify k = null; foreach (var item in result.Keys) { if (item.Path == path) { k = item; } } if (k == null) { k = new AssetIdentify() { Path = path, AssetType = "Folder", Guid = null, Md5 = null }; result[k] = new FolderNode() { Self = k, AssetType = "Folder", }; } return (k, result[k]); } public (AssetIdentify id, AssetNode node) GetOrCreateAssetNode(string path, ConcurrentDictionary result) { AssetIdentify k = null; foreach (var item in result.Keys) { if (item.Path == path) { k = item; } } if (k == null) { k = new AssetIdentify() { Path = path, Guid = null, //Md5 = Utils.Md5(path) }; if (FileExtensionHelper.IsPackage(Path.GetExtension(path))) { result[k] = new PackageNode() { Self = k, }; } else { result[k] = new AssetNode() { Self = k, }; } } return (k, result[k]); } } public class UnityDependencyAnalysis1 : IDependencyAnalysis { HashSet processed = new(); public void Analyze(string path, ConcurrentDictionary result) { if (!processed.Add(path)) { return; } var dependencyAnalysis = (this as IDependencyAnalysis); var kv = dependencyAnalysis.GetOrCreateAssetNode(path, result); var relatePath = path.ToUnityRelatePath(); kv.id.Guid = AssetDatabase.AssetPathToGUID(relatePath); var selfNode = kv.node; if (FileExtensionHelper.GetTypeByExtension(Path.GetExtension(path)) is string assetType && assetType != null) { kv.id.AssetType = assetType; selfNode.AssetType = assetType; } else { if (relatePath.EndsWith("Rock_01_Prefab.prefab")) { Console.WriteLine(); } Object mainObject = AssetDatabase.LoadMainAssetAtPath(relatePath); if (mainObject != null) { var prefabType = PrefabUtility.GetPrefabAssetType(mainObject); if (prefabType != PrefabAssetType.NotAPrefab) { selfNode.AssetType = prefabType.ToString(); } else { selfNode.AssetType = mainObject.GetType().Name; } kv.id.AssetType = selfNode.AssetType; string[] dependencies = AssetDatabase.GetDependencies(relatePath, false); for (int i = 0; i < dependencies.Length; i++) { var dep = dependencies[i].ToUnityRelatePath(); var depkv = dependencyAnalysis.GetOrCreateAssetNode(dep, result); depkv.node.Dependent.Add(selfNode.Self); selfNode.Dependencies.Add(depkv.id); Analyze(dep, result); } } else { UnityEngine.Debug.LogWarning("unknown type:" + path); } } } } public class FolderDependencyAnalysis : IDependencyAnalysis { public void Analyze(string path, ConcurrentDictionary result) { var dependencyAnalysis = (this as IDependencyAnalysis); var k = dependencyAnalysis.GetOrCreateFolderNode(path, result); foreach (string file in Directory.EnumerateFiles(path)) { if (FileExtensionHelper.Exclude(file)) { continue; } var p = file.ToUnityRelatePath().ToUniversalPath(); var selfNode = result[k.id]; var kv = dependencyAnalysis.GetOrCreateAssetNode(p, result); kv.node.Dependent.Add(selfNode.Self); selfNode.Dependencies.Add(kv.id); } foreach (string directory in Directory.EnumerateDirectories(path)) { var p = directory.ToUnityRelatePath().ToUniversalPath(); var selfNode = result[k.id]; var kv = dependencyAnalysis.GetOrCreateFolderNode(p, result); kv.node.Dependent.Add(selfNode.Self); selfNode.Dependencies.Add(kv.id); } } } public class UnityDependencyAnalysis2 : IDependencyAnalysis { UnityLmdb unityLmdb; HashSet processed = new(); public UnityDependencyAnalysis2() { unityLmdb = new UnityLmdb(); unityLmdb.ResolveGuidPath(); } public void Analyze(string path, ConcurrentDictionary result) { if (!processed.Add(path)) { return; } var dependencyAnalysis = (this as IDependencyAnalysis); var kv = dependencyAnalysis.GetOrCreateAssetNode(path, result); var relatePath = path.ToUnityRelatePath(); kv.id.Guid = unityLmdb.GetGuidByPath(relatePath); var selfNode = kv.node; var ext = Path.GetExtension(path); var assetType = FileExtensionHelper.GetTypeByExtension(ext); selfNode.AssetType = assetType; kv.id.AssetType = assetType; if (FileExtensionHelper.NeedAnalyzeDepend(ext)) { var dependencies = UnityFileApi.DependencyTool.GetDependencies(path); for (int i = 0; i < dependencies.Count; i++) { var dep = dependencies[i].ToUnityRelatePath(); var depkv = dependencyAnalysis.GetOrCreateAssetNode(dep, result); depkv.node.Dependent.Add(selfNode.Self); selfNode.Dependencies.Add(depkv.id); Analyze(dep, result); } } } } internal class DependencyAnalyzer { private Dictionary, IDependencyAnalysis> dependencyAnalysisDic = new(); private ConcurrentDictionary assetIdentify2AssetNodeDic = new(); private List allPath = new(); public DependencyAnalyzer() { //dependencyAnalysisDic.Add(new Predicate(path => !Directory.Exists(path)), new UnityDependencyAnalysis1()); //dependencyAnalysisDic.Add(new Predicate(path => !Directory.Exists(path)), new UnityDependencyAnalysis2()); dependencyAnalysisDic.Add(new Predicate(path => Directory.Exists(path)), new FolderDependencyAnalysis()); } private void Visivt(string path) { path = path.ToUniversalPath(); if (FileExtensionHelper.Exclude(path)) { return; } allPath.Add(path); } public void Analyze(string rootFolder) { Stopwatch sw = Stopwatch.StartNew(); sw.Start(); Utils.TraverseDirectory(rootFolder, Visivt, -1); Parallel.ForEach(allPath, (path) => { path = path.ToUnityRelatePath(); foreach (var item in dependencyAnalysisDic) { if (item.Key(path)) { item.Value.Analyze(path, assetIdentify2AssetNodeDic); } } }); sw.Stop(); UnityEngine.Debug.Log($"分析引用耗时:{sw.ElapsedMilliseconds / 1000f}s"); AssetDependencyGraphDB db = new AssetDependencyGraphDB("", "", "localhost"); sw.Restart(); db.Clean(); Parallel.ForEach(assetIdentify2AssetNodeDic, item => { db.UpdateOrInsert(item.Value); }); sw.Stop(); UnityEngine.Debug.Log($"更新数据库:{sw.ElapsedMilliseconds / 1000f}s"); } } }