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
{
///
/// Packs each asset bundle and calculates the asset load file dependency list.
///
public class GenerateBundlePacking : IBuildTask
{
///
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 assets, HashSet 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));
}
///
public ReturnCode Run()
{
Dictionary> assetToReferences = new Dictionary>();
HashSet customAssets = new HashSet();
#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 files = m_WriteData.AssetToFiles[asset];
List references = assetToReferences[asset];
foreach (var reference in references)
{
List referenceFiles = m_WriteData.AssetToFiles[reference];
if (!files.Contains(referenceFiles[0]))
files.Add(referenceFiles[0]);
}
}
}
return ReturnCode.Success;
}
void PackAssetBundle(string bundleName, List includedAssets, Dictionary> assetToReferences)
{
var internalName = string.Format(CommonStrings.AssetBundleNameFormat, m_PackingMethod.GenerateInternalFileName(bundleName));
var allObjects = new HashSet();
Dictionary> assetObjectIdentifierHashSets = new Dictionary>();
foreach (var asset in includedAssets)
{
AssetLoadInfo assetInfo = m_DependencyData.AssetInfo[asset];
allObjects.UnionWith(assetInfo.includedObjects);
var references = new List();
references.AddRange(assetInfo.referencedObjects);
assetToReferences[asset] = FilterReferencesForAsset(m_DependencyData, asset, references, null, null, assetObjectIdentifierHashSets);
allObjects.UnionWith(references);
m_WriteData.AssetToFiles[asset] = new List { internalName };
}
m_WriteData.FileToBundle.Add(internalName, bundleName);
m_WriteData.FileToObjects.Add(internalName, allObjects.ToList());
}
void PackSceneBundle(string bundleName, List includedScenes, Dictionary> assetToReferences)
{
if (includedScenes.IsNullOrEmpty())
return;
string firstFileName = "";
HashSet previousSceneObjects = new HashSet();
HashSet previousSceneAssets = new HashSet();
List sceneInternalNames = new List();
Dictionary> assetObjectIdentifierHashSets = new Dictionary>();
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();
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 { internalName };
files.AddRange(sceneInternalNames);
m_WriteData.AssetToFiles[scene] = files;
sceneInternalNames.Add(internalName);
}
}
static HashSet GetRefObjectIdLookup(AssetLoadInfo referencedAsset, Dictionary> assetObjectIdentifierHashSets)
{
HashSet refObjectIdLookup;
if ((assetObjectIdentifierHashSets == null) || (!assetObjectIdentifierHashSets.TryGetValue(referencedAsset.asset, out refObjectIdLookup)))
{
refObjectIdLookup = new HashSet(referencedAsset.referencedObjects);
assetObjectIdentifierHashSets?.Add(referencedAsset.asset, refObjectIdLookup);
}
return refObjectIdLookup;
}
internal static List FilterReferencesForAsset(IDependencyData dependencyData, GUID asset, List references, HashSet previousSceneObjects = null, HashSet previousSceneReferences = null, Dictionary> assetObjectIdentifierHashSets = null)
{
var referencedAssets = new HashSet();
var referencedAssetsGuids = new List(referencedAssets.Count);
var referencesPruned = new List(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;
}
}
}