/// <inheritdoc /> public ReturnCode Run() { TaskInput input = new TaskInput(); input.InternalFilenameToWriteResults = m_Results.WriteResults; #if UNITY_2019_3_OR_NEWER input.InternalFilenameToAdditionalFiles = m_Content.AdditionalFiles; #endif input.InternalFilenameToBundleName = m_WriteData.FileToBundle; input.GetCompressionForIdentifier = (x) => m_Parameters.GetCompressionForIdentifier(x); input.GetOutputFilePathForIdentifier = (x) => m_Parameters.GetOutputFilePathForIdentifier(x); input.BuildCache = m_Parameters.UseCache ? m_Cache : null; input.ProgressTracker = m_Tracker; input.TempOutputFolder = m_Parameters.TempOutputFolder; input.AssetToFilesDependencies = m_WriteData.AssetToFiles; input.Log = m_Log; input.Threaded = SupportsMultiThreadedArchiving && ScriptableBuildPipeline.threadedArchiving; TaskOutput output; ReturnCode code = Run(input, out output); if (code == ReturnCode.Success) { foreach (var item in output.BundleDetails) { m_Results.BundleInfos.Add(item.Key, item.Value); } } return(code); }
public ReturnCode Run() { Dictionary <string, ulong> fileOffsets = new Dictionary <string, ulong>(); List <KeyValuePair <string, List <ResourceFile> > > bundleResources; { Dictionary <string, List <ResourceFile> > bundleToResources = new Dictionary <string, List <ResourceFile> >(); foreach (var pair in m_Results.WriteResults) { string bundle = m_WriteData.FileToBundle[pair.Key]; List <ResourceFile> resourceFiles; bundleToResources.GetOrAdd(bundle, out resourceFiles); resourceFiles.AddRange(pair.Value.resourceFiles); foreach (ResourceFile serializedFile in pair.Value.resourceFiles) { if (!serializedFile.serializedFile) { continue; } ObjectSerializedInfo firstObject = pair.Value.serializedObjects.First(x => x.header.fileName == serializedFile.fileAlias); fileOffsets[serializedFile.fileName] = firstObject.header.offset; } } bundleResources = bundleToResources.ToList(); } Dictionary <string, HashSet <string> > bundleDependencies = new Dictionary <string, HashSet <string> >(); foreach (var files in m_WriteData.AssetToFiles.Values) { if (files.IsNullOrEmpty()) { continue; } string bundle = m_WriteData.FileToBundle[files.First()]; HashSet <string> dependencies; bundleDependencies.GetOrAdd(bundle, out dependencies); dependencies.UnionWith(files.Select(x => m_WriteData.FileToBundle[x])); dependencies.Remove(bundle); } IList <CacheEntry> entries = bundleResources.Select(x => GetCacheEntry(x.Key, x.Value, m_Parameters.GetCompressionForIdentifier(x.Key))).ToList(); IList <CachedInfo> cachedInfo = null; IList <CachedInfo> uncachedInfo = null; if (m_Parameters.UseCache && m_Cache != null) { m_Cache.LoadCachedData(entries, out cachedInfo); uncachedInfo = new List <CachedInfo>(); } for (int i = 0; i < bundleResources.Count; i++) { string bundleName = bundleResources[i].Key; ResourceFile[] resourceFiles = bundleResources[i].Value.ToArray(); BuildCompression compression = m_Parameters.GetCompressionForIdentifier(bundleName); string writePath; BundleDetails details; if (cachedInfo != null && cachedInfo[i] != null) { if (!m_Tracker.UpdateInfoUnchecked(string.Format("{0} (Cached)", bundleName))) { return(ReturnCode.Canceled); } details = (BundleDetails)cachedInfo[i].Data[0]; details.FileName = string.Format("{0}/{1}", m_Parameters.OutputFolder, bundleName); HashSet <string> dependencies; if (bundleDependencies.TryGetValue(bundleName, out dependencies)) { details.Dependencies = dependencies.ToArray(); } else { details.Dependencies = new string[0]; } writePath = string.Format("{0}/{1}", m_Cache.GetCachedArtifactsDirectory(entries[i]), bundleName); } else { if (!m_Tracker.UpdateInfoUnchecked(bundleName)) { return(ReturnCode.Canceled); } details = new BundleDetails(); writePath = string.Format("{0}/{1}", m_Parameters.TempOutputFolder, bundleName); if (m_Parameters.UseCache && m_Cache != null) { writePath = string.Format("{0}/{1}", m_Cache.GetCachedArtifactsDirectory(entries[i]), bundleName); } Directory.CreateDirectory(Path.GetDirectoryName(writePath)); details.FileName = string.Format("{0}/{1}", m_Parameters.OutputFolder, bundleName); details.Crc = ContentBuildInterface.ArchiveAndCompress(resourceFiles, writePath, compression); details.Hash = CalculateHashVersion(fileOffsets, resourceFiles); HashSet <string> dependencies; if (bundleDependencies.TryGetValue(bundleName, out dependencies)) { details.Dependencies = dependencies.ToArray(); } else { details.Dependencies = new string[0]; } if (uncachedInfo != null) { uncachedInfo.Add(GetCachedInfo(m_Cache, entries[i], resourceFiles, details)); } } SetOutputInformation(writePath, details.FileName, bundleName, details); } if (m_Parameters.UseCache && m_Cache != null) { m_Cache.SaveCachedData(uncachedInfo); } return(ReturnCode.Success); }
private BuildLayout CreateBuildLayout() { LayoutLookupTables lookup = new LayoutLookupTables(); foreach (string bundleName in m_WriteData.FileToBundle.Values.Distinct()) { BuildLayout.Bundle bundle = new BuildLayout.Bundle(); bundle.Name = bundleName; string path = m_Parameters.GetOutputFilePathForIdentifier(bundle.Name); UnityEngine.BuildCompression compression = m_Parameters.GetCompressionForIdentifier(bundle.Name); bundle.FileSize = (ulong)new FileInfo(path).Length; bundle.Compression = compression.compression.ToString(); lookup.Bundles.Add(bundle.Name, bundle); } // create files foreach (KeyValuePair <string, string> fileBundle in m_WriteData.FileToBundle) { BuildLayout.Bundle bundle = lookup.Bundles[fileBundle.Value]; BuildLayout.File f = new BuildLayout.File(); f.Name = fileBundle.Key; WriteResult result = m_Results.WriteResults[f.Name]; foreach (ResourceFile rf in result.resourceFiles) { var sf = new BuildLayout.SubFile(); sf.IsSerializedFile = rf.serializedFile; sf.Name = rf.fileAlias; sf.Size = (ulong)new FileInfo(rf.fileName).Length; f.SubFiles.Add(sf); } bundle.Files.Add(f); lookup.Files.Add(f.Name, f); } // create assets foreach (KeyValuePair <GUID, List <string> > assetFile in m_WriteData.AssetToFiles) { BuildLayout.File file = lookup.Files[assetFile.Value[0]]; BuildLayout.ExplicitAsset a = new BuildLayout.ExplicitAsset(); a.Guid = assetFile.Key.ToString(); a.AssetPath = AssetDatabase.GUIDToAssetPath(a.Guid); file.Assets.Add(a); lookup.GuidToExplicitAsset.Add(a.Guid, a); } Dictionary <string, List <BuildLayout.DataFromOtherAsset> > guidToPulledInBuckets = new Dictionary <string, List <BuildLayout.DataFromOtherAsset> >(); foreach (BuildLayout.File file in lookup.Files.Values) { Dictionary <string, AssetBucket> buckets = new Dictionary <string, AssetBucket>(); WriteResult writeResult = m_Results.WriteResults[file.Name]; List <ObjectSerializedInfo> sceneObjects = new List <ObjectSerializedInfo>(); foreach (ObjectSerializedInfo info in writeResult.serializedObjects) { string sourceGuid = string.Empty; if (info.serializedObject.guid.Empty()) { if (info.serializedObject.filePath.Equals("temp:/assetbundle", StringComparison.OrdinalIgnoreCase)) { file.BundleObjectInfo = new BuildLayout.AssetBundleObjectInfo(); file.BundleObjectInfo.Size = info.header.size; continue; } else if (info.serializedObject.filePath.StartsWith("temp:/preloaddata", StringComparison.OrdinalIgnoreCase)) { file.PreloadInfoSize = (int)info.header.size; continue; } else if (info.serializedObject.filePath.StartsWith("temp:/", StringComparison.OrdinalIgnoreCase)) { sceneObjects.Add(info); continue; } else if (!string.IsNullOrEmpty(info.serializedObject.filePath)) { AssetBucket pathBucket = GetOrCreate(buckets, info.serializedObject.filePath.ToString()); pathBucket.isFilePathBucket = true; pathBucket.objs.Add(info); continue; } } AssetBucket bucket = GetOrCreate(buckets, info.serializedObject.guid.ToString()); bucket.objs.Add(info); } if (sceneObjects.Count > 0) { BuildLayout.ExplicitAsset sceneAsset = file.Assets.First(x => x.AssetPath.EndsWith(".unity")); AssetBucket bucket = GetOrCreate(buckets, sceneAsset.Guid); bucket.objs.AddRange(sceneObjects); } // Update buckets with a reference to their explicit asset file.Assets.ForEach(eAsset => { if (!buckets.TryGetValue(eAsset.Guid, out AssetBucket b)) { b = GetOrCreate(buckets, eAsset.Guid); // some assets might not pull in any objects } b.ExplictAsset = eAsset; }); // Create entries for buckets that are implicitly pulled in Dictionary <string, BuildLayout.DataFromOtherAsset> guidToOtherData = new Dictionary <string, BuildLayout.DataFromOtherAsset>(); foreach (AssetBucket bucket in buckets.Values.Where(x => x.ExplictAsset == null)) { string assetPath = bucket.isFilePathBucket ? bucket.guid : AssetDatabase.GUIDToAssetPath(bucket.guid); if (assetPath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) || assetPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { file.MonoScriptCount++; file.MonoScriptSize += bucket.CalcObjectSize(); continue; } var otherData = new BuildLayout.DataFromOtherAsset(); otherData.AssetPath = assetPath; otherData.AssetGuid = bucket.guid; otherData.SerializedSize = bucket.CalcObjectSize(); otherData.StreamedSize = bucket.CalcStreamedSize(); otherData.ObjectCount = bucket.objs.Count; file.OtherAssets.Add(otherData); guidToOtherData[otherData.AssetGuid] = otherData; if (!guidToPulledInBuckets.TryGetValue(otherData.AssetGuid, out List <BuildLayout.DataFromOtherAsset> bucketList)) { bucketList = guidToPulledInBuckets[otherData.AssetGuid] = new List <BuildLayout.DataFromOtherAsset>(); } bucketList.Add(otherData); } // Add references foreach (BuildLayout.ExplicitAsset asset in file.Assets) { AssetBucket bucket = buckets[asset.Guid]; asset.SerializedSize = bucket.CalcObjectSize(); asset.StreamedSize = bucket.CalcStreamedSize(); IEnumerable <ObjectIdentifier> refs = null; if (m_DependencyData.AssetInfo.TryGetValue(new GUID(asset.Guid), out AssetLoadInfo info)) { refs = info.referencedObjects; } else { refs = m_DependencyData.SceneInfo[new GUID(asset.Guid)].referencedObjects; } foreach (string refGUID in refs.Select(x => x.guid.Empty() ? x.filePath : x.guid.ToString()).Distinct()) { if (guidToOtherData.TryGetValue(refGUID, out BuildLayout.DataFromOtherAsset dfoa)) { dfoa.ReferencingAssets.Add(asset); asset.InternalReferencedOtherAssets.Add(dfoa); } else if (buckets.TryGetValue(refGUID, out AssetBucket refBucket)) { asset.InternalReferencedExplicitAssets.Add(refBucket.ExplictAsset); } else if (lookup.GuidToExplicitAsset.TryGetValue(refGUID, out BuildLayout.ExplicitAsset refAsset)) { asset.ExternallyReferencedAssets.Add(refAsset); } } } } BuildLayout layout = new BuildLayout(); // This is the addressables section. Everything above could technically be moved to SBP. { AddressableAssetsBuildContext aaContext = (AddressableAssetsBuildContext)m_AaBuildContext; // Map from GUID to AddrssableAssetEntry Dictionary <string, AddressableAssetEntry> guidToEntry = aaContext.assetEntries.ToDictionary(x => x.guid, x => x); // create groups foreach (AddressableAssetGroup group in aaContext.Settings.groups) { var grp = new BuildLayout.Group(); grp.Name = group.Name; grp.Guid = group.Guid; foreach (AddressableAssetGroupSchema schema in group.Schemas) { var sd = new BuildLayout.SchemaData(); sd.Guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(schema)); sd.Type = schema.GetType().Name.ToString(); BundledAssetGroupSchema bSchema = schema as BundledAssetGroupSchema; if (bSchema != null) { sd.KvpDetails.Add(new Tuple <string, string>("PackingMode", bSchema.BundleMode.ToString())); sd.KvpDetails.Add(new Tuple <string, string>("Compression", bSchema.Compression.ToString())); } grp.Schemas.Add(sd); } lookup.GroupLookup.Add(group.Guid, grp); layout.Groups.Add(grp); } // go through all the bundles and put them in groups foreach (BuildLayout.Bundle b in lookup.Bundles.Values) { if (aaContext.bundleToImmediateBundleDependencies.TryGetValue(b.Name, out List <string> deps)) { b.Dependencies = deps.Select(x => lookup.Bundles[x]).Where(x => b != x).ToList(); } if (aaContext.bundleToExpandedBundleDependencies.TryGetValue(b.Name, out List <string> deps2)) { b.ExpandedDependencies = deps2.Select(x => lookup.Bundles[x]).Where(x => b != x).ToList(); } if (aaContext.bundleToAssetGroup.TryGetValue(b.Name, out string grpName)) { lookup.GroupLookup[grpName].Bundles.Add(b); } else { layout.BuiltInBundles.Add(b); } } // Apply the addressable name to the asset foreach (BuildLayout.ExplicitAsset a in BuildLayoutHelpers.EnumerateAssets(layout)) { if (guidToEntry.TryGetValue(a.Guid, out AddressableAssetEntry entry)) { a.AddressableName = entry.address; } } // The addressables build script can rename the bundles foreach (BuildLayout.Bundle b in BuildLayoutHelpers.EnumerateBundles(layout)) { if (m_BundleNameRemap.TryGetValue(b.Name, out string newName)) { b.Name = newName; } } } return(layout); }