using System; using System.Collections.Generic; using System.Linq; using UnityEditor.AddressableAssets.Build.DataBuilders; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEditor.Build.Pipeline; using UnityEditor.SceneManagement; using UnityEngine; namespace UnityEditor.AddressableAssets.Build.AnalyzeRules { /// /// Rule class to check for duplicate bundle dependencies /// public class CheckBundleDupeDependencies : BundleRuleBase { /// /// Result for checking for duplicates /// protected internal struct CheckDupeResult { public AddressableAssetGroup Group; public string DuplicatedFile; public string AssetPath; public GUID DuplicatedGroupGuid; } /// /// Results for duplicate results inverted check /// protected internal struct ExtraCheckBundleDupeData { public bool ResultsInverted; } /// public override bool CanFix { get { return true; } } /// public override string ruleName { get { return "Check Duplicate Bundle Dependencies"; } } [NonSerialized] internal readonly Dictionary>> m_AllIssues = new Dictionary>>(); [SerializeField] internal HashSet m_ImplicitAssets; /// /// Clear current analysis and rerun check for duplicates /// /// The current Addressables settings object /// List of the analysis results public override List RefreshAnalysis(AddressableAssetSettings settings) { ClearAnalysis(); return CheckForDuplicateDependencies(settings); } void RefreshDisplay() { var savedData = AnalyzeSystem.GetDataForRule(this); if (!savedData.ResultsInverted) { m_Results = (from issueGroup in m_AllIssues from bundle in issueGroup.Value from item in bundle.Value select new AnalyzeResult { resultName = issueGroup.Key + kDelimiter + ConvertBundleName(bundle.Key, issueGroup.Key) + kDelimiter + item, severity = MessageType.Warning }).ToList(); } else { m_Results = (from issueGroup in m_AllIssues from bundle in issueGroup.Value from item in bundle.Value select new AnalyzeResult { resultName = item + kDelimiter + ConvertBundleName(bundle.Key, issueGroup.Key) + kDelimiter + issueGroup.Key, severity = MessageType.Warning }).ToList(); } if (m_Results.Count == 0) m_Results.Add(noErrors); } internal override IList GetCustomContextMenuItems() { IList customItems = new List(); customItems.Add(new CustomContextMenu("Organize by Asset", () => InvertDisplay(), AnalyzeSystem.AnalyzeData.Data[ruleName].Any(), AnalyzeSystem.GetDataForRule(this).ResultsInverted)); return customItems; } void InvertDisplay() { List updatedResults = new List(); foreach (var result in AnalyzeSystem.AnalyzeData.Data[ruleName]) { updatedResults.Add(new AnalyzeResult() { //start at index 1 because the first result is going to be the rule name which we want to remain where it is. resultName = ReverseStringFromIndex(result.resultName, 1, kDelimiter), severity = result.severity }); } AnalyzeSystem.ReplaceAnalyzeData(this, updatedResults); var savedData = AnalyzeSystem.GetDataForRule(this); savedData.ResultsInverted = !savedData.ResultsInverted; AnalyzeSystem.SaveDataForRule(this, savedData); AnalyzeSystem.SerializeData(); AnalyzeSystem.ReloadUI(); } private string ReverseStringFromIndex(string data, int startingIndex, char delimiter) { string[] splitData = data.Split(delimiter); int i = startingIndex; int k = splitData.Length - 1; while (i < k) { string temp = splitData[i]; splitData[i] = splitData[k]; splitData[k] = temp; i++; k--; } return String.Join(kDelimiter.ToString(), splitData); } /// /// Check for duplicates among the dependencies and build implicit duplicates /// /// The current Addressables settings object /// List of results from analysis protected List CheckForDuplicateDependencies(AddressableAssetSettings settings) { if (!BuildUtility.CheckModifiedScenesAndAskToSave()) { Debug.LogError("Cannot run Analyze with unsaved scenes"); m_Results.Add(new AnalyzeResult { resultName = ruleName + "Cannot run Analyze with unsaved scenes" }); return m_Results; } CalculateInputDefinitions(settings); if (m_AllBundleInputDefs.Count > 0) { var context = GetBuildContext(settings); ReturnCode exitCode = RefreshBuild(context); if (exitCode < ReturnCode.Success) { Debug.LogError("Analyze build failed. " + exitCode); m_Results.Add(new AnalyzeResult { resultName = ruleName + "Analyze build failed. " + exitCode }); return m_Results; } var implicitGuids = GetImplicitGuidToFilesMap(); var checkDupeResults = CalculateDuplicates(implicitGuids, context); BuildImplicitDuplicatedAssetsSet(checkDupeResults); } RefreshDisplay(); return m_Results; } /// /// Calculate duplicate dependencies /// /// Map of implicit guids to their bundle files /// The build context information /// Enumerable of results from duplicates check protected internal IEnumerable CalculateDuplicates(Dictionary> implicitGuids, AddressableAssetsBuildContext aaContext) { //Get all guids that have more than one bundle referencing them IEnumerable>> validGuids = from dupeGuid in implicitGuids where dupeGuid.Value.Distinct().Count() > 1 where IsValidPath(AssetDatabase.GUIDToAssetPath(dupeGuid.Key.ToString())) select dupeGuid; return from guidToFile in validGuids from file in guidToFile.Value //Get the files that belong to those guids let fileToBundle = m_ExtractData.WriteData.FileToBundle[file] //Get the bundles that belong to those files let bundleToGroup = aaContext.bundleToAssetGroup[fileToBundle] //Get the asset groups that belong to those bundles let selectedGroup = aaContext.Settings.FindGroup(findGroup => findGroup != null && findGroup.Guid == bundleToGroup) select new CheckDupeResult { Group = selectedGroup, DuplicatedFile = file, AssetPath = AssetDatabase.GUIDToAssetPath(guidToFile.Key.ToString()), DuplicatedGroupGuid = guidToFile.Key }; } internal void BuildImplicitDuplicatedAssetsSet(IEnumerable checkDupeResults) { m_ImplicitAssets = new HashSet(); foreach (var checkDupeResult in checkDupeResults) { Dictionary> groupData; if (!m_AllIssues.TryGetValue(checkDupeResult.Group.Name, out groupData)) { groupData = new Dictionary>(); m_AllIssues.Add(checkDupeResult.Group.Name, groupData); } List assets; if (!groupData.TryGetValue(m_ExtractData.WriteData.FileToBundle[checkDupeResult.DuplicatedFile], out assets)) { assets = new List(); groupData.Add(m_ExtractData.WriteData.FileToBundle[checkDupeResult.DuplicatedFile], assets); } assets.Add(checkDupeResult.AssetPath); m_ImplicitAssets.Add(checkDupeResult.DuplicatedGroupGuid); } } /// /// Fix duplicates by moving to a new group /// /// The current Addressables settings object public override void FixIssues(AddressableAssetSettings settings) { if (m_ImplicitAssets == null) CheckForDuplicateDependencies(settings); if (m_ImplicitAssets.Count == 0) return; var group = settings.CreateGroup("Duplicate Asset Isolation", false, false, false, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema)); group.GetSchema().StaticContent = true; foreach (var asset in m_ImplicitAssets) settings.CreateOrMoveEntry(asset.ToString(), group, false, false); settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification, null, true, true); } /// public override void ClearAnalysis() { m_AllIssues.Clear(); m_ImplicitAssets = null; base.ClearAnalysis(); } } [InitializeOnLoad] class RegisterCheckBundleDupeDependencies { static RegisterCheckBundleDupeDependencies() { AnalyzeSystem.RegisterNewRule(); } } }