【前置知识】
先要搞清楚Asset和Object的关系,可以简单理解为一个Asset对应多个Object。
unity自定义的Asset也要有一个存储的标准,其采用的是YAML,我们看到的所有Unity自定义的Asset格式,例如.prefab(预制体),.scene(场景文件),.asset(资源文件,例如ScriptableObject),.mat(材质球),.controller(动画状态机),还有图集等。
例如,prefab文件用YAML的格式记录了该Asset包含的Object,每个Object有一个FileID,用于区分Asset内的Object,每个Object中会记录自身的属性信息、包含的Type、引用的其他Object、引用的其他Asset、引用的其他Asset的Object等信息。
ObjectIdentifier是YAML中的Object的简要记录
可以建立这样一个层级一对多关系:Asset->Object->Type
【Native接口】
- 获取一个Asset引用的其他Asset
- AssetDatabase.GetDependencies,可以获取直接或间接依赖, 这个操作比较耗时,可以优化Unity中资源依赖关系获取效率优化 - 知乎
- 获取一个Asset引用的其他Asset的Hash值
- AssetDatabase.GetAssetDependencyHash
- 获取一个Asset包含的ObjectIdentifier
- ContentBuildInterface.GetPlayerObjectIdentifiersInAsset
- 获取一个或一系列Object引用的其他Object
- ContentBuildInterface.GetPlayerDependenciesForObjects
- 获取一个Object中包含的Type
- ContentBuildInterface.GetTypesForObject
- 获取一个Asset在目标平台上的Object,例如有些Object只在Editor上用,打包时要剔除
- ContentBuildInterface.GetPlayerAssetRepresentations
- 计算Asset的使用情况,例如是否使用光照、雾效、阴影等,结果放在BuildUsageTagSet中
- ContentBuildInterface.CalculateBuildUsageTags
【什么是增量构建】
增量是缓存的一种方式,每次在已有缓存的基础上进行构建,就是增量构建。一般的缓存,例如对象池等,其数据还在内存中,增量时缓存数据要保存在磁盘中,构建时再加载到内存中。
【如何读代码】
我们看到的代码是完整的增量构建代码,但我们读代码时要分开来读。
构建是基础的,增量是新加的。要先看如何构建的,再看如何做增量的。
看构建时,要看:输入数据从哪来、输入数据如何使用(即输出数据从哪来)、输出数据是什么
看增量时,要看:缓存数据从哪来如何保存、缓存数据如何读取、缓存数据如何使用
总之要分开多个步骤跳着看,不要从方法的第一行顺着看到最后一行,很容易晕,而且也看不懂。
【CalculateSceneDependencyData】
输入数据从哪来
要计算场景的依赖,我们可以配置决定要计算哪些场景,配置好后会被保存到m_Content.Scenes
输入数据如何使用
这里就是如何计算得到场景的依赖数据,核心是调用
ContentBuildInterface.CalculatePlayerDependenciesForScene(scenePath, settings, usageTags, m_DependencyData.DependencyUsageCache);
返回值是SceneDependencyInfo结构体,其包含四个字段:
- internal string m_Scene; 场景的名字
- internal ObjectIdentifier[] m_ReferencedObjects;依赖的物体
- internal Type[] m_IncludedTypes;包含的类型
- internal BuildUsageTagGlobal m_GlobalUsage; 使用的类型
依赖分为递归和非递归两种,递归就是要依次获取所有的依赖,非递归只要获取直接依赖。
输出数据是什么
- 得到所有Scene的依赖数据,放入到BuildDependencyData中,其是该Task的输出Data
- m_DependencyData.SceneInfo.Add(asset, sceneInfo);
缓存数据如何获取
同一个对象,在不同的系统中会有不同的表示,也即会有不同的类,类中会有些相同的字段来表示同一个对象。
在构建中,场景的依赖数据放在SceneDependencyInfo中,每个Object对应一个 ObjectIdentifier
在增量中,场景的缓存数据放在CachedInfo中,每个Asset对应一个CacheEntry
缓存数据获取代码在if (uncachedInfo != null)中:
- 通过AssetDatabase.GetDependencies获取某个场景依赖的Asset
- 得到依赖中类型为prefab的CacheEntry
- 计算所有CacheEntry的hash128,通过其了解场景的依赖是否发生变化
- 计算得到该场景对应的CachedInfo
- 保存所有场景的CachedInfo
缓存数据如何保存
保存代码为:m_Cache.SaveCachedData(uncachedInfo)
实现在 BuildCache.SaveCachedData中,用多线程进行保存的,用多线程是为了加速。
这里需要保存成文件,要有输入路径,也是可以配置的,默认是"Library/BuildCache"
缓存数据如何读取
读取代码为:m_Cache.LoadCachedData(entries, out cachedInfo);
要看懂数据如何读取/导入,要先看数据如何保存/导出
缓存数据如何使用
缓存数据必然包含输出数据,一般而言,就是直接拿来用,复杂些需要做个转换。这里直接拿来用即可。
使用缓存数据的核心问题是缓存的数据是不是最新的,有效的。因此,在使用缓存数据前,必须要有个方式判断缓存数据是否有效。
这里的方式是如果引用的Object的类型是Sprite,那么重新计算场景的依赖,得到新的ObjectIdentifier,对比前后的ObjectIdentifier以判断缓存数据是否有效。
【增量的数据结构】
CacheEntry:
- public GUID Guid //该资源的GUID
- public int Version //该资源的版本号,一般是1
- public EntryType Type //该资源的类型,分为Asset\Data\File\ScriptType 四种,一般为Asset
- public Hash128 Hash
- internal InclusionType Inclusion //显式还是隐式包含,明确配置的要收集的Asset是显式的,没有被配置但被配置的引用的Asset是隐式的
- public string File //文件类型时文件的名字
- public string ScriptType //脚本类型时脚本的名字
CachedInfo
- public CacheEntry Asset 自身的CacheEntry
- public CacheEntry[] Dependencies依赖的对应的CacheEntry
- public object[] Data其他附加信息,这里用个Object[]数组来统合不同情况的数据,类似一个object类型的参数,很常见的处理方式。场景的Data为:
- SceneDependencyInfo
- BuildUsageTagSet
- prefabDependency的Hash128
- List<ObjectTypes> 每个Object所涉及的Type,即哪些脚本类
【CalculateAssetDependencyData】
构建时
将输入和输出数据又做了一层封装,封装成TaskInput和TaskOutput,感觉没有必要。
- 输入数据就是要收集的所有Asset:m_Content.Assets
- 计算该Asset的依赖数据
- var includedObjects = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset
- var referencedObjects = ContentBuildInterface.GetPlayerDependenciesForObjects
- 计算Object的依赖的Object数据
- 计算对象构建使用情况
- ContentBuildInterface.CalculateBuildUsageTags
- 对于Sprite类型的资源,生成SpriteImporterData
- 获取Asset的AssetRepresentations放入ExtendedAssetData,剔除Editor上的Object
- ContentBuildInterface.GetPlayerAssetRepresentations
- 得到输出数据
- 所有Asset的依赖数据,放入到BuildDependencyData中
- m_DependencyData.AssetInfo.Add(o.asset, o.assetInfo);
- 所有Object的依赖数据,放入ObjectDependencyData中
- m_ObjectDependencyData.ObjectDependencyMap[objectDependencyInfo.Object] = objectDependencyInfo.Dependencies;
- 所有Sprite的数据,放入BuildSpriteData中
- m_SpriteData.ImporterData.Add(o.asset, o.spriteData)
- 所有平台相关的数据,放入BuildExtendedAssetData中
- m_ExtendedAssetData.ExtendedData.Add(o.asset, o.extendedData);
- 所有的使用情况数据,放入BuildDependencyData中
- m_DependencyData.AssetUsage.Add(assetOutput.asset, assetOutput.usageTags);
- 所有Asset的依赖数据,放入到BuildDependencyData中
增量时
- 获取缓存数据GetCachedInfo,同样是CacheInfo这个数据结构
- public CacheEntry Asset //自身的CacheEntry
- public CacheEntry[] Dependencies //依赖的Asset的CacheEntry,可以通过依赖的ObjectObjectIdentifier的GUID找到依赖的Asset
- 自定义的数据,有些冗余
- BuildUsageTagSet
- SpriteImporterData
- ExtendedAssetData
- List<ObjectTypes>
- List<ObjectDependencyInfo>
- AssetLoadInfo 类同SceneDependencyInfo
- internal GUID m_Asset; 自身的GUID
- internal string m_Address 自身的路径
- internal List<ObjectIdentifier> m_IncludedObjects; 包含的Object
- internal List<ObjectIdentifier> m_ReferencedObjects; 引用的所有Object
- 缓存数据的保存、读取和使用类同场景