当前位置:   article > 正文

HybridCLR 探索

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.

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/686985
推荐阅读
相关标签
  

闽ICP备14008679号