Unity构建详解(8)——SBP的Bundle生成

发布于:2024-04-17 ⋅ 阅读:(29) ⋅ 点赞:(0)

【WriteSerializedFiles】

这里将实际的写操作执行单独拎了出来共用,放在了IRunCachedCallbacks,但数据的传入和处理还是在Task中执行。

这一步会生成实际的SerializedFile文件,文件名就是之前的InternalName,但这还不是最终的Bundle文件,所以在之前的处理中会有一个InternalnameToBundleName的映射。

这一步可以改造成多线程的。

Unity中将用到的文件分为4类

public enum FileType
{
    //
    // 摘要:
    //     Object is contained in file not currently tracked by the AssetDatabase.
    NonAssetType,//主要是内置资产,这些资产guid都是0,得通过路径(path)直接找到
    //
    // 摘要:
    //     Object is contained in a very old format. Currently unused.
    DeprecatedCachedAssetType,
    //
    // 摘要:
    //     Object is contained in a standard asset file type located in the Assets folder.
    SerializedAssetType,//Asset文件夹下Unity自定义的文件,例如scene,prefab,material,animationclip
    //
    // 摘要:
    //     Object is contained in the imported asset meta data located in the Library folder.
    MetaAssetType //一些行业标准文件,例如fbx,texture,这些在导入Unity中会转成Unity的格式,被保存在Artifacts中
}

 先看没有缓存时

  • ProcessUncached
    • 获取文件保存路径:就是TempOutputFolder
    • 调用WriteOperations中的Write方法
      • ContentBuildInterface.WriteSerializedFile将序列化的数据写入到文件中得到WriteResult具体如下
        • ObjectSerializedInfo[] m_SerializedObjects要序列化的Object
          • ObjectIdentifier m_SerializedObject Object的标识
            • GUID m_GUID
            • long m_LocalIdentifierInFile 就是Bundle内的PathID
            • FileType m_FileType
            • string m_FilePath
          • SerializedLocation m_Header ObjectHeader的数据的位置
          • SerializedLocation m_RawData Object包含的数据的位置
            • string m_FileName
            • ulong m_Offset
            • ulong m_Size
        • ResourceFile[] m_ResourceFiles 生成的CAB-xxxxx文件,一般的AssetBundle只有一个,Scene还会有CAB-xxxx.sharedAsset文件
          • string m_FileName
          • string m_FileAlias
          • bool m_SerializedFile
        • Type[] m_IncludedTypes
        • string[] m_IncludedSerializeReferenceFQN
        • ExternalFileReference[] m_ExternalFileReferences
          • string m_filePath;
          • int m_type
          • GUID m_guid
    • 计算文件的Hash数据CalculateFileMetadata
      • RawFileHash:该Bundle内包含的所有ResourceFiles的名字的Hash
        • data.RawFileHash = HashingMethods.Calculate(fullHashObjects).ToHash128();
      • ContentHash:根据第一个Object的内容计算Hash
        • data.ContentHash = HashingMethods.Calculate(contentHashObjects).ToHash128();
        • 注意计算Hash只会从流中取4kb的数据,很可能导致文件Object数据变了,但Hash值没变
    • 简化SerializedObject数据SlimifySerializedObjects
    • PostProcess将写入的数据放入WriteResult中
      • m_Results.WriteResultsMetaData.Add(op.Command.internalName, item.Context.MetaData);
      • m_Results.WriteResults.Add(op.Command.internalName, item.Context.Result);

缓存数据的获取和保存 

  • 创建缓存CreateCacheEntry
    • entry.Type = CacheEntry.EntryType.Data;这里的EntryType是Data
  • 获取缓存数据GetCachedInfo
    • 这里缓存的定义数据就是WriteResult和SerializedFileMetaData
      • info.Data = new object[] { result, metaData };
  • 保存缓存数据
    • 和之前保存Asset和Scene的缓存数据逻辑一致cache.SaveCachedData(uncachedInfo);

缓存数据的读取和使用

  • 读取缓存数据cache.LoadCachedData(cacheEntries, out cachedInfo);
  • 直接读取缓存数据
    • item.Context.Result = (WriteResult)info.Data[0];
      item.Context.MetaData = (SerializedFileMetaData)info.Data[1];

【ArchiveAndCompressBundles】

这里生成最终的Bundle文件

没有缓存时

  • 创建ArchiveWorkItem
    • BundleName:还是之前算出来的bundleName
    • Compression:配置的压缩格式
    • OutputFilePath:Bundle文件输入路径,这个也是要配置的,是真正的文件路径,之前的只是临时路径
    • ResourceFiles:WriteData时算出来的
    • SeriliazedFileMetaDatas:WriteData时算出来的
  • 执行ArchiveWorkItem,不考虑多线程,实际写文件在ArchiveSingleItem
    • 文件先写到tempOutputFolder文件夹
      • SerializedFile文件也是先写入到这个文件夹
    • 调用ContentBuildInterface.ArchiveAndCompress得到bundle文件,返回该文件的Crc
    • 得到BundleDetails,这就是BuildIn模式的Manifest:
      • item.ResultDetails.FileName = item.OutputFilePath;
      • CRC
    • 将文件从tempOutputFolder复制到OutputFilePath
  • 后处理PostArchiveProcessing,主要是计算BundleDetails的Dependencies和Hash
    • Dependencies 算出来这个Bundle依赖的其他Bundle
    • Hash 根据依赖算Hash

