Hybird热更预研汇总(0.8.0)

(代号wolong)是一个特性完整、零成本、高性能、低内存近乎完美的Unity全平台原生c#热更方案。

HybridCLR扩充了il2cpp的代码,使它由纯AOT (opens new window)runtime变成‘AOT+Interpreter’ 混合runtime,进而原生支持动态加载assembly,使得基于il2cpp backend打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以AOT+interpreter混合模式执行。从底层彻底支持了热更新。

注释:

AOT,Ahead Of Time,指运行前编译。JIT,即Just-in-time,动态(即时)编译,边运行边编译。

IL2cpp:Unity公司就自行研发了IL2cpp,把本来应该再mono的虚拟机上跑的中间代码转换成cpp代码,这样再把生成的cpp代码,利用c++的跨平台特性,在各个平台上通过对各平台都有良好优化的native c++编译器编译,以获得更高的效率和更好的兼容性。

特性。

1.特性完整,近乎完整实现了ECMA-335规范。除了 下文中"限制和注意事项" 之外的特性都支持。

2.零学习和使用成本。 HybridCLR将纯AOT runtime增强为完整的runtime,使得热更新代码与AOT代码无缝工作。脚本类与AOT类在同一个运行时内,可以随意写继承、反射、多线程(volatile、ThreadStatic、Task、async)之类的代码。不需要额外写任何特殊代码、没有代码生成,也没有什么特殊限制。

3.执行高效。实现了一个极其高效的寄存器解释器,所有指标都大幅优于其他热更新方案

4.内存高效。 热更新脚本中定义的类跟普通c#类占用一样的内存空间,远优于其他热更新方案。

5.原生支持hotfix修复AOT部分代码。几乎不增加任何开发和运行开销。

限制和注意事项:(后面补充)

与其他流行的c#热更新方案的区别

HybridCLR是原生的c#热更新方案。通俗地说,il2cpp相当于mono的aot模块,HybridCLR相当于mono的interpreter模块,两者合一成为完整mono。HybridCLR使得il2cpp变成一个全功能的runtime,原生(即通过System.Reflection.Assembly.Load)支持动态加载dll,从而支持ios平台的热更新。

其他热更新方案则是独立vm,与il2cpp的关系本质上相当于mono中嵌入lua的关系。因此类型系统不统一,为了让热更新类型能够继承AOT部分类型,需要写适配器,并且解释器中的类型不能为主工程的类型系统所识别。特性不完整、开发麻烦、运行效率低下。

稳定性状况

目前PC(x86及x64)、macOS(x86、x64、Arm64)、Android(arm v7及v8)、iOS(64bit) 可稳定使用。已经有数百个大中小型商业游戏项目完成接入,其中一些在紧锣密鼓作上线前测试。

Hybrid导入

  1. 打开PackageManager 通过 Add package from git URL 添加 https://github.com/focus-creative-games/hybridclr_unity.git或者下载到本地, 通过 Add package from disk 导入插件。

  1. HybridCLR ---- Installer 导入安装 (当前项目2020.3.29f1可直接点击安装,更低版本需要手动操作)

