• HybridCLR 示例项目简析


    重要提醒:
    耐心了解基础理论知识,是准确、快速接入的前提条件!
    磨刀不误砍柴工。

    .NET相关概念_NRatel的博客-CSDN博客

    Unity 中的 .NET、Mono 和 IL2CPP_NRatel的博客-CSDN博客

    关于HybridCLR | Focus Creative Games

    一、示例项目 hybridclr_trial(旧)

    1、下载并按照指引操作

    指引:HybridCLRData/README.md
    (目的是让Unity 使用 hybridclr 自己实现的 libil2cpp)

        - 酌情修改 init_local_il2cpp_data.bat(或.sh)文件中代码
        - `set IL2CPP_BRANCH=2020.3.33` 改成你的版本(目前只有2020.3.33或2021.3.1)
        - `set IL2CPP_PATH=<你的Unity editor的il2cpp目录的路径>` 改成你的Unity安装目录
        - 运行 init_local_il2cpp_data.bat 或.sh 文件 创建本地il2cpp目录,即 LocalIl2CppData 目录。

        看到如下表示成功:
        Cloning into 'hybridclr_repo'...
        Cloning into 'il2cpp_plus_repo'...
        ...
        succ

        若出现:'git' 不是内部或外部命令,也不是可运行的程序或批处理文件。 
        下载安装git:Git for Windows

    2、运行

    ⑴、LoaddDll.cs 中:

    DownLoadDlls ==》LoadGameDll ==》RunMain;
    (下载热更Dll ==》加载热更Dll ==》运行)

    ⑵、App.cs 的 Main 方法中:

    LoadMetadataForAOTAssembly ==》TestAOTGeneric;
    (为AOT程序集加载元数据 ==》 测试AOT泛型正常运行)

    3、代码文件简析

    ⑴、RuntimeApi.cs 

    主要引入 C++ 程序集的方法 LoadMetadataForAOTAssembly(为AOT程序集加载元数据)

    ⑵、RefTypes.cs 

    避免 反射用到的代码 和 值类型泛型 被裁剪/剥离。
    具体方式为:
    ①、使用 Preserve 特性
    ②、在方法中使用,但方法不被实际调用(仅为了告诉编译器有用,不要裁剪)
    ③、还可使用 link.xml 文件。
    -----------------------------------
    (https://focus-creative-games.github.io/hybridclr/code_striping/)
    (https://focus-creative-games.github.io/hybridclr/aot_generic/)

    ⑶、BuildConfig.cs

    配置 MonoHotUpdateDllNames、AllHotUpdateDllNames、AOTMetaDlls、AssetBundleFiles、各阶段输入输出路径等。

    ⑷、AssetBundleBuildHelper.cs

    提供各平台(Win64、Win32、Android、IOS)构建 AssetBundle 的接口方法。
    ①、编译HotFixDlls
    ②、将HotFixDlls、AOTMetaDlls(需要提前BuildPlayer生成)、testPrefab(测试AOT补充元数据的预设)编入 notSceneAb 中。
        (notSceneAb 是名为 common 的 AssetBundleBuild)
        (ab 内的 assetNames 取各资源的相对路径)
    ③、调用 BuildAssetBundles(string outputPath, AssetBundleBuild[] builds, ...) 构建 abs。(注意,并非全量接口)
    ④、将 abs 拷入 Application.streamingAssetsPath。

    ⑸、BuildPlayerHelper.cs

    提供各平台(Win64、Win32、Android、IOS)构建 App 的接口方法。
    最终调用 BuildPipeline.BuildPlayer。
    注意,移动平台需要调用两次,第1次 Build App 是为了生成补充AOT元数据dll。

    ⑹、CompileDllHelper.cs

    提供各平台(Win64、Win32、Android、IOS)编译 HotFixDlls 的方法。
    最终调用 PlayerBuildInterface.CompilePlayerScripts。

    ⑺、MethodBridgeHelper.cs

    提供各平台(Win64、Win32、Android、IOS)生成桥接函数的接口方法。
    (桥接函数:HybridCLR 的 interpreter 与 AOT 之间需要双向函数调用)
    (文档:https://focus-creative-games.github.io/hybridclr/method_bridge/)

    ⑻、BuildProcessor_2019.cs

        在 OnFilterAssemblies 中,将热更dll从打包列表中移除。
        在 OnPostprocessBuild 中(非安卓时),加回在OnFilterAssemblies 中移除的条目。
        在 OnPostGenerateGradleAndroidProject(安卓时),加回在OnFilterAssemblies 中移除的条目。

    ⑼、BuildProcessor_2020_1_OR_NEWER.cs

        在 OnFilterAssemblies 中,将热更dll从打包列表中移除。
        在 OnPostprocessBuild 中(非安卓时),加回在OnFilterAssemblies 中移除的条目。
        在 OnPostGenerateGradleAndroidProject(安卓时),加回在OnFilterAssemblies 中移除的条目。

        在 OnBeforeConvertRun(仅2020中),拷贝 StripDlls。
      (例安卓,从 Application.dataPath/../Library/Bee/artifacts/Android/ManagedStripped 拷贝到 Application.dataPath/../HybridCLRDataDir/AssembliesPostIl2CppStrip)

    ⑽、Generators 目录:生成桥接函数的库方法。

    ⑾、UnityBinFileReader 目录:Unity二进制文件的读写工具。

    二、示例项目(新)(2022/10/19)

    整体更方便易用了。

    1、HybridCLR 整体改为通过 unitypackage 放入Unity。
        编译 Dll、生成桥接函数已内置到其中。

    2、安装入口改为了 Untiy菜单栏 HybridCLR/Installer... 。

    3、配置方式改为了 Untiy菜单栏 HybridCLR/Settings。

    4、使用上还是 下载 Dlls 和 预设ab、加载Dlls、实例化预设还原成功。但 Dlls 不再打入ab中。(Dll 本来就是序列化文件,可以不打AB)

    5、BuildAssetsCommand.cs:对外提供方法 BuildAndCopyABAOTHotUpdateDlls

    ①、为 activeBuildTarget 打资源 ab。
    ②、拷贝 资源ab 到 Application.streamingAssets。
    ③、为 activeBuildTarget 编译 Dll。
    ④、拷贝 AOTAssemblies 到 Application.streamingAssets。
    ④、拷贝 HotUpdateAssemblies 到 Application.streamingAssets。

    6、BuildPlayerCommand.cs:对外提供方法 Build_Win64

    ①、执行 PrebuildCommand.GenerateAll; 包括 link.xml、MethodBridge、AOTGenericReferences、ReversePInvokeWrapper。
    ②、执行 BuildPipeline.BuildPlayer
    ③、执行 BuildAssetsCommand.BuildAndCopyABAOTHotUpdateDlls(就是上面那5步)
    ④、将 Application.streamingAssets 整体复制到 Application.dataPath/../Release-Win64/HybridCLRTrial_Data/StreamingAssets。

    暂未提供 移动平台的构建方法。

    7、AOTGenericReferences.cs

    根据当前热更新 dll 扫描出所有产生的AOT泛型类型及函数的实例化,并生成一个启发的泛型实例化文件。

    自动生成的是注释的,可以不要,但影响性能。
    生成后应该 打开注释并挪走,不然会被覆盖。

    这个文件,本来不手加会在真机运行时报错(IL2CPP本身的机制)。
    但 HybridCLR 利用 “基于补充元数据的泛型函数实例化技术” 在解释器里去运行,避免报错。
    但 由于是解释执行,所以效率不高。
    对于一些性能敏感的代码,提前泛型实例化可以明显提升性能。

    三、实际接入要做什么?

    1、理解 代码裁剪AOT 泛型桥接函数 这些重要问题的概念和处理流程(理论基础)。

    代码裁剪问题 | Focus Creative Games

    AOT泛型问题 | Focus Creative Games

    桥接函数 | Focus Creative Games

    2、理解 AOT dll 和 热更 dll 的关系(理论基础)。

    AOT dll 内容主要包含:程序主入口支持资源和脚本热更的脚本理论上不会变化的工具、插件等热更 dll 需要 借助 AOT dll 才能完成热更。

    理论上,AOT dll 不能热更,若有变化,需要重新打包。

    但据Q群内消息(2022/11/1):HybridCLR开创性地实现了 `differential hybrid execution` 技术。即可以对AOT dll任意增删改,会智能地让变化或者新增的类和函数以interpreter模式运行,但未改动的类和函数以AOT方式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平。正在申请专利。

    这意味着,可以不用刻意划分 AOT dll 和 热更 dll(清晰起见,还是建议划分)

    以一个点为界,在 “某个方法” 调用之后,HybridCLR 会自动区分是否变化,以 AOT+interpreter 方式运行后续代码。(需要注意,“这个方法” 调用前的代码还是完全以 AOT 方式运行的。)

    这可以解决一个纠结点:“业务框架” 要放AOT dll里还是热更Dll里?想在AOT里也用业务框架的一部分,又怕业务框架在上线后有改动(大概率会有改动)。

    通常大家都会比较贪心!既希望一些底层代码能够被畅用(希望尽量放到AOT),又希望让更多的代码能够被热更(希望放到热更程序集)。很明显这是需要权衡的。

    3、理解 MonoBehaviour 的挂载/还原限制(理论基础)。

     热更新MonoBehaviour | Focus Creative Games

    AOT 资源挂热更程序集中的脚本,不能还原。
    因为它是逆向的、底层依赖高层的。
    ( 如下图:同级引用(水平方向)和 高引用低(斜向下)都是没问题的)

    4、实际接入。

    (按照文档:为现有项目添加HybridCLR支持 | Focus Creative Games)。

    ⑴、安装HybridCLR | Focus Creative Games

    ⑵、项目设置 | Focus Creative Games

    问题:【程序集划分】是否将 Assembly-CSharp 作为热更新程序集?

    需权衡! 它似乎会默认直接依赖所有导入Untiy的运行时程序集。
    这样的话,如果后期有人 直接 using 一个新插件的程序集,将不能在编辑器下发现,而是会在热更后出错。但不得不说,它是最简单方便的方式。

    ⑶、打包app(紫色为自己需要做的)

    HybridCLR打包工作流 | Focus Creative Games

    iOS平台打包 | Focus Creative Games

    ①、设置 UNITY_IL2CPP_PATH 环境变量。
    (若开启HybridCLR,打包时自动)
    ②、排除热更新 assembly。
    (根据设置,打包时自动)
    ③、将热更新 dll 名添加到 assembly 列表。
    (根据设置,打包时自动)
    ④、将打包过程中生成的裁剪后的 aot dll 拷贝出来,供补充元数据使用。
    (打包时会被自动复制到 HybridCLRData/AssembliesPostIl2CppStrip/{platform}目录)
    (应在热更打包资源时,将这些 dll 拷走)
    ⑤、编译热更新dll。
    (应在热更打包资源时执行编译,并将这些 dll 拷走)
    使用 Unity 的 PlayerBuildInterface.CompilePlayerScriptsApi 进行编译
    ⑥、生成一些打包需要的文件和代码。
    (应在打包时调用 PrebuildCommand.GenerateAll() 生成)
    (或 提前手动点击 HybridCLR/Generate/All 生成)
        其中包括:扫描生成 link.xml、生成桥接函数、生成 AOT 泛型实例化代码、生成ReversePInvokeCallback 相关 wrapper文件。
    ⑦、iOS平台的特殊处理。
    (打ios包前,需要自行手动替换xcode工程中的libil2cpp.a为扩充了HybridCLR代码libil2cpp.a)

    ⑷、将 热更新 dll(必选) 补充元数据AOT dll(可选) 纳入项目的热更新资源管理系统。

    热更时,将 热更Dll 和 AOT Dll 拷到自己的热更目录。
    使其可以上传到资源服务器,再更新到玩家设备。

    ⑸、加载补充元数据dll(可选)。

    主要方法:RuntimeApi.LoadMetadataForAOTAssembly(dllBytes);

    ⑹、加载热更新dll。

    主要方法:Assembly gameAss = System.Reflection.Assembly.Load(dllBytes.bytes);

    注意,应按照程序集依赖顺序进行加载(被依赖的先加载)。

    ⑺、开始执行热更新代码逻辑。

    加载包含热更新脚本的热更新场景或者实例化热更新prefab。
    Unity自动会创建相应的热更新脚本并执行脚本函数。
    这种办法使用简单,对项目改动最小,而且不会产生反射开销。(强烈推荐)

    四、C# 编译效率问题

    1、运行时触发编译卡死:
    修改 Preferences/General/ScriptChangesWhilePlaying

    2、脚本每次修改都触发资源刷新:
    修改 Preferences/AssetPipeline/AutoRefresh 为 false,
    然后使用 Ctrl + R 手动触发(Windows 下)

    3、尽量减少 dll 的编译时间:

    ⑴、将 第三方库 或 不常改动的代码放入 Standard Assets 或 Plugins.

    ⑵、尽可能多的使用自定义程序集。

  • 相关阅读:
    Spring-RabbitMQ 消费者消息确认案例实践
    GIT技巧
    mysql日志总结
    常用的工程测量仪器详细介绍
    云工作流 CloudFlow 重磅发布,流程式开发让云上应用构建更简单
    GRFB-UNet:一种新的多尺度注意力网络,用于铺路分割
    全国各区县日照时长数据,逐月数据均有!
    探究js数据类型和底层原理
    计算机网络(三):数据链路层
    Springcloud实战之自研分布式id生成器
  • 原文地址:https://blog.csdn.net/NRatel/article/details/127355276