缓存数据获取和保存

  • GetCacheEntry
  • GetCachedInfo
    • 定义的Data是BundleDetails
  • SaveCachedData(保存在"Library/BuildCache"文件夹中)

缓存数据读取和使用

  • GetCacheEntry
  • LoadCachedData
    • 通过计算的CacheEntry从缓存的CacheInfo中找到
  • 筛选出 cachedItems和nonCachedItems
  • 计算缓存文件路径并拷贝CopyingCachedFiles

【GenerateLocationListsTask】

这个Task目的是为Addressable创建寻址信息,在Addressable中每个Bundle、Asset都会有一个用于寻址的Location,上层加载传入路径,根据路径找到Location,根据Location找到Bundle、Asset。在Catalog中,Bundle和Asset的相关信息保存在ContentCatalogDataEntry

该Task可以改为多线程的

public class ContentCatalogDataEntry
{
    /// <summary>
    /// Internl id.
    /// </summary>
    public string InternalId { get; set; }
    /// <summary>
    /// IResourceProvider identifier.
    /// </summary>
    public string Provider { get; private set; }
    /// <summary>
    /// Keys for this location.
    /// </summary>
    public List<object> Keys { get; private set; }
    /// <summary>
    /// Dependency keys.
    /// </summary>
    public List<object> Dependencies { get; private set; }
    /// <summary>
    /// Serializable data for the provider.
    /// </summary>
    public object Data { get; set; }

    /// <summary>
    /// The type of the resource for th location.
    /// </summary>
    public Type ResourceType { get; private set; }
}
  • 创建 BundleEntry,每个Bundle对应一个BundleEntry
    • 计算包含的List<GUID> Assets :从WriteData.AssetToFiles和WriteData.FileToBundle可以得到每个bundle包含的Asset
    • 计算依赖的bundle HashSet<BundleEntry> Dependencies :WriteData.AssetToFiles其他File对应的bundle就是依赖的bundle
    • 计算其他依赖HashSet<BundleEntry> ExpandedDependencies :上面计算的是直接依赖,这里一直找到所有依赖
    • 指定AddressableAssetGroup Group:从aaContext.bundleToAssetGroup中找到该bundle对应的Group
  • 创建Bundle的ContentCatalogDataEntry
    • string InternalId 运行时加载的路径,对于bundle可能是本地路径或者是网络路径
    • string Provider 加载方法位于哪个类中
    • Type ResourceType:这个加载路径对应的资源类型
    • key只有BundleName
    • 没有Dependencies
  • 创建每个资源的ContentCatalogDataEntry:Calculate Locations
    • 创建该Asset的KeyList:CreateKeyList
      • 从Group的配置中,看KeyList是否要包含该Asset的address、guid、labels
    • Type ResourceType获取该资源运行时的类型
      • 对于Scene: Type runtimeProvider = GetRuntimeProviderType(providerType, mainType);
      • 对于Asset
        • 根据从该AddressableAssetEntry的guid从DependencyData.AssetInfo中找到includedObjects
        • 找到每个Object的Type:ContentBuildInterface.GetTypeForObjects
        • 再转为运行时的Type:rtType = AddressableAssetUtility.MapEditorTypeToRuntimeType(objType, false)
    • string InternalId 计算运行时从bundle中加载的路径
      • string assetPath = GetAssetLoadPath(isBundled, assetsInBundle);
    • HashSet<BundleEntry> ExpandedDependencies:
      • 其所在bundle的所有其他依赖
    •  string Provider
      • 从Group中得到 string assetProvider = GetAssetProviderName(bEntry.Group);
    • object Data 为空
  • 得到输出
    • 每个Bundle和bundle中每个资源的ContentCatalogDataEntry放入aaContext.locations
    • 每个AddressableAssetGroup对应的所有bunlde放入aaContext.assetGroupToBundles
    • bundle和asset对应的Provider放入aaContext.providerTypes
    • 每个Bundle的直接依赖放入aaContext.bundleToExpandedBundleDependencies
    • 每个Bundle的间接依赖放入aaContext.bundleToExpandedBundleDependencies

【Post Processing AssetBundles】

这里可以作为一个Task,但被Unity拿了出来,完全可以放到GenerateLocationListsTask中。这个Task的目的是为了继续补全ContentCatalogDataEntry

  • ContentCatalogDataEntry的KeyList中,第一个Key是primaryKey
  • 从aaContext.locations可以建立primaryKeyToCatalogEntryDic
  • Bundle的ContentCatalogDataEntry的Data是AssetBundleRequestOptions(这里的Data可以自定义,Unity提供的有很多冗余信息)
    • Crc:看Group的配置是否用,如果用,则从之前计算的BundleDetails中获取
    • UseCrcForCachedBundle:If false, the CRC will not be used when loading bundles from the cache.
    • UseUnityWebRequestForLocalBundles:If true, UnityWebRequest will be used even if the bundle is stored locally
    • Hash:看Group的配置是否用,如果用,则从之前计算的BundleDetails中获取
    • ChunkedTransfer
    • RedirectLimit
    • RetryCount
    • Timeout
    • BundleName
    • AssetLoadMode
    • BundleSize
    • ClearOtherCachedVersionsWhenLoaded
  • 重建输出的bundle的名字
    • ConstructAssetBundleName
  • 修改加载依赖
    • 修改bundle的InternalId和primaryKey
    • 修改Asset的依赖
      • 利用从aaContext.locations.Dependencies可以建立每个依赖在locations的索引和Dependencies的索引Dictionary<string, List<int[]>>来加速