3.PlayerSetting配置

  • 关闭增量式GC(Use Incremental GC) 选项。因为目前不支持增量式GC

  • Scripting Backend 切换为 il2cpp

  • Api Compatability Level 切换为 .Net 4 or .Net Framework (打主包时可以使用.net standard,但使用脚本Compile热更新dll时必须切换到.Net 4.x or .Net Framework

4.热更新模块拆分

很显然,项目必须拆分为AOT(即编译到游戏主包内)和热更新 assembly,才能进行热更新。HybridCLR对于 怎么拆分程序集并无任何限制,甚至你将AOT或者热更新程序集放到第三方工程中也是可以的。

常见的拆分方式有几种:

  • 使用Unity支持的Assembly Definition将整个项目拆分为多个程序集,Assembly-CSharp作为AOT程序集,不引用任何热更新程序集的代码

  • 将AOT部分拆分为一个或多个程序集,Assembly-CSharp作为热更新程序集,同时还有其他0-N个热更新程序集。

无论哪种拆分方式,正确设置好程序集之间的引用关系即可。

点击菜单 HybridCLR/Settings 打开配置界面。

对于项目中的热更新程序集,如果是assembly definition(asmdef)定义的程序集,加入 hotUpdateAssemblyDefinitions列表,如果是普通dll,则将程序集名字(不包含'.dll'后缀,如Main、Assembly-CSharp)加入hotUpdateAssemblies即可。这两个列表是等价的,不要重复添加,否则会报错。

Hybrid - PC平台测试

1.安装Addressables Window - PackageManager - Package:Unity Registry - 找到Addressables

2.安装Python2.7

3.VS安装C++开发支持 (VS - 工具 - 获取工具和功能)

4.Gradle5.6.4 升级到Gradle 6.1.1

2020.3.29版本测试(新建测试工程Hybrid_Kane,仿照示例工程进行设置)

HybridCLR - Build - Win64 打包成功后,运行成功!

新增热更代码Debug.Log("我热更了一句代码")!

HybridCLR - BuildBundles - BuildAll_ActiveBuildTarget

HybridCLR - Build - BuildAssetsAndCopyToStreamingAssets

手动拷贝编辑器工程StreamingAssets文件到打包后工程StreamingAssets

可见"我热更了一句代码" 至此PC版本测试成功!

Hybrid - Android测试

出包测试:

  1. 新建

Main.dll 以及包含的LoadDll.cs负责加载热更dll以及aot-dll.

LoadDll 逻辑 加载dll - aot加载原始metadata - 加载热更dll。(基础模块,不能热更)

  1. 新建Editor - HybridCLRBuilder 负责打包 热更dll以及aot-dll流程处理。

HybridCLRBuilder 逻辑 - 同步热更dll - 打包生成裁剪dll - 清空临时文件夹 - Hybrid 代码Generate生成 - 编写对应平台dll - 打包hybrid特殊ab - 拷贝aot和hotupdate的dll.

  1. Unity出包Build , 生成apk ,运行成功!

热更测试(接入Dev出包流程):

  1. LoadDll后执行项目第一个启动文件 (通过加载 hybrid的ab XXX.prefab)

(1)加载逻辑,先读外部目录(persistentDataPath),不存在的话,读内部目录(streamingAssetsPath)

  1. 打包 在“构建资源AssetBundle”后

  1. 执行 HybridCLRBuilder的流程,资源根据项目放到basic文件夹下

  1. 内置资源处理

  1. 将hybrid特殊ab 和 aot 以及hotupdate的dll 添加到内置资源中。

检查引用丢失(注意事项):

Hybrid与XLua交互:

1.在打包时,先生成wrapper文件,再将文件生成到热更新模块中。

2.因为xlua生成的代码全在全局的Assembly-CSharp里,甚至做成partial类与Runtime代码关联,因此需要做代码 调整.配合热更新操作。(将xLua原生代码拆分成两部分,编辑器脚本放在主工程,其他部分放在Hotfix工程)

经测试打包,新增方法,Xlua获取新增C#方法均实现。

性能测试:

测试用例:

local function test0()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 0)
    local cnt = CS.HybridHotUpdateTool.times * 1000

    local go = CS.UnityEngine.GameObject("_")
    local transform = go.transform

    for i = 1, cnt do
        transform.position = transform.position
    end

    CS.UnityEngine.GameObject.Destroy(go)
    CS.HybridHotUpdateTool.EndTime()
end

local function test1()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 1)
    local cnt = CS.HybridHotUpdateTool.times * 100

    local go = CS.UnityEngine.GameObject("_")
    local transform = go.transform

    for i = 1, cnt do
        transform:Rotate(CS.UnityEngine.Vector3.up, 1)
    end

    CS.UnityEngine.GameObject.Destroy(go)
    CS.HybridHotUpdateTool.EndTime()
end

local function test2()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 2)
    local cnt = CS.HybridHotUpdateTool.times * 1000

    local go = CS.UnityEngine.GameObject("_")
    local transform = go.transform

    for i = 1, cnt do
        local tmp = CS.UnityEngine.Vector3(i, i, i)
        local x = tmp.x
        local y = tmp.y
        local z = tmp.z
        local r = x + y * z
    end
    CS.HybridHotUpdateTool.EndTime()
end

local function test3()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 3)
    local cnt = CS.HybridHotUpdateTool.times * 10
    for i = 1, cnt do
        local tmp = CS.UnityEngine.GameObject("___")
        CS.UnityEngine.GameObject.Destroy(tmp)
    end
    CS.HybridHotUpdateTool.EndTime()
end

local function test4()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 4)
    local cnt = CS.HybridHotUpdateTool.times * 10
    for i = 1, cnt do
        local tmp = CS.UnityEngine.GameObject("___")
        tmp:AddComponent(typeof(CS.UnityEngine.SkinnedMeshRenderer))
        local c = tmp:GetComponent(typeof(CS.UnityEngine.SkinnedMeshRenderer))
        c.receiveShadows = false
        CS.UnityEngine.GameObject.Destroy(tmp)
    end
    CS.HybridHotUpdateTool.EndTime()
end

local function test5()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 5)
    local cnt = CS.HybridHotUpdateTool.times * 1000
    for i = 1, cnt do
        local tmp = CS.UnityEngine.Input.mousePosition;
    end
    CS.HybridHotUpdateTool.EndTime()
end

local function test6()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 6)
    local cnt = CS.HybridHotUpdateTool.times * 1000
    for i = 1, cnt do
        local tmp = CS.UnityEngine.Vector3(i, i, i)
        CS.UnityEngine.Vector3.Normalize(tmp)
    end
    CS.HybridHotUpdateTool.EndTime()
end

local function test7()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 7)
    local cnt = CS.HybridHotUpdateTool.times * 100
    for i = 1, cnt do
        local t1 = CS.UnityEngine.Quaternion.Euler(i, i, i)
        local t2 = CS.UnityEngine.Quaternion.Euler(i * 2, i * 2, i * 2)
        CS.UnityEngine.Quaternion.Slerp(t1, t2, CS.UnityEngine.Random.Range(0.1, 0.9))
    end
    CS.HybridHotUpdateTool.EndTime()
