Unity ILRuntime热更新基础入门(ILRuntime手记一)

news/2025/1/16 16:06:16/

2023.3.20简介

ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现快速方便可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新

ILRuntime的优势

同市面上的其他热更方案相比,ILRuntime主要有以下优点:

  • 无缝访问C#工程的现成代码,无需额外抽象脚本API

  • 直接使用VS2015进行开发,ILRuntime的解译引擎支持.Net 4.6编译的DLL

  • 执行效率是L#的10-20倍

  • 选择性的CLR绑定使跨域调用更快速,绑定后跨域调用的性能能达到slua的2倍左右(从脚本调用GameObject之类的接口)

  • 支持跨域继承

  • 完整的泛型支持

  • 拥有Visual Studio的调试插件,可以实现真机源码级调试。支持Visual Studio 2015 Update3、Visual Studio 2017、Visual Studio 2019和Visual Studio 2022

  • 支持VS Code源码级调试,支持Mac OSX

  • 最新的2.0版引入的寄存器模式将数学运算性能进行了大幅优化

C# vs Lua

目前市面上主流的热更方案,主要分为Lua的实现和用C#的实现,两种实现方式各有各的优缺点。

Lua是一个已经非常成熟的解决方案,但是对于Unity项目而言,也有非常明显的缺点。就是如果使用Lua来进行逻辑开发,就势必要求团队当中的人员需要同时对Lua和C#都特别熟悉,或者将团队中的人员分成C#小组和Lua小组。不管哪一种方案,对于中小型团队都是非常痛苦的一件事情。

用C#来作为热更语言最大的优势就是项目可以用同一个语言来进行开发,对Unity项目而言,这种方式肯定是开发效率最高的。

Lua的优势在于解决方案足够成熟,之前的C++团队可能比起C#,更加习惯使用Lua来进行逻辑开发。此外借助luajit,在某些情况下的执行效率会非常不错,但是luajit现在维护情况也不容乐观,官方还是推荐使用公版Lua来开发。

如果需要测试ILRuntime对比Lua的性能Benchmark,需要确认以下几点:

  • ILRuntime加载的dll文件是Release模式编译的

  • dll中对外部API的调用都进行了CLR绑定

  • 确保没有勾选Development Build的情况下发布成正式真机运行包,而不是在Editor中直接运行

  • 可以直接使用Demo工程中提供的性能测试进行对比

ILRuntime设计上为了在开发时提供更多的调试支持,在Unity Editor中运行会有很多额外的性能开销, 因此在Unity Editor中直接测试并不能代表ILRuntime的实际运行性能。

最新2.0版本的ILRuntime,加入了寄存器模式,在10多项测试用例当中的性能,均已超过lua53版xlua,详细测试代码可参见ILRuntime的U3D Demo工程以及视频教程

建议版本控制大于:2021.3.2 教程来源Unity

安装:

在 unity 中的: manifest.json下的第一行粘贴官方提供的地址

"scopedRegistries": [{"name": "ILRuntime","url": "https://registry.npmjs.org","scopes": ["com.ourpalm"]}
],

接着回到unity,到window->PacketageManager中找到ILRuntime

安装即可

接下来是设置:排除报错

开启:不安全代码选项:Allow 'unsafe' Code

安装完成!(以下步骤为官方描述)


然后通过Unity的Window->Package Manager菜单,打开Package Manager,将上部标签页选项选择为All Packages,Advanced里勾上Show Preview Packages,等待Unity加载完包信息,应该就能在左侧列表中找到ILRuntime,点击安装即可

部分Unity版本可以无法直接在列表中刷出ILRuntime,如果左边列表找不着,那就在项目的manifest.json中的dependencies段的开头,增加如下代码手动将ILRuntime添加进项目

"com.ourpalm.ilruntime": "1.6.0",

ILRuntime包安装完毕后,在Package Manager中选中ILRuntime, 右边详细页面中有Samples,点击右方的Import to project可以将ILRuntime的示例Demo直接导入当前工程。

示例导入工程后有可能因为没开启unsafe导致编译报错,可以在PlayerSettings中勾选Allow unsafe code解决编译问题。


Demo测试部署:

打开Demo文件夹:Demo->HotFix_Project~->HotFix_Project.sln,使用vs在侧栏右击选择生成即可完成自动部署

然后选择Demo中的第一个场景,有输出即为测试成功!

ILRuntime开始:

ILRuntime利用基本构成简单介绍,通过c#.Net建立dll文件,将他隐藏在unity工程文件中,通过www/UnityWebRequest进行流加载到unityMon中进行调用完成热更新。

基于c#断点特性实现断点方式.

通过建立AppDomain调取ILRuntime,利用携程方式进行加载读取内容。

