2025-04-15 16:49:22 +08:00

412 lines
13 KiB
C#

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<AssetIdentify, AssetNode> result);
public (AssetIdentify id, AssetNode node) GetOrCreateFolderNode(string path, ConcurrentDictionary<AssetIdentify, AssetNode> 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<AssetIdentify, AssetNode> 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<string> processed = new();
public void Analyze(string path, ConcurrentDictionary<AssetIdentify, AssetNode> 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<AssetIdentify, AssetNode> 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<string> processed = new();
public UnityDependencyAnalysis2()
{
unityLmdb = new UnityLmdb();
unityLmdb.ResolveGuidPath();
}
public void Analyze(string path, ConcurrentDictionary<AssetIdentify, AssetNode> 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<Predicate<string>, IDependencyAnalysis> dependencyAnalysisDic = new();
private ConcurrentDictionary<AssetIdentify, AssetNode> assetIdentify2AssetNodeDic = new();
private List<string> allPath = new();
public DependencyAnalyzer()
{
//dependencyAnalysisDic.Add(new Predicate<string>(path => !Directory.Exists(path)), new UnityDependencyAnalysis1());
//dependencyAnalysisDic.Add(new Predicate<string>(path => !Directory.Exists(path)), new UnityDependencyAnalysis2());
dependencyAnalysisDic.Add(new Predicate<string>(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");
}
}
}