【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
- ObjectIdentifier m_SerializedObject Object的标识
- 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
- ObjectSerializedInfo[] m_SerializedObjects要序列化的Object
- ContentBuildInterface.WriteSerializedFile将序列化的数据写入到文件中得到WriteResult具体如下
- 计算文件的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值没变
- RawFileHash:该Bundle内包含的所有ResourceFiles的名字的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 };
- 这里缓存的定义数据就是WriteResult和SerializedFileMetaData
- 保存缓存数据
- 和之前保存Asset和Scene的缓存数据逻辑一致cache.SaveCachedData(uncachedInfo);
缓存数据的读取和使用
- 读取缓存数据cache.LoadCachedData(cacheEntries, out cachedInfo);
- 直接读取缓存数据
- item.Context.Result = (WriteResult)info.Data[0];
item.Context.MetaData = (SerializedFileMetaData)info.Data[1];
- item.Context.Result = (WriteResult)info.Data[0];
【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
- 文件先写到tempOutputFolder文件夹
- 后处理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 为空
- 创建该Asset的KeyList:CreateKeyList
- 得到输出
- 每个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[]>>来加速