读取完后在进程中完成进程ID属性对齐,告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler,完成正式运行

接着在方法中进行正常调用即可。这样就完成了初始化,在unity调取到了热更文件。

[unity文件夹隐藏方式:("名称"+"~")完成隐藏]

其他调取方式:

在unity中调取c#热更文件的方法

注意:IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];是可以复用的,为了分开书写,便于阅读故多次出现。具体可结合视频以及Demo

基础方法:

//类名 方法名 null null (进行调用)

第一个 null ,如果对象为静态static方法的话为null

如果不是则传:对象

第二个 null 传递的是这个方法的参数。如果存在多个参数,直接在后面添加","直接添加参数即可

示例如下:

        Debug.Log("调用无参数静态方法");//调用无参数静态方法,appdomain.Invoke("类名", "方法名", 对象引用, 参数列表);appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);//调用带参数的静态方法Debug.Log("调用带参数的静态方法");appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123);

存在的问题:

性能比较差,每次需要通过字符串寻找这个类的对象,寻找方法

比基础方法高效一点的:

通过ILRuntime的接口拿取方法。

通过appdomain应用程序域拿取,在LoadedTypes中保存了所有的已经加载ILRuntime的类型

再通过type.GetMethod反射拿到对应的方法 ,这里存在几个方式:

1、存在的方式的数量,该参数方式的数量

2、如果存在的数量一样,但类型不一致。则通过重载传递一个list,里面则包含具体参数的类型,还有泛型、返回值都可进行指定

         Debug.Log("通过IMethod调用方法");//预先获得IMethod,可以减低每次调用查找方法耗用的时间IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];//根据方法名称和参数个数获取方法IMethod method = type.GetMethod("StaticFunTest2", 1);appdomain.Invoke(method, null, 123);

问题:

appdomain.Invoke(method, null, 123);在这里可能会产生了频繁的装箱拆箱,导致出现GC垃圾

解决上方无GC的方案:

通过ILRuntime中的BeginInvoke进行参数传递.

通过栈来进行数值的一次性传递,完成无GC消耗

如果要传递成员对象还需要先将自己这个对象压栈进入(示例中的: ctx),再通过Invoke调用

        //先获取与上方一致IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];//根据方法名称和参数个数获取方法IMethod method = type.GetMethod("StaticFunTest2", 1);//通过无GC Alloc方式调用方法using (var ctx = appdomain.BeginInvoke(method)){//将参数在此一个个压栈ctx.PushInteger(123);//.....//Invoke调用触发ctx.Invoke();}

如何指定参数类型:

先指定参数类型,再组件参数集合<ILRuntime.CLR.TypeSystem.IType>,然后将类型添加进集合中。

IType是ILRuntime中的,因此想要获取须通过appdomain.GetType(typeof(int))进行。

如果具有相同方法但具有不同参数类型可通过此方法进行。

        Debug.Log("指定参数类型来获得IMethod");IType intType = appdomain.GetType(typeof(int));//参数类型列表List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();paramList.Add(intType);//根据方法名称和参数类型列表获取方法method = type.GetMethod("StaticFunTest2", paramList, null);appdomain.Invoke(method, null, 456);

如何实例化热更里的类:

1、第一种方式

appdomain.Instantiate(“类名”,构造函数)

        Debug.Log("实例化热更里的类");object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 });

2、第二种方式

需要先获取type ,再进行一次转换才能使用

Instantiate();也可指定参数,无参直接使用即可

        //第二种方式IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];object obj2 = ((ILType)type).Instantiate();

如何调用成员方法:

IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
Debug.Log("调用成员方法");
method = type.GetMethod("get_ID", 0);//获取成员属性变量(名称)using (var ctx = appdomain.BeginInvoke(method))//采用无GC方法调用{ctx.PushObject(obj);//先将对象压栈ctx.Invoke();//再调用触发int id = ctx.ReadInteger();//读取返回值Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id);}
​

如果方法为泛型方法:

调用:

        Debug.Log("调用泛型方法");IType stringType = appdomain.GetType(typeof(string));//指定参数类型IType[] genericArguments = new IType[] { stringType };//先生成参数列表appdomain.InvokeGenericMethod("HotFix_Project.InstanceClass", "GenericMethod",                genericArguments, null, "TestString");//调用

获取:

        Debug.Log("获取泛型方法的IMethod");//参数类型列表List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();paramList.Clear();paramList.Add(intType);genericArguments = new IType[] { intType };method = type.GetMethod("GenericMethod", paramList, genericArguments);appdomain.Invoke(method, null, 33333);

热更方法带out,ref的方法