end

local function test8()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 8)
    local cnt = CS.HybridHotUpdateTool.times * 10000
    local total = 0
    for i = 1, cnt do
        total = total + i - (i / 2) * (i + 3) / (i + 5)
    end
    CS.HybridHotUpdateTool.EndTime()
end

local function test9()
    CS.HybridHotUpdateTool.StartTime("lua_test" .. 9)
    local cnt = CS.HybridHotUpdateTool.times * 1000
    for i = 1, cnt do
        local tmp0 = CS.UnityEngine.Vector3(1, 2, 3)
        local tmp1 = CS.UnityEngine.Vector3(4, 5, 6)
        local tmp2 = tmp0 + tmp1
    end
    CS.HybridHotUpdateTool.EndTime()
end
private static void Test0()
    {
        StartTime("CS_Test" + 0);
        var go = new GameObject("t");
        var transform = go.transform;

        var cnt = times * 1000;
        for (var i = 0; i < cnt; i++)
        {
            transform.position = transform.position;
        }

        UnityEngine.Object.Destroy(go);
        EndTime();
    }

    private static void Test1()
    {
        StartTime("CS_Test" + 1);
        var go = new GameObject("t");
        var transform = go.transform;

        var cnt = times * 100;
        for (var i = 0; i < cnt; i++)
        {
            transform.Rotate(Vector3.up, 1);
        }

        UnityEngine.Object.Destroy(go);
        EndTime();
    }

    private static void Test2()
    {
        StartTime("CS_Test" + 2);
        var cnt = times * 1000;
        for (var i = 0; i < cnt; i++)
        {
            var v = new Vector3(i, i, i);
            var x = v.x;
            var y = v.y;
            var z = v.z;
            var r = x + y * z;
        }
        EndTime();
    }

    private static void Test3()
    {
        StartTime("CS_Test" + 3);
        var cnt = times * 10;
        for (var i = 0; i < cnt; i++)
        {
            var go = new GameObject("t");
            UnityEngine.Object.Destroy(go);
        }
        EndTime();
    }

    private static void Test4()
    {
        StartTime("CS_Test" +4);
        var cnt = times * 10;
        for (var i = 0; i < cnt; i++)
        {
            var go = new GameObject();
            go.AddComponent<SkinnedMeshRenderer>();
            var c = go.GetComponent<SkinnedMeshRenderer>();
            c.receiveShadows = false;
            UnityEngine.Object.Destroy(go);
        }
        EndTime();
    }

    private static void Test5()
    {
        StartTime("CS_Test" + 5);
        var cnt = times * 1000;
        for (var i = 0; i < cnt; i++)
        {
            var p = Input.mousePosition;
        }
        EndTime();
    }

    private static void Test6()
    {
        StartTime("CS_Test" + 6);
        var cnt = times * 1000;
        for (var i = 0; i < cnt; i++)
        {
            var v = new Vector3(i, i, i);
            Vector3.Normalize(v);
        }
        EndTime();
    }

    private static void Test7()
    {
        StartTime("CS_Test" + 7);
        var cnt = times * 100;
        for (var i = 0; i < cnt; i++)
        {
            var q1 = Quaternion.Euler(i, i, i);
            var q2 = Quaternion.Euler(i * 2, i * 2, i * 2);
            Quaternion.Slerp(Quaternion.identity, q1, 0.5f);
        }
        EndTime();
    }

    private static void Test8()
    {
        StartTime("CS_Test" + 8);
        long total = 0;
        var cnt = times * 10000;
        for (var i = 0; i < cnt; i++)
        {
            total = total + i - (i / 2) * (i + 3) / (i + 5);
        }
        EndTime();
    }

    private static void Test9()
    {
        StartTime("CS_Test" + 9);
        var cnt = times * 1000;
        for (var i = 0; i < cnt; i++)
        {
            var a = new Vector3(1, 2, 3);
            var b = new Vector3(4, 5, 6);
            var c = a + b;
        }
        EndTime();
    }

测试结果:

Hybrid - IOS测试:

libil2cpp.a 编译

确保你的macOS版本>=12以及xcode版本>=13

点击菜单 生成所有必要的文件

HybridCLRData/Generate/All

安装Camke

brew install cmake

坑点(需要优化,处理):

  1. Did you #ifdef UNITY_EDITOR a section of your serialized properties in any of your scripts?

热更dll中,不能用 UNITY_EDITOR 宏, compile之后,已经没有所谓的宏了。

  1. dll裁剪生成导出是在打包时,所以需要二次打包(要整理出包流程,当前是用二次出包,需要优化)

  1. 暂不支持:

  1. GetComponent(string name)方式无法获得组件(PC,和真机不一致,打包后报错风险)

  1. IOS打包需要生成并替换Xcode工程中的libil2cpp.a文件。

  1. 某些Unity版本需要手动修改Unity编辑器相关dll.(目前2020.3.29不需要)

  1. Unity 安装目录必须包含版本号,否则无法识别版本。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值