using System; using System.Collections.Generic; using System.Linq; using UnityEditor.AddressableAssets.Build.BuildPipelineTasks; using UnityEditor.AddressableAssets.Build.DataBuilders; using UnityEditor.AddressableAssets.Settings; using UnityEditor.AddressableAssets.Settings.GroupSchemas; using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline; using UnityEditor.Build.Pipeline.Interfaces; using UnityEditor.Build.Pipeline.Tasks; using UnityEditor.Build.Pipeline.Utilities; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.AddressableAssets.Initialization; using UnityEngine.AddressableAssets.ResourceLocators; namespace UnityEditor.AddressableAssets.Build.AnalyzeRules { /// /// Base class for handling analyzing bundle rules tasks and checking dependencies /// public class BundleRuleBase : AnalyzeRule { [NonSerialized] internal List m_AddressableAssets = new List(); [NonSerialized] internal Dictionary> m_ResourcesToDependencies = new Dictionary>(); [NonSerialized] internal readonly List m_Locations = new List(); [NonSerialized] internal readonly List m_AllBundleInputDefs = new List(); [NonSerialized] internal readonly Dictionary m_BundleToAssetGroup = new Dictionary(); [NonSerialized] internal readonly List m_AssetEntries = new List(); [NonSerialized] internal ExtractDataTask m_ExtractData = new ExtractDataTask(); internal IList RuntimeDataBuildTasks(string builtinShaderBundleName) { IList buildTasks = new List(); // Setup buildTasks.Add(new SwitchToBuildPlatform()); buildTasks.Add(new RebuildSpriteAtlasCache()); // Player Scripts buildTasks.Add(new BuildPlayerScripts()); // Dependency buildTasks.Add(new CalculateSceneDependencyData()); buildTasks.Add(new CalculateAssetDependencyData()); buildTasks.Add(new StripUnusedSpriteSources()); buildTasks.Add(new CreateBuiltInShadersBundle(builtinShaderBundleName)); // Packing buildTasks.Add(new GenerateBundlePacking()); buildTasks.Add(new UpdateBundleObjectLayout()); buildTasks.Add(new GenerateBundleCommands()); buildTasks.Add(new GenerateSubAssetPathMaps()); buildTasks.Add(new GenerateBundleMaps()); buildTasks.Add(new GenerateLocationListsTask()); return buildTasks; } /// /// Get context for current Addressables settings /// /// The current Addressables settings object /// The build context information protected internal AddressableAssetsBuildContext GetBuildContext(AddressableAssetSettings settings) { ResourceManagerRuntimeData runtimeData = new ResourceManagerRuntimeData(); runtimeData.LogResourceManagerExceptions = settings.buildSettings.LogResourceManagerExceptions; var aaContext = new AddressableAssetsBuildContext { Settings = settings, runtimeData = runtimeData, bundleToAssetGroup = m_BundleToAssetGroup, locations = m_Locations, providerTypes = new HashSet(), assetEntries = m_AssetEntries, assetGroupToBundles = new Dictionary>() }; return aaContext; } /// /// Check path is valid path for Addressables entry /// /// The path to check /// Whether path is valid protected bool IsValidPath(string path) { return AddressableAssetUtility.IsPathValidForEntry(path) && !path.ToLower().Contains("/resources/") && !path.ToLower().StartsWith("resources/"); } /// /// Refresh build to check bundles against current rules /// /// Context information for building /// The return code of whether analyze build was successful, protected internal ReturnCode RefreshBuild(AddressableAssetsBuildContext buildContext) { var settings = buildContext.Settings; var context = new AddressablesDataBuilderInput(settings); var buildTarget = context.Target; var buildTargetGroup = context.TargetGroup; var buildParams = new AddressableAssetsBundleBuildParameters(settings, m_BundleToAssetGroup, buildTarget, buildTargetGroup, settings.buildSettings.bundleBuildPath); var builtinShaderBundleName = settings.DefaultGroup.Name.ToLower().Replace(" ", "").Replace('\\', '/').Replace("//", "/") + "_unitybuiltinshaders.bundle"; var buildTasks = RuntimeDataBuildTasks(builtinShaderBundleName); buildTasks.Add(m_ExtractData); IBundleBuildResults buildResults; var exitCode = ContentPipeline.BuildAssetBundles(buildParams, new BundleBuildContent(m_AllBundleInputDefs), out buildResults, buildTasks, buildContext); return exitCode; } /// /// Get dependencies from bundles /// /// The list of GUIDs of bundle dependencies protected List GetAllBundleDependencies() { var explicitGuids = m_ExtractData.WriteData.AssetToFiles.Keys; var implicitGuids = GetImplicitGuidToFilesMap().Keys; var allBundleGuids = explicitGuids.Union(implicitGuids); return allBundleGuids.ToList(); } /// /// Add Resource and Bundle dependencies in common to map of resources to dependencies /// /// GUID list of bundle dependencies protected internal void IntersectResourcesDepedenciesWithBundleDependencies(List bundleDependencyGuids) { foreach (var key in m_ResourcesToDependencies.Keys) { var bundleDependencies = bundleDependencyGuids.Intersect(m_ResourcesToDependencies[key]).ToList(); m_ResourcesToDependencies[key].Clear(); m_ResourcesToDependencies[key].AddRange(bundleDependencies); } } /// /// Build map of resources to corresponding dependencies /// /// Array of resource paths protected internal virtual void BuiltInResourcesToDependenciesMap(string[] resourcePaths) { for (int sceneIndex=0; sceneIndex(dependencies.Length)); else m_ResourcesToDependencies[path].Capacity += dependencies.Length; foreach (string dependency in dependencies) { if (dependency.EndsWith(".cs") || dependency.EndsWith(".dll")) continue; m_ResourcesToDependencies[path].Add(new GUID(AssetDatabase.AssetPathToGUID(dependency))); } } EditorUtility.ClearProgressBar(); } /// /// Use bundle names to create group names for AssetBundleBuild /// /// Context information for building protected internal void ConvertBundleNamesToGroupNames(AddressableAssetsBuildContext buildContext) { Dictionary bundleNamesToUpdate = new Dictionary(); foreach (var assetGroup in buildContext.Settings.groups) { if (assetGroup == null) continue; List bundles; if (buildContext.assetGroupToBundles.TryGetValue(assetGroup, out bundles)) { foreach (string bundle in bundles) { var keys = m_ExtractData.WriteData.FileToBundle.Keys.Where(key => m_ExtractData.WriteData.FileToBundle[key] == bundle); foreach (string key in keys) bundleNamesToUpdate.Add(key, assetGroup.Name); } } } foreach (string key in bundleNamesToUpdate.Keys) { var bundle = m_ExtractData.WriteData.FileToBundle[key]; var inputDef = m_AllBundleInputDefs.FirstOrDefault(b => b.assetBundleName == bundle); int index = m_AllBundleInputDefs.IndexOf(inputDef); if (index >= 0) { inputDef.assetBundleName = ConvertBundleName(inputDef.assetBundleName, bundleNamesToUpdate[key]); m_AllBundleInputDefs[index] = inputDef; m_ExtractData.WriteData.FileToBundle[key] = inputDef.assetBundleName; } } } /// /// Generate input definitions and entries for AssetBundleBuild /// /// The current Addressables settings object protected internal void CalculateInputDefinitions(AddressableAssetSettings settings) { int updateFrequency = Mathf.Max(settings.groups.Count / 10, 1); bool progressDisplayed = false; for (int groupIndex = 0; groupIndex < settings.groups.Count; ++groupIndex) { AddressableAssetGroup group = settings.groups[groupIndex]; if (group == null) continue; if (!progressDisplayed || groupIndex % updateFrequency == 0) { progressDisplayed = true; if (EditorUtility.DisplayCancelableProgressBar("Calculating Input Definitions", "", (float) groupIndex / settings.groups.Count)) { m_AssetEntries.Clear(); m_BundleToAssetGroup.Clear(); m_AllBundleInputDefs.Clear(); break; } } var schema = group.GetSchema(); if (schema != null && schema.IncludeInBuild) { List bundleInputDefinitions = new List(); m_AssetEntries.AddRange(BuildScriptPackedMode.PrepGroupBundlePacking(group, bundleInputDefinitions, schema)); for (int i = 0; i < bundleInputDefinitions.Count; i++) { if (m_BundleToAssetGroup.ContainsKey(bundleInputDefinitions[i].assetBundleName)) bundleInputDefinitions[i] = CreateUniqueBundle(bundleInputDefinitions[i]); m_BundleToAssetGroup.Add(bundleInputDefinitions[i].assetBundleName, schema.Group.Guid); } m_AllBundleInputDefs.AddRange(bundleInputDefinitions); } } if (progressDisplayed) EditorUtility.ClearProgressBar(); } internal AssetBundleBuild CreateUniqueBundle(AssetBundleBuild bid) { return CreateUniqueBundle(bid, m_BundleToAssetGroup); } /// /// Create new AssetBundleBuild /// /// ID for new AssetBundleBuild /// Map of bundle names to asset group Guids /// protected internal static AssetBundleBuild CreateUniqueBundle(AssetBundleBuild bid, Dictionary bundleToAssetGroup) { int count = 1; var newName = bid.assetBundleName; while (bundleToAssetGroup.ContainsKey(newName) && count < 1000) newName = bid.assetBundleName.Replace(".bundle", string.Format("{0}.bundle", count++)); return new AssetBundleBuild { assetBundleName = newName, addressableNames = bid.addressableNames, assetBundleVariant = bid.assetBundleVariant, assetNames = bid.assetNames }; } /// /// Get bundle's object ids that have no dependency file /// /// Name of bundle file /// List of GUIDS of objects in bundle with no dependency file protected List GetImplicitGuidsForBundle(string fileName) { List guids = (from id in m_ExtractData.WriteData.FileToObjects[fileName] where !m_ExtractData.WriteData.AssetToFiles.Keys.Contains(id.guid) select id.guid).ToList(); return guids; } /// /// Build map of implicit guids to their bundle files /// /// Dictionary of implicit guids to their corresponding file protected internal Dictionary> GetImplicitGuidToFilesMap() { Dictionary> implicitGuids = new Dictionary>(); IEnumerable> validImplicitGuids = from fileToObject in m_ExtractData.WriteData.FileToObjects from objectId in fileToObject.Value where !m_ExtractData.WriteData.AssetToFiles.Keys.Contains(objectId.guid) select new KeyValuePair(objectId, fileToObject.Key); //Build our Dictionary from our list of valid implicit guids (guids not already in explicit guids) foreach (var objectIdToFile in validImplicitGuids) { if (!implicitGuids.ContainsKey(objectIdToFile.Key.guid)) implicitGuids.Add(objectIdToFile.Key.guid, new List()); implicitGuids[objectIdToFile.Key.guid].Add(objectIdToFile.Value); } return implicitGuids; } /// /// Calculate built in resources and corresponding bundle dependencies /// /// The current Addressables settings object /// Array of resource paths /// List of rule results after calculating resource and bundle dependency combined protected List CalculateBuiltInResourceDependenciesToBundleDependecies(AddressableAssetSettings settings, string[] builtInResourcesPaths) { List results = new List(); if (!BuildUtility.CheckModifiedScenesAndAskToSave()) { Debug.LogError("Cannot run Analyze with unsaved scenes"); results.Add(new AnalyzeResult { resultName = ruleName + "Cannot run Analyze with unsaved scenes" }); return results; } EditorUtility.DisplayProgressBar("Calculating Built-in dependencies", "Calculating dependencies between Built-in resources and Addressables", 0); m_AddressableAssets = (from aaGroup in settings.groups where aaGroup != null from entry in aaGroup.entries select new GUID(entry.guid)).ToList(); // bulk of work and progress bars displayed in these methods BuiltInResourcesToDependenciesMap(builtInResourcesPaths); if (m_ResourcesToDependencies == null || m_ResourcesToDependencies.Count == 0) { results.Add(new AnalyzeResult {resultName = ruleName + " - No issues found."}); return results; } CalculateInputDefinitions(settings); if (m_AllBundleInputDefs == null || m_AllBundleInputDefs.Count == 0) { results.Add(new AnalyzeResult {resultName = ruleName + " - No issues found."}); return results; } EditorUtility.DisplayProgressBar("Calculating Built-in dependencies", "Calculating dependencies between Built-in resources and Addressables", 0.5f); var context = GetBuildContext(settings); ReturnCode exitCode = RefreshBuild(context); if (exitCode < ReturnCode.Success) { Debug.LogError("Analyze build failed. " + exitCode); results.Add(new AnalyzeResult { resultName = ruleName + "Analyze build failed. " + exitCode }); EditorUtility.ClearProgressBar(); return results; } EditorUtility.DisplayProgressBar("Calculating Built-in dependencies", "Calculating dependencies between Built-in resources and Addressables", 0.9f); IntersectResourcesDepedenciesWithBundleDependencies(GetAllBundleDependencies()); ConvertBundleNamesToGroupNames(context); results = (from resource in m_ResourcesToDependencies.Keys from dependency in m_ResourcesToDependencies[resource] let assetPath = AssetDatabase.GUIDToAssetPath(dependency.ToString()) let files = m_ExtractData.WriteData.FileToObjects.Keys from file in files where m_ExtractData.WriteData.FileToObjects[file].Any(oid => oid.guid == dependency) where m_ExtractData.WriteData.FileToBundle.ContainsKey(file) let bundle = m_ExtractData.WriteData.FileToBundle[file] select new AnalyzeResult { resultName = resource + kDelimiter + bundle + kDelimiter + assetPath, severity = MessageType.Warning }).ToList(); if (results.Count == 0) results.Add(new AnalyzeResult { resultName = ruleName + " - No issues found." }); EditorUtility.ClearProgressBar(); return results; } /// /// Convert bundle name to include group name /// /// Current bundle name /// Group name of bundle's group /// The new bundle name protected string ConvertBundleName(string bundleName, string groupName) { string[] bundleNameSegments = bundleName.Split('_'); bundleNameSegments[0] = groupName.Replace(" ", "").ToLower(); return string.Join("_", bundleNameSegments); } /// /// Clear all previously gathered bundle data and analysis /// public override void ClearAnalysis() { m_Locations.Clear(); m_AddressableAssets.Clear(); m_AssetEntries.Clear(); m_AllBundleInputDefs.Clear(); m_BundleToAssetGroup.Clear(); m_ResourcesToDependencies.Clear(); m_ExtractData = new ExtractDataTask(); base.ClearAnalysis(); } } }