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