public static bool BuildAssetBundle(string manifestPath, GUID assetGuid, string cacheFilePath, BuildTarget target, bool collectDependencies = false, HashSet <Entities.Hash128> dependencies = null, HashSet <System.Type> types = null, long fileIdent = -1) { using (new BuildInterfacesWrapper()) { Directory.CreateDirectory(k_TempBuildPath); // Used for naming string asset; if (fileIdent != -1) { GUID convGuid = assetGuid; PackBuiltinExtraWithFileIdent(ref convGuid, fileIdent); asset = convGuid.ToString(); } else { asset = assetGuid.ToString(); } // Deterministic ID Generator var generator = new Unity5PackedIdentifiers(); // Target platform settings & script information var settings = new BuildSettings { buildFlags = ContentBuildFlags.None, target = target, group = BuildPipeline.GetBuildTargetGroup(target), typeDB = null }; if (assetGuid == k_UnityBuiltinResources) { FilterBuiltinResourcesObjectManifest(manifestPath); } if (assetGuid == k_UnityBuiltinExtraResources) { FilterBuiltinExtraResourcesObjectManifest(manifestPath); } #if UNITY_2020_1_OR_NEWER // Collect all the objects we need for this asset & bundle (returned array order is deterministic) var manifestObjects = ContentBuildInterface.GetPlayerObjectIdentifiersInSerializedFile(manifestPath, settings.target); #else var method = typeof(ContentBuildInterface).GetMethod("GetPlayerObjectIdentifiersInSerializedFile", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); // Collect all the objects we need for this asset & bundle (returned array order is deterministic) var manifestObjects = (ObjectIdentifier[])method.Invoke(null, new object[] { manifestPath, settings.target }); #endif // Collect all the objects we need to reference for this asset (returned array order is deterministic) var manifestDependencies = ContentBuildInterface.GetPlayerDependenciesForObjects(manifestObjects, settings.target, settings.typeDB); // Scene / Project lighting information var globalUsage = ContentBuildInterface.GetGlobalUsageFromGraphicsSettings(); globalUsage = ForceKeepInstancingVariants(globalUsage); // Inter-asset feature usage (shader features, used mesh channels) var usageSet = new BuildUsageTagSet(); ContentBuildInterface.CalculateBuildUsageTags(manifestDependencies, manifestDependencies, globalUsage, usageSet); // TODO: Cache & Append to the assets that are influenced by this usageTagSet, ideally it would be a nice api to extract just the data for a given asset or object from the result // Bundle all the needed write parameters var writeParams = new WriteParameters { // Target platform settings & script information settings = settings, // Scene / Project lighting information globalUsage = globalUsage, // Inter-asset feature usage (shader features, used mesh channels) usageSet = usageSet, // Serialized File Layout writeCommand = new WriteCommand { fileName = generator.GenerateInternalFileName(asset), internalName = generator.GenerateAssetBundleInternalFileName(asset), serializeObjects = new List <SerializationInfo>() // Populated Below }, // External object references referenceMap = new BuildReferenceMap(), // Populated Below // Asset Bundle object layout bundleInfo = new AssetBundleInfo { bundleName = asset, // What is loadable from this bundle bundleAssets = new List <AssetLoadInfo> { // The manifest object and it's dependencies new AssetLoadInfo { address = asset, asset = assetGuid, // TODO: Remove this as it is unused in C++ includedObjects = manifestObjects.ToList(), // TODO: In our effort to modernize the public API design we over complicated it trying to take List or return ReadOnlyLists. Should have just stuck with Arrays[] in all places referencedObjects = manifestDependencies.ToList() } } } }; // For Builtin Resources, we just want to reference them directly instead of pull them in. //if (assetGuid == k_UnityBuiltinResources) // assetGuid = manifestGuid; // The requirement is that a single asset bundle only contains the ObjectManifest and the objects that are directly part of the asset, objects for external assets will be in their own bundles. IE: 1 asset per bundle layout // So this means we need to take manifestObjects & manifestDependencies and filter storing them into writeCommand.serializeObjects and/or referenceMap based on if they are this asset or other assets foreach (var obj in manifestObjects) { writeParams.writeCommand.serializeObjects.Add(new SerializationInfo { serializationObject = obj, serializationIndex = generator.SerializationIndexFromObjectIdentifier(obj) }); writeParams.referenceMap.AddMapping(writeParams.writeCommand.internalName, generator.SerializationIndexFromObjectIdentifier(obj), obj); } foreach (var obj in manifestDependencies) { // MonoScripts need to live beside the MonoBehavior (in this case ScriptableObject) and loaded first. We could move it to it's own bundle, but this is safer and it's a lightweight object var type = ContentBuildInterface.GetTypeForObject(obj); if (obj.guid == k_UnityBuiltinResources) { // For Builtin Resources, we can reference them directly // TODO: Once we switch to using GlobalObjectId for SBP, we will need a mapping for certain special cases of GUID <> FilePath for Builtin Resources writeParams.referenceMap.AddMapping(obj.filePath, obj.localIdentifierInFile, obj); } else if (type == typeof(MonoScript)) { writeParams.writeCommand.serializeObjects.Add(new SerializationInfo { serializationObject = obj, serializationIndex = generator.SerializationIndexFromObjectIdentifier(obj) }); writeParams.referenceMap.AddMapping(writeParams.writeCommand.internalName, generator.SerializationIndexFromObjectIdentifier(obj), obj); } else if (collectDependencies || obj.guid == assetGuid) { // If we are a specific built-in asset, only add the built-in asset if (fileIdent != -1 && obj.localIdentifierInFile != fileIdent) { continue; } writeParams.writeCommand.serializeObjects.Add(new SerializationInfo { serializationObject = obj, serializationIndex = generator.SerializationIndexFromObjectIdentifier(obj) }); writeParams.referenceMap.AddMapping(writeParams.writeCommand.internalName, generator.SerializationIndexFromObjectIdentifier(obj), obj); } else if (obj.guid == k_UnityBuiltinExtraResources) { GUID convGUID = obj.guid; PackBuiltinExtraWithFileIdent(ref convGUID, obj.localIdentifierInFile); writeParams.referenceMap.AddMapping(generator.GenerateAssetBundleInternalFileName(convGUID.ToString()), generator.SerializationIndexFromObjectIdentifier(obj), obj); dependencies?.Add(convGUID); } else if (!obj.guid.Empty()) { writeParams.referenceMap.AddMapping(generator.GenerateAssetBundleInternalFileName(obj.guid.ToString()), generator.SerializationIndexFromObjectIdentifier(obj), obj); } // This will be solvable after we move SBP into the asset pipeline as importers. if (!obj.guid.Empty() && obj.guid != assetGuid && type != typeof(MonoScript) && obj.guid != k_UnityBuiltinExtraResources) { dependencies?.Add(obj.guid); } if (type != null) { types?.Add(type); } } // Write the serialized file var result = ContentBuildInterface.WriteSerializedFile(k_TempBuildPath, writeParams); // Archive and compress the serialized & resource files for the previous operation var crc = ContentBuildInterface.ArchiveAndCompress(result.resourceFiles.ToArray(), cacheFilePath, UnityEngine.BuildCompression.Uncompressed); // Because the shader compiler progress bar hooks are absolute shit EditorUtility.ClearProgressBar(); //Debug.Log($"Wrote '{writeParams.writeCommand.fileName}' to '{k_TempBuildPath}/{writeParams.writeCommand.internalName}' resulting in {result.serializedObjects.Count} objects in the serialized file."); //Debug.Log($"Archived '{k_TempBuildPath}/{writeParams.writeCommand.internalName}' to '{cacheFilePath}' resulting in {crc} CRC."); Directory.Delete(k_TempBuildPath, true); return(crc != 0); } }
public static bool BuildSceneBundle(GUID sceneGuid, string cacheFilePath, BuildTarget target, bool collectDependencies = false, HashSet <Entities.Hash128> dependencies = null, HashSet <System.Type> types = null) { using (new BuildInterfacesWrapper()) using (new SceneStateCleanup()) { Directory.CreateDirectory(k_TempBuildPath); var scene = sceneGuid.ToString(); var scenePath = AssetDatabase.GUIDToAssetPath(scene); // Deterministic ID Generator var generator = new Unity5PackedIdentifiers(); // Target platform settings & script information var settings = new BuildSettings { buildFlags = ContentBuildFlags.None, target = target, group = BuildPipeline.GetBuildTargetGroup(target), typeDB = null }; // Inter-asset feature usage (shader features, used mesh channels) var usageSet = new BuildUsageTagSet(); var dependencyResults = ContentBuildInterface.CalculatePlayerDependenciesForScene(scenePath, settings, usageSet); // Bundle all the needed write parameters var writeParams = new WriteSceneParameters { // Target platform settings & script information settings = settings, // Scene / Project lighting information globalUsage = dependencyResults.globalUsage, // Inter-asset feature usage (shader features, used mesh channels) usageSet = usageSet, // Scene being written out scenePath = scenePath, // Serialized File Layout writeCommand = new WriteCommand { fileName = generator.GenerateSceneInternalFileName(scenePath), internalName = generator.GenerateSceneBundleInternalFileName(scenePath), serializeObjects = new List <SerializationInfo>() // Populated Below }, // External object references referenceMap = new BuildReferenceMap(), // Populated Below // External object preload preloadInfo = new PreloadInfo(), // Populated Below sceneBundleInfo = new SceneBundleInfo { bundleName = scene, bundleScenes = new List <SceneLoadInfo> { new SceneLoadInfo { asset = sceneGuid, address = scenePath, internalName = generator.GenerateInternalFileName(scenePath) } } } }; // The requirement is that a single asset bundle only contains the ObjectManifest and the objects that are directly part of the asset, objects for external assets will be in their own bundles. IE: 1 asset per bundle layout // So this means we need to take manifestObjects & manifestDependencies and filter storing them into writeCommand.serializeObjects and/or referenceMap based on if they are this asset or other assets foreach (var obj in dependencyResults.referencedObjects) { // MonoScripts need to live beside the MonoBehavior (in this case ScriptableObject) and loaded first. We could move it to it's own bundle, but this is safer and it's a lightweight object var type = ContentBuildInterface.GetTypeForObject(obj); if (obj.guid == GUIDHelper.UnityBuiltinResources) { // For Builtin Resources, we can reference them directly // TODO: Once we switch to using GlobalObjectId for SBP, we will need a mapping for certain special cases of GUID <> FilePath for Builtin Resources writeParams.referenceMap.AddMapping(obj.filePath, obj.localIdentifierInFile, obj); } else if (type == typeof(MonoScript)) { writeParams.writeCommand.serializeObjects.Add(new SerializationInfo { serializationObject = obj, serializationIndex = generator.SerializationIndexFromObjectIdentifier(obj) }); writeParams.referenceMap.AddMapping(writeParams.writeCommand.internalName, generator.SerializationIndexFromObjectIdentifier(obj), obj); } else if (collectDependencies || obj.guid == sceneGuid) { writeParams.writeCommand.serializeObjects.Add(new SerializationInfo { serializationObject = obj, serializationIndex = generator.SerializationIndexFromObjectIdentifier(obj) }); writeParams.referenceMap.AddMapping(writeParams.writeCommand.internalName, generator.SerializationIndexFromObjectIdentifier(obj), obj); } else if (obj.guid == GUIDHelper.UnityBuiltinExtraResources) { var convGUID = obj.guid; GUIDHelper.PackBuiltinExtraWithFileIdent(ref convGUID, obj.localIdentifierInFile); writeParams.referenceMap.AddMapping(generator.GenerateAssetBundleInternalFileName(convGUID), generator.SerializationIndexFromObjectIdentifier(obj), obj); dependencies?.Add(convGUID); } else if (!obj.guid.Empty()) { writeParams.referenceMap.AddMapping(generator.GenerateAssetBundleInternalFileName(obj.guid), generator.SerializationIndexFromObjectIdentifier(obj), obj); } // This will be solvable after we move SBP into the asset pipeline as importers. if (!obj.guid.Empty() && obj.guid != sceneGuid && type != typeof(MonoScript) && !GUIDHelper.IsBuiltin(obj.guid)) { dependencies?.Add(obj.guid); } if (type != null) { types?.Add(type); } } // Write the serialized file var result = ContentBuildInterface.WriteSceneSerializedFile(k_TempBuildPath, writeParams); // Archive and compress the serialized & resource files for the previous operation var crc = ContentBuildInterface.ArchiveAndCompress(result.resourceFiles.ToArray(), cacheFilePath, UnityEngine.BuildCompression.Uncompressed); // Because the shader compiler progress bar hooks are absolute shit EditorUtility.ClearProgressBar(); //Debug.Log($"Wrote '{writeParams.writeCommand.fileName}' to '{k_TempBuildPath}/{writeParams.writeCommand.internalName}' resulting in {result.serializedObjects.Count} objects in the serialized file."); //Debug.Log($"Archived '{k_TempBuildPath}/{writeParams.writeCommand.internalName}' to '{cacheFilePath}' resulting in {crc} CRC."); return(crc != 0); } }
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 = m_Parameters.GetOutputFilePathForIdentifier(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 = m_Parameters.GetOutputFilePathForIdentifier(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); }