Unity Scriptable Build Pipeline源码学习笔记(1)-总体架构,依赖分析接口

Addressable底层打包是由Scriptable Build Pipeline(简称SBP)实现的,为了更好的使用Addressable,所以有必要学习下SBP。

0x0 总体流程篇

Pipeline设计模式

总体来看,SBP使用了pipeline模式,将打包分过程成若干个IBuildTask步骤,然后依次执行,因此如果需要增加额外功能,可以实现自己的IBuildTask。

断点后可以看到默认流程使用了21个步骤(有点复杂),最终完成了打包工作。

0x1 新的打包接口ContentBuildInterface

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}");
}

posted @ 2023-05-17 18:30  jeoyao  阅读(1717)  评论(0编辑  收藏  举报