Unity构建详解(3)——SBP的依赖计算

发布于:2024-03-28 ⋅ 阅读:(16) ⋅ 点赞:(0)

【前置知识】

先要搞清楚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
  • 获取一个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);
增量时
  • 获取缓存数据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
  • 缓存数据的保存、读取和使用类同场景
本文含有隐藏内容,请 开通VIP 后查看