需要进行处理 这里建议直接看视频

       Debug.Log("调用带Ref/Out参数的方法");object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 });method = type.GetMethod("RefOutMethod", 3);int initialVal = 500;using(var ctx = appdomain.BeginInvoke(method)){//第一个ref/out参数初始值ctx.PushObject(null);//第二个ref/out参数初始值ctx.PushInteger(initialVal);//为何:传递默认的初始值//压入this  对象开始调用ctx.PushObject(obj);//压入参数1:additionctx.PushInteger(100);//压入参数2: lst,由于是ref/out,需要压引用,这里是引用0号位,也就是第一个PushObject的位置ctx.PushReference(0);//压入参数3,val,同ref/outctx.PushReference(1);ctx.Invoke();//调用//读取0号位的值List<int> lst = ctx.ReadObject<List<int>>(0);//读取被热更方法改变的值initialVal = ctx.ReadInteger(1);//完成调用Debug.Log(string.Format("lst[0]={0}, initialVal={1}", lst[0], initialVal));}

热更调用主工程:

1、设置引用类库在热更工程文件中设置

(1)引用unity的CSharp

位置:unity项目->library->ScriptAssemblies

(2)引用UnityEngine.CoreModule

(3)引用UnityEngine.UIModule

位置:unity安装目录->Editor->Data\Managed->UnityEngine

然后与在unity中一样直接进行使用即可

默认可能会拷贝一些不需要的文件:

选中引入的类库下面的“引用属性”复制本地改为false即可,再进行生成

也可通过修改路径直接完成dll文件替换,参考百度或视频


http://www.ppmy.cn/news/35702.html

相关文章

ChatGPT相关核心算法

ChatGPT 的卓越表现得益于其背后多项核心算法的支持和配合。本文将分别介绍作为其实现基础的 Transformer 模型、激发出其所蕴含知识的Prompt/Instruction Tuning 算法、其涌现出的思维链能力、以及确保其与人类意图对齐的基于人类反馈的强化学习算法。 1.基于Transformer的预…

强人工智能时代,区块链还有戏吗?

最近很多人都在问我&#xff0c;ChatGPT 把 AI 又带火了&#xff0c;区块链和 Web3 被抢了风头&#xff0c;以后还有戏吗&#xff1f;还有比较了解我的朋友问&#xff0c;当年你放弃 AI 而选择区块链&#xff0c;有没有后悔&#xff1f;这里有一个小背景。2017 年初我离开 IBM …

【面试题】这道 JS 经典面试题不要背,今天帮你彻底搞懂它!

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 https://mp.weixin.qq.com/s?__bizMzU5NzA0NzQyNg&mid2247485824&idx3&sn70cd26a7c0c683de64802f6cb9835003&scene21…

Linux - 第9节 - Linux多线程

1.Linux线程概念 1.1.线程的概念 书本中对线程的描述&#xff1a; 1.在进程内部运行的执行流 2.线程比进程粒度更细&#xff0c;调度成本更低 3.线程是CPU调度的基本单位 注&#xff1a;上面的说法都没有问题&#xff0c;这里我们以Linux内核的角度切入来讲解线程。 线程的概念…

基于深度学习的海洋动物检测系统(Python+YOLOv5+清新界面)

摘要&#xff1a;基于深度学习的海洋动物检测系统使用深度学习技术检测常见海洋动物&#xff0c;识别图片、视频和实时视频中的海洋动物&#xff0c;方便记录、展示和保存结果。本文详细介绍海洋动物检测系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实现代码…

怎么防止SQL注入?

首先SQL注入是一种常见的安全漏洞&#xff0c;黑客可以通过注入恶意代码来攻击数据库和应用程序。以下是一些防止SQL注入的基本措施&#xff1a; 数据库操作层面 使用参数化查询&#xff1a;参数化查询可以防止SQL注入&#xff0c;因为参数化查询会对用户输入的数据进行过滤和…

阿维塔城区NCA智驾导航辅助,复杂路口,全面胜任

阿维塔11城区NCA智驾导航辅助将于3月在上海、深圳等城市分阶段开启体验&#xff0c;以看得清、判得准、控得稳的“智驾”&#xff0c;进一步巩固业界智能天花板的地位。智能驾驶里程碑&#xff0c;拨杆两下开启都市安适旅程作为AVATRANS智能领航系统的重要组成部分&#xff0c;…

基于 Docker 的深度学习环境:入门篇

这篇文章聊聊如何从零到一安装、配置一个基于 Docker 容器的深度学习环境。 写在前面 这段时间&#xff0c;不论是 NLP 模型&#xff0c;还是 CV 模型&#xff0c;都得到了极大的发展。有不少模型甚至可以愉快的在本地运行&#xff0c;并且有着不错的效果。所以&#xff0c;经…