using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Build.Content; using UnityEditor.Build.Pipeline.Injector; using UnityEditor.Build.Pipeline.Interfaces; using UnityEditor.Build.Pipeline.Utilities; using UnityEditor.Build.Utilities; namespace UnityEditor.Build.Pipeline.Tasks { /// <summary> /// Packs each asset bundle and calculates the asset load file dependency list. /// </summary> public class GenerateBundlePacking : IBuildTask { /// <inheritdoc /> public int Version { get { return 1; } } #pragma warning disable 649 [InjectContext(ContextUsage.In)] IBundleBuildContent m_BuildContent; [InjectContext(ContextUsage.In)] IDependencyData m_DependencyData; [InjectContext] IBundleWriteData m_WriteData; [InjectContext(ContextUsage.In)] IDeterministicIdentifiers m_PackingMethod; #if UNITY_2019_3_OR_NEWER [InjectContext(ContextUsage.In, true)] ICustomAssets m_CustomAssets; #endif #pragma warning restore 649 static bool ValidAssetBundle(List<GUID> assets, HashSet<GUID> customAssets) { // Custom Valid Asset Bundle function that tests if every asset is known by the asset database, is an asset (not a scene), or is a user driven custom asset return assets.All(x => ValidationMethods.ValidAsset(x) == ValidationMethods.Status.Asset || customAssets.Contains(x)); } /// <inheritdoc /> public ReturnCode Run() { Dictionary<GUID, List<GUID>> assetToReferences = new Dictionary<GUID, List<GUID>>(); HashSet<GUID> customAssets = new HashSet<GUID>(); #if UNITY_2019_3_OR_NEWER if (m_CustomAssets != null) customAssets.UnionWith(m_CustomAssets.Assets); #endif // Pack each asset bundle foreach (var bundle in m_BuildContent.BundleLayout) { if (ValidAssetBundle(bundle.Value, customAssets)) PackAssetBundle(bundle.Key, bundle.Value, assetToReferences); else if (ValidationMethods.ValidSceneBundle(bundle.Value)) PackSceneBundle(bundle.Key, bundle.Value, assetToReferences); } // Calculate Asset file load dependency list foreach (var bundle in m_BuildContent.BundleLayout) { foreach (var asset in bundle.Value) { List<string> files = m_WriteData.AssetToFiles[asset]; List<GUID> references = assetToReferences[asset]; foreach (var reference in references) { List<string> referenceFiles = m_WriteData.AssetToFiles[reference]; if (!files.Contains(referenceFiles[0])) files.Add(referenceFiles[0]); } } } return ReturnCode.Success; } void PackAssetBundle(string bundleName, List<GUID> includedAssets, Dictionary<GUID, List<GUID>> assetToReferences) { var internalName = string.Format(CommonStrings.AssetBundleNameFormat, m_PackingMethod.GenerateInternalFileName(bundleName)); var allObjects = new HashSet<ObjectIdentifier>(); Dictionary<GUID, HashSet<ObjectIdentifier>> assetObjectIdentifierHashSets = new Dictionary<GUID, HashSet<ObjectIdentifier>>(); foreach (var asset in includedAssets) { AssetLoadInfo assetInfo = m_DependencyData.AssetInfo[asset]; allObjects.UnionWith(assetInfo.includedObjects); var references = new List<ObjectIdentifier>(); references.AddRange(assetInfo.referencedObjects); assetToReferences[asset] = FilterReferencesForAsset(m_DependencyData, asset, references, null, null, assetObjectIdentifierHashSets); allObjects.UnionWith(references); m_WriteData.AssetToFiles[asset] = new List<string> { internalName }; } m_WriteData.FileToBundle.Add(internalName, bundleName); m_WriteData.FileToObjects.Add(internalName, allObjects.ToList()); } void PackSceneBundle(string bundleName, List<GUID> includedScenes, Dictionary<GUID, List<GUID>> assetToReferences) { if (includedScenes.IsNullOrEmpty()) return; string firstFileName = ""; HashSet<ObjectIdentifier> previousSceneObjects = new HashSet<ObjectIdentifier>(); HashSet<GUID> previousSceneAssets = new HashSet<GUID>(); List<string> sceneInternalNames = new List<string>(); Dictionary<GUID, HashSet<ObjectIdentifier>> assetObjectIdentifierHashSets = new Dictionary<GUID, HashSet<ObjectIdentifier>>(); foreach (var scene in includedScenes) { var scenePath = AssetDatabase.GUIDToAssetPath(scene.ToString()); var internalSceneName = m_PackingMethod.GenerateInternalFileName(scenePath); if (string.IsNullOrEmpty(firstFileName)) firstFileName = internalSceneName; var internalName = string.Format(CommonStrings.SceneBundleNameFormat, firstFileName, internalSceneName); SceneDependencyInfo sceneInfo = m_DependencyData.SceneInfo[scene]; var references = new List<ObjectIdentifier>(); references.AddRange(sceneInfo.referencedObjects); assetToReferences[scene] = FilterReferencesForAsset(m_DependencyData, scene, references, previousSceneObjects, previousSceneAssets, assetObjectIdentifierHashSets); previousSceneObjects.UnionWith(references); previousSceneAssets.UnionWith(assetToReferences[scene]); m_WriteData.FileToObjects.Add(internalName, references); m_WriteData.FileToBundle.Add(internalName, bundleName); var files = new List<string> { internalName }; files.AddRange(sceneInternalNames); m_WriteData.AssetToFiles[scene] = files; sceneInternalNames.Add(internalName); } } static HashSet<ObjectIdentifier> GetRefObjectIdLookup(AssetLoadInfo referencedAsset, Dictionary<GUID, HashSet<ObjectIdentifier>> assetObjectIdentifierHashSets) { HashSet<ObjectIdentifier> refObjectIdLookup; if ((assetObjectIdentifierHashSets == null) || (!assetObjectIdentifierHashSets.TryGetValue(referencedAsset.asset, out refObjectIdLookup))) { refObjectIdLookup = new HashSet<ObjectIdentifier>(referencedAsset.referencedObjects); assetObjectIdentifierHashSets?.Add(referencedAsset.asset, refObjectIdLookup); } return refObjectIdLookup; } internal static List<GUID> FilterReferencesForAsset(IDependencyData dependencyData, GUID asset, List<ObjectIdentifier> references, HashSet<ObjectIdentifier> previousSceneObjects = null, HashSet<GUID> previousSceneReferences = null, Dictionary<GUID, HashSet<ObjectIdentifier>> assetObjectIdentifierHashSets = null) { var referencedAssets = new HashSet<AssetLoadInfo>(); var referencedAssetsGuids = new List<GUID>(referencedAssets.Count); var referencesPruned = new List<ObjectIdentifier>(references.Count); // Remove Default Resources and Includes for Assets assigned to Bundles foreach (ObjectIdentifier reference in references) { if (reference.filePath.Equals(CommonStrings.UnityDefaultResourcePath, StringComparison.OrdinalIgnoreCase)) continue; if (dependencyData.AssetInfo.TryGetValue(reference.guid, out AssetLoadInfo referenceInfo)) { if (referencedAssets.Add(referenceInfo)) referencedAssetsGuids.Add(referenceInfo.asset); continue; } referencesPruned.Add(reference); } references.Clear(); references.AddRange(referencesPruned); // Remove References also included by non-circular Referenced Assets // Remove References also included by circular Referenced Assets if Asset's GUID is higher than Referenced Asset's GUID foreach (AssetLoadInfo referencedAsset in referencedAssets) { if ((asset > referencedAsset.asset) || (asset == referencedAsset.asset)) { references.RemoveAll(GetRefObjectIdLookup(referencedAsset, assetObjectIdentifierHashSets).Contains); } else { bool exists = false; foreach (ObjectIdentifier referencedObject in referencedAsset.referencedObjects) { if (referencedObject.guid == asset) { exists = true; break; } } if (!exists) { references.RemoveAll(GetRefObjectIdLookup(referencedAsset, assetObjectIdentifierHashSets).Contains); } } } // Special path for scenes, they can reference the same assets previously references if (!previousSceneReferences.IsNullOrEmpty()) { foreach (GUID reference in previousSceneReferences) { if (!dependencyData.AssetInfo.TryGetValue(reference, out AssetLoadInfo referencedAsset)) continue; var refObjectIdLookup = GetRefObjectIdLookup(referencedAsset, assetObjectIdentifierHashSets); // NOTE: It's impossible for an asset to depend on a scene, thus no need for circular reference checks // So just remove and add a dependency on the asset if there is a need to depend upon it. if (references.RemoveAll(refObjectIdLookup.Contains) > 0) referencedAssetsGuids.Add(referencedAsset.asset); } } // Special path for scenes, they can use data from previous sharedAssets in the same bundle if (!previousSceneObjects.IsNullOrEmpty()) references.RemoveAll(previousSceneObjects.Contains); return referencedAssetsGuids; } } }