基于有向图的循环依赖检查

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);
    }
}

  

posted @ 2026-07-25 20:42  钢与铁  阅读(2)  评论(0)    收藏  举报