Unity Scriptable Build Pipeline源码学习笔记(1)-总体架构,依赖分析接口
Addressable底层打包是由Scriptable Build Pipeline(简称SBP)实现的,为了更好的使用Addressable,所以有必要学习下SBP。
0x0 总体流程篇
Pipeline设计模式
总体来看,SBP使用了pipeline模式,将打包分过程成若干个IBuildTask步骤,然后依次执行,因此如果需要增加额外功能,可以实现自己的IBuildTask。
断点后可以看到默认流程使用了21个步骤(有点复杂),最终完成了打包工作。
0x1 新的打包接口ContentBuildInterface
具体来看SBP中IBuildTask通过使用ContentBuildInterface类来实现具体功能,大致包括:
接口名 | 功能 |
---|---|
GetPlayerObjectIdentifiersInAsset | 传入guid,返回Asset资源内所有的ObjectIdentify信息 |
GetPlayerDependenciesForObject | 查询Asset内部的Object所依赖的其他对象列表 |
WriteSerializedFile | 保存AssetBundle文件 |
一句话文档,Unity的传统艺能
查看文档发现,只有一句话,基本不知道是干嘛用的,所以还是需要配合断点研究
0x2 依赖分析步骤研究
SBP中CalculateCustomDependencyData实现了对Asset进行依赖分析的功能。这个步骤主要使用了ContentBuildInterface提供的2个接口,
1.GetPlayerObjectIdentifiersInAsset
2.GetPlayerDependenciesForObject
为了更好的断点这2个接口,我们尝试脱离SBP做测试,直接调用2个接口打印具体的信息是什么。
GetPlayerObjectIdentifiersInAsset返回YAML结构内的序列化对象ObjectIdentifier
先写结论,GetPlayerObjectIdentifiersInAsset返回Asset文件YAML级别的序列化对象,该对象用ObjectIdentifier类表示
具体实验步骤如下:
1.新建一个Cube,一个Mateial,使用一张Texture,保存为Prefab作为测试用的Asset。
2.实现一个Prefab信息查看窗口
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build.Content;
using UnityEngine;
namespace Yaojz
{
public class AssetTestWindow:EditorWindow
{
private Object _asset;
private List<ObjectIdentifier> _objectIdentifiers = new List<ObjectIdentifier>();
[MenuItem("yaojz/testAsset")]
public static void Open()
{
var win = CreateInstance<AssetTestWindow>();
win.Show();
}
private void OnGUI()
{
_asset = EditorGUILayout.ObjectField(_asset, typeof(Object));
if (GUILayout.Button("show"))
{
GetAssetInfo();
}
foreach (var objId in _objectIdentifiers)
{
var localId = objId.localIdentifierInFile.ToString();
EditorGUILayout.LabelField("guid", objId.guid.ToString());
EditorGUILayout.LabelField("localId", localId);
EditorGUILayout.LabelField("filepath", objId.filePath);
EditorGUILayout.LabelField("FileType", objId.fileType.ToString());
}
}
private void GetAssetInfo()
{
//获得prefab的guid
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(_asset, out var guid, out long localId);
//传入prefab的guid
var ids = ContentBuildInterface.GetPlayerObjectIdentifiersInAsset(new GUID(guid), BuildTarget.Android);
_objectIdentifiers.Clear();
foreach (var id in ids)
{
_objectIdentifiers.Add(id);
}
}
}
}
Asset中的对象信息ObjectIdentifier
通过上面的测试结果对比Prefab的文件内容,可以知道ObjectIdentifier的含义是Asset内的可序列化对象(Object)的信息,这里打印出来的Ojbect对象是Component对象和GameObject对象。
查看prefab序列化文件我们可以看到,2514506536740829174这个id对应的类型是Transform,而ObjectIdentifier中的fileType显示结果是MetaAssetType,暂时不知道是什么含义,我希望获取Transform类型,那么应当如何获得ObjectIdentifier对应的Type呢?
通过ContentBuildInterface.GetTypeForObject获得ObjectIdentifier的Type信息
调用返回数组或者者单个结果的接口中的任意一个,都可以获得ObjectIdentifier的Type
//数组版本
var types = ContentBuildInterface.GetTypeForObjects(ids);
//单个结果版本
var type = ContentBuildInterface.GetTypeForObject(ids[0]);
通过ContentBuildInterface.GetPlayerDependenciesForObject获取依赖对象列表
下面实现一个打印结果的log函数,这个函数需要3个参数,另外2个参数是BuildTarget和TypeDB
BuildTarget我们可以选一个,比如Android,那么TypeDB从哪里来呢
private void LogDependency(ObjectIdentifier id)
{
var depIds = ContentBuildInterface.GetPlayerDependenciesForObject(id,_buildTarget,_typeDB);
foreach (var depId in depIds)
{
Debug.Log(depId);
}
}
TypeDB 类型数据库
在BuildPlayerScripts类中,通过调用PlayerBuildInterface.CompilePlayerScripts我们可以获得TypeDB对象
CompilePlayerScripts的作用是编译所有脚本代码。
根据ChatGPT的回答,TypeDB(类型数据库)是一个用于存储所有类型信息的数据库。TypeDB包含了Unity中所有的类、结构体、枚举、委托等类型的信息,它是Unity的一个核心组件,可以用来进行反射和序列化等操作。
TypeDB中存储的信息包括类型的名称、命名空间、父类、成员变量、方法等信息。这些信息可以通过C#代码或Unity编辑器中的各种工具来访问和修改。
而官方文档描述只有一句话:用于保存有关脚本类型和属性数据的信息的容器。
因此,在调用GetPlayerDependenciesForObject之前,我们调用CompilePlayerScripts来获得TypeDB对象
private void BuildScripts()
{
var rlt = PlayerBuildInterface.CompilePlayerScripts(_settings, ContentPipeline.kScriptBuildPath);
_typeDB = rlt.typeDB;
}
依赖关系接口ContentBuildInterface.GetPlayerDependenciesForObject与AssetDatabase.GetDependencies的性能比较
在SBP问世之前,我们通过AssetDatabase.GetDependencies来查询资源的依赖关系,ContentBuildInterface提供的接口性能更高,100000次循环2个接口大致差10倍,我的笔记本测试为4s和63s
private int _testCount = 100000;
private void Test(ObjectIdentifier id)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i <_testCount ; i++)
{
var depIds = ContentBuildInterface.GetPlayerDependenciesForObject(id,_buildTarget,_typeDB);
}
stopwatch.Stop();
Debug.Log($"sec:{stopwatch.Elapsed.TotalSeconds}");
}
private void Test2()
{
var path = AssetDatabase.GetAssetPath(_asset);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < _testCount; i++)
{
var rlt = AssetDatabase.GetDependencies(path);
}
stopwatch.Stop();
Debug.Log($"sec:{stopwatch.Elapsed.TotalSeconds}");
}