基于有向图的循环依赖检查
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
public class AssetBundleCycleChecker : EditorWindow
{
[MenuItem("Tools/AssetBundle/检查循环依赖")]
public static void CheckCycles()
{
// 1. 获取项目中所有已分配AB名的资源
string[] allAssetPaths = AssetDatabase.GetAllAssetPaths();
var abToDependencies = new Dictionary<string, HashSet<string>>();
foreach (var path in allAssetPaths)
{
string abName = AssetImporter.GetAtPath(path).assetBundleName;
if (string.IsNullOrEmpty(abName)) continue;
if (!abToDependencies.ContainsKey(abName))
abToDependencies[abName] = new HashSet<string>();
// 2. 获取该资源的所有直接依赖
string[] deps = AssetDatabase.GetDependencies(path, false); // false表示不递归,只取直接依赖
foreach (var dep in deps)
{
string depAbName = AssetImporter.GetAtPath(dep).assetBundleName;
// 记录AB包级别的依赖关系,排除自身依赖和未打AB的依赖
if (!string.IsNullOrEmpty(depAbName) && depAbName != abName)
{
abToDependencies[abName].Add(depAbName);
}
}
}
// 3. 使用DFS检测环路,记录唯一环
var visited = new HashSet<string>();
var stack = new HashSet<string>();
var currentPath = new List<string>();
var uniqueCycles = new HashSet<string>(); // 用规范化环字符串去重
foreach (var ab in abToDependencies.Keys)
{
DetectCycle(ab, abToDependencies, visited, stack, currentPath, uniqueCycles);
}
// 4. 输出结果
if (uniqueCycles.Count > 0)
{
Debug.LogError($"<color=red>发现 {uniqueCycles.Count} 个AssetBundle循环依赖环!请检查以下环:\n" + string.Join("\n", uniqueCycles) + "</color>");
}
else
{
Debug.Log("<color=green>AssetBundle依赖检查通过,未发现循环依赖。</color>");
}
}
private static void DetectCycle(string current, Dictionary<string, HashSet<string>> graph,
HashSet<string> visited, HashSet<string> stack, List<string> currentPath, HashSet<string> uniqueCycles)
{
if (visited.Contains(current))
return;
visited.Add(current);
stack.Add(current);
currentPath.Add(current);
if (graph.ContainsKey(current))
{
foreach (var neighbor in graph[current])
{
if (!visited.Contains(neighbor))
{
DetectCycle(neighbor, graph, visited, stack, currentPath, uniqueCycles);
}
else if (stack.Contains(neighbor))
{
// 找到环,从currentPath中提取环段
int startIndex = currentPath.IndexOf(neighbor);
var cycle = currentPath.GetRange(startIndex, currentPath.Count - startIndex);
cycle.Add(neighbor); // 闭合环:A→B→C→A
// 规范化后去重
string normalizedCycle = NormalizeCycle(cycle);
uniqueCycles.Add(normalizedCycle);
}
}
}
stack.Remove(current);
currentPath.RemoveAt(currentPath.Count - 1);
}
/// <summary>
/// 将环旋转到最小元素开头,保证同一环的不同起点表示一致
/// </summary>
private static string NormalizeCycle(List<string> cycle)
{
// cycle 最后一个元素和第一个元素相同,闭合用
int cycleLength = cycle.Count - 1;
if (cycleLength <= 0) return string.Join(" → ", cycle);
// 找到最小元素位置
int minIndex = 0;
for (int i = 1; i < cycleLength; i++)
{
if (string.Compare(cycle[i], cycle[minIndex], StringComparison.Ordinal) < 0)
minIndex = i;
}
// 从最小元素开始旋转
var ordered = new List<string>();
for (int i = 0; i < cycleLength; i++)
{
ordered.Add(cycle[(minIndex + i) % cycleLength]);
}
ordered.Add(ordered[0]); // 闭合
return string.Join(" → ", ordered);
}
}
https://blog.csdn.net/qq_14914623

浙公网安备 33010602011771号