/// <summary> /// Does list already have an entry similar to the one supplied? /// Similar means same AssetPath, same IndentLevel, /// same asset that has higher indent level above it (parent). /// </summary> /// <param name="list"></param> /// <param name="check"></param> /// <param name="assetParentOfCheck"></param> /// <returns></returns> static bool Contains(List <AssetUserFlattened> list, AssetUserFlattened check, string assetParentOfCheck) { for (int n = list.Count - 1; n >= 0; --n) { if (list[n].IndentLevel < check.IndentLevel) { // we've gone past the indent level we need to check for break; } if (list[n].AssetPath.Equals(check.AssetPath, StringComparison.OrdinalIgnoreCase) && list[n].IndentLevel == check.IndentLevel && GetAssetUsageParent(list, n) == assetParentOfCheck) { return(true); } } return(false); }
// ================================================================================== static void Create(AssetDependencies data, Queue <string> openSet, bool debugLog = false, bool recursiveGetDependencies = false) { var assetDependencies = data.GetAssetDependencies(); assetDependencies.Clear(); // ------------------------------------------------------------- // assets that we've finished inspecting var closedSet = new HashSet <string>(); // ------------------------------------------------------------- while (openSet.Count > 0) { var newAssetToInspect = openSet.Dequeue(); if (string.IsNullOrEmpty(newAssetToInspect)) { continue; } closedSet.Add(newAssetToInspect); string[] foundDependencies = GetDependencies(newAssetToInspect, recursiveGetDependencies); if (foundDependencies.Length <= 0) { // this asset doesn't use others continue; } StringBuilder stringBuilder = null; if (debugLog) { stringBuilder = new StringBuilder(); stringBuilder.Append("Assets used by "); stringBuilder.Append(newAssetToInspect); stringBuilder.Append(" ("); stringBuilder.Append(foundDependencies.Length.ToString()); stringBuilder.Append("):\n"); } DependencyEntry assetsUsed; if (assetDependencies.ContainsKey(newAssetToInspect)) { assetsUsed = assetDependencies[newAssetToInspect]; } else { assetsUsed = new DependencyEntry(); assetDependencies.Add(newAssetToInspect, assetsUsed); } // ------------------------------------------------------------- // we're looping through the assets that newAssetToInspect uses // so add them to the `DependencyEntry.Uses` list for (int n = 0, len = foundDependencies.Length; n < len; ++n) { if (string.IsNullOrEmpty(foundDependencies[n])) { continue; } if (foundDependencies[n].Equals(newAssetToInspect, StringComparison.Ordinal)) { // for some reason, the API reports assets to be using their own selves // so skip it when they have the same value continue; } if (debugLog) { stringBuilder.Append(n.ToString()); stringBuilder.Append(". "); stringBuilder.Append(foundDependencies[n]); stringBuilder.Append("\n"); } // -------------------------------------------------------- if (!assetsUsed.Uses.Contains(foundDependencies[n])) { assetsUsed.Uses.Add(foundDependencies[n]); } // -------------------------------------------------------- // now record that dependency as being used by `newAssetToInspect` DependencyEntry assetsUsedByEdit; if (assetDependencies.ContainsKey(foundDependencies[n])) { assetsUsedByEdit = assetDependencies[foundDependencies[n]]; } else { assetsUsedByEdit = new DependencyEntry(); assetDependencies.Add(foundDependencies[n], assetsUsedByEdit); } if (!assetsUsedByEdit.Users.Contains(newAssetToInspect)) { assetsUsedByEdit.Users.Add(newAssetToInspect); } // -------------------------------------------------------- if (closedSet.Contains(foundDependencies[n])) { // this asset was already searched through. skip it. continue; } if (!openSet.Contains(foundDependencies[n])) { // add to list of assets to search through openSet.Enqueue(foundDependencies[n]); } } if (debugLog) { Debug.Log(stringBuilder.ToString()); } } // ==================================================================== // Create the AssetUserFlattened List // This is the recursively calculated users of the asset. // Indent Levels signify which asset uses which. List <AssetUserFlattened> openFlattenedSet = new List <AssetUserFlattened>(); foreach (var pair in assetDependencies) { var assetUsers = pair.Value.Users; if (assetUsers.Count == 0) { continue; } var usersFlattened = new List <AssetUserFlattened>(assetUsers.Count); pair.Value.UsersFlattened = usersFlattened; openFlattenedSet.Clear(); for (int n = 0, len = assetUsers.Count; n < len; ++n) { AssetUserFlattened newEntry = new AssetUserFlattened(assetUsers[n], 1 #if BRT_ASSET_DEPENDENCY_DEBUG , "[initial]" #endif ); // put them in reverse since always take from the end of the openFlattenedSet openFlattenedSet.Insert(0, newEntry); } var endlessLoopGuard = 0; while (openFlattenedSet.Count > 0) { ++endlessLoopGuard; if (endlessLoopGuard >= 9999) // that ought to be enough for real-world usage (I hope) { break; } var userAsset = openFlattenedSet[openFlattenedSet.Count - 1]; openFlattenedSet.RemoveAt(openFlattenedSet.Count - 1); var assetUsed = GetAssetUsageParent(usersFlattened, usersFlattened.Count - 1); if (Contains(usersFlattened, userAsset, assetUsed)) { // `userAsset` is already added previously in this list in same indent level and same parent // can't add this because it would be a duplicate entry continue; } usersFlattened.Add(userAsset); if (userAsset.AssetPath.Equals(pair.Key, StringComparison.OrdinalIgnoreCase)) { // no need to go inside this further because this is // already what we're primarily going inside currently userAsset.CyclicDependency = true; #if BRT_ASSET_DEPENDENCY_DEBUG userAsset.DebugInfo = "[selected cyclic]"; #endif usersFlattened[usersFlattened.Count - 1] = userAsset; continue; } var userAssetIsFbxUsingMaterial = false; string materialAssignedAsDefault = null; if (usersFlattened.Count >= 2) { // note: usersFlattened[usersFlattened.Count - 1] is userAsset // var noNeedToAddUsersOfUser = false; if (userAsset.IndentLevel > 1) { var currentIndentLevel = userAsset.IndentLevel - 1; if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") && userAsset.AssetPath.Contains("AbilityEntry Settings.asset")) { Debug.LogFormat( "=========================================================\nChecking for cyclic dependencies for {0} at index {1}\nLooking for Indent Level {2}", userAsset.AssetPath, (usersFlattened.Count - 1).ToString(), currentIndentLevel); } // start just before our own position in the list, then go backwards for (int traceN = usersFlattened.Count - 2; traceN >= 0; --traceN) { if (usersFlattened[traceN].IndentLevel > currentIndentLevel) { // not the indent level we're looking for if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") && userAsset.AssetPath.Contains("AbilityEntry Settings.asset")) { Debug.LogFormat("Skipped element {0} since its indent level is: {1} (looking for {2})", traceN, usersFlattened[traceN].IndentLevel.ToString(), currentIndentLevel); } continue; } // at this point, the current element is above our indent level, meaning it's what the asset uses if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") && userAsset.AssetPath.Contains("AbilityEntry Settings.asset")) { Debug.LogFormat("Now at element {0}: {1} at indent level {2}. Checking if same as {3}", traceN, usersFlattened[traceN].AssetPath, usersFlattened[traceN].IndentLevel.ToString(), userAsset.AssetPath); } currentIndentLevel = usersFlattened[traceN].IndentLevel - 1; if (usersFlattened[traceN].AssetPath .Equals(userAsset.AssetPath, StringComparison.OrdinalIgnoreCase)) { // this is an asset we've been in earlier. // should not add users of this asset, because that includes the // one we've already been in, and it would be an endless recursion userAsset.CyclicDependency = true; #if BRT_ASSET_DEPENDENCY_DEBUG userAsset.DebugInfo = "[earlier indent cyclic]"; #endif usersFlattened[usersFlattened.Count - 1] = userAsset; noNeedToAddUsersOfUser = true; if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") && userAsset.AssetPath.Contains("AbilityEntry Settings.asset")) { Debug.LogFormat( "Element {0} is found to be same so latest entry is marked as cyclic dependency. check is done.", traceN); } break; } if (usersFlattened[traceN].IndentLevel <= 1) { // went through the last asset in this usage chain, no need to look further if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") && userAsset.AssetPath.Contains("AbilityEntry Settings.asset")) { Debug.LogFormat("Element {0} has indent level of {1} so aborting the check", traceN, usersFlattened[traceN].IndentLevel.ToString()); } break; } } if (debugLog && pair.Key.Contains("Runtime-Only DLL/TextMeshPro.dll") && userAsset.AssetPath.Contains("AbilityEntry Settings.asset")) { Debug.LogFormat( "=========================================================\nDone checking for cyclic dependencies for {0} at index {1}", userAsset.AssetPath, (usersFlattened.Count - 1).ToString()); } } if (!noNeedToAddUsersOfUser) { for (int n = usersFlattened.Count - 2; n >= 0; --n) { if (usersFlattened[n].AssetPath == userAsset.AssetPath) { // users of this user has already been shown in earlier entries // although from a different usage chain #if BRT_ASSET_DEPENDENCY_DEBUG userAsset.DebugInfo += " [already visited]"; #endif // update the entry usersFlattened[usersFlattened.Count - 1] = userAsset; noNeedToAddUsersOfUser = true; break; } } } int idxOfPrevious; if (!noNeedToAddUsersOfUser && IsFileTypeBeforeAnother(usersFlattened, usersFlattened.Count - 1, ".mat", ".fbx", out idxOfPrevious)) { // a Material being used by an FBX is only as a default, initial value for that FBX. // after the FBX is instantiated, we have no guarantee that Material is still being // used, so additional checks are needed // check the assets that use the FBX file, meaning check the places where that FBX // is instantiated or referenced, which are either prefabs, scenes, or scriptable objects. // // do those prefabs/scenes/scriptable objects really use that default material, or has it // been overriden? userAssetIsFbxUsingMaterial = true; materialAssignedAsDefault = usersFlattened[idxOfPrevious].AssetPath; var fbxUsingTheMaterialAsDefault = userAsset.AssetPath; if (debugLog) { Debug.LogFormat("Detected fbx using a material.\n" + "Material:\n{0}\n" + "Fbx:\n{1}\n" + "Checking the assets that use the fbx if they really also use the material...", materialAssignedAsDefault, fbxUsingTheMaterialAsDefault); } var isMaterialReallyBeingUsed = AreInstancesOfFbxUsingMaterial(fbxUsingTheMaterialAsDefault, materialAssignedAsDefault, assetDependencies, debugLog); if (!isMaterialReallyBeingUsed) { #if BRT_ASSET_DEPENDENCY_DEBUG userAsset.DebugInfo += " [using as default for material]"; #endif usersFlattened[usersFlattened.Count - 1] = userAsset; noNeedToAddUsersOfUser = true; } } if (!noNeedToAddUsersOfUser && userAsset.AssetPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) { // something being used by a .cs script file is only as a default, initial value for that script // after the script is instantiated, we have no guarantee that something is still being // used, so it doesn't make sense to go further #if BRT_ASSET_DEPENDENCY_DEBUG userAsset.DebugInfo += " [using as default for script]"; #endif usersFlattened[usersFlattened.Count - 1] = userAsset; noNeedToAddUsersOfUser = true; } if (noNeedToAddUsersOfUser) { continue; } } if (assetDependencies.ContainsKey(userAsset.AssetPath)) { var usersOfUser = assetDependencies[userAsset.AssetPath].Users; for (int n = 0, len = usersOfUser.Count; n < len; ++n) { AssetUserFlattened newEntry = new AssetUserFlattened(usersOfUser[n], userAsset.IndentLevel + 1); if (userAssetIsFbxUsingMaterial) { if (!IsInstanceOfFbxUsingMaterial(userAsset.AssetPath, usersOfUser[n], materialAssignedAsDefault, assetDependencies, debugLog)) { continue; } if (usersOfUser[n].EndsWith(".unity", StringComparison.OrdinalIgnoreCase)) { // userAsset is an fbx which uses a material // newEntry is a scene that uses (an instance of) that fbx // BUT we are not sure that newEntry.IndentLevel = userAsset.IndentLevel; if (Contains(usersFlattened, newEntry, userAsset.AssetPath)) { // already added continue; } } #if BRT_ASSET_DEPENDENCY_DEBUG newEntry.DebugInfo = " [uses fbx which uses material]"; #endif } openFlattenedSet.Add(newEntry); } } } } if (debugLog) { Debug.Log("==================================================="); foreach (var pair in assetDependencies) { var stringBuilder = new StringBuilder(); stringBuilder.Append(pair.Key); stringBuilder.Append(" used by:\n"); for (int n = 0, len = pair.Value.Users.Count; n < len; ++n) { stringBuilder.Append(n.ToString()); stringBuilder.Append(". "); stringBuilder.Append(pair.Value.Users[n]); stringBuilder.Append("\n"); } Debug.Log(stringBuilder.ToString()); } } }