ET ILRuntime

ET ILRuntime

官网:http://ourpalm.github.io/ILRuntime/public/v1/guide/index.html

原理

代码热更需要将热更域编译过后的程序集,以资源的方式打包到热更资源服务器中,用户在下载热更程序集后利用IL反射使用该程序集中方法运行游戏

安装

  1. 下载Unity3D实例工程(切勿下载原工程,修改起来太麻烦http://ourpalm.github.io/ILRuntime/public/v1/guide/tutorial.html
  2. 下载VS调试插件(上一步页面最下方有链接)
    • 使用时需要在你的Unity工程里添加appdomain.DebugService.StartDebugService(56000);该代码,其中的appdomain指向你的热更程序集
    • 然后启动Unity,不要启动VS附加
    • 如果安装插件成功可以在VS 的调试选项下面看见Attach to ILRuntime,点击后即可附加到Unity上
    • 貌似使用该插件时,如果断点打到异步方法(ETVoid与ETTask)内程序会报错,且无法执行,取消断点啥事没有
  3. 将以下几个文件夹复制到项目中:CLR、Other、Plugins、Reflection、Runtime

代码结构

BuildHotfixEditor编辑器类,将热更层程序集从项目根目录拷贝到项目中,方便后续的资源热更

ILRuntimeCLRBinding编辑器类,用于CLR绑定

CLRBindings用于绑定生成的CLR代码

ILHelper用于注册委托适配器和跨域继承适配器

  • IAsyncStateMachineAdaptor异步状态机适配器
  • IDisposableAdaptorDisposable适配器
  • IMessageIMessage适配器

ActionHelper委托转换器

Hotfix热更层管理类

ILStaticMethod一个待执行的IL静态方法

编辑器类

  • BuildHotfixEditor

    用于每次Unity编译后自动将unity项目根目录下Library/ScriptAssemblies文件夹里编译过后的的热更程序集复制到unityAssets/Res/Code文件夹中

  • ILRuntimeCLRBinding

    用于生成CLR绑定脚本

    1. 获取热更程序集,调用ILHelper.InitILRuntime(domain);对程序集进行适配器注册
    2. 调用ILRuntime.Runtime.CLRBinding.BindingCodeGenerator.GenerateBindingCode(domain, "Assets/Model/ILBinding");来生成CLR绑定脚本,到指定目录下

热更流程

  1. 在下载完热更资源(AB包)后,调用Game.Hotfix.LoadHotfixAssembly();方法加载热更程序集

    • ResourcesComponent加载之前创建的预制体,并获取程序集文件
    • 将程序集文件转换成内存流
    • 创建ILRuntime.Runtime.Enviorment.AppDomain,调用它的LoadAssembly方法,并将上一步获取到的内存流作为参数传入
    • 传入类名和要执行的方法名,new一个ILStaticMethod等待执行静态方法(该方法指向热更层的Init类中的Start方法)
    • 最后保存程序集中类型
  2. 调用Game.Hotfix.GotoHotfix();启动热更层Init类的Start方法

    • 调用ILHelper.InitILRuntime方法进行适配器注册
      • 调用appdomain.DelegateManager.RegisterMethodDelegate<T>();来进行不同类型的委托适配器注册
      • 从Model程序集中获取标识了ILAdapterAttribute适配器标识类的类型,实例化该类型,然后调用appdomain.RegisterCrossBindingAdaptor(adaptor);来注册该跨域继承适配器
    • 调用之前创建的IL静态方法的Run函数,执行热更程序集中对应方法
  3. 关于委托转换器,参考ActionHelper

    ILRuntime内部是使用Action,以及Func这两个系统自带委托类型来生成的委托实例,所以如果你需要将一个不是Action或者Func类型的委托实例传到ILRuntime外部使用的话,除了委托适配器,还需要额外写一个转换器,将Action和Func转换成你真正需要的那个委托类型。例如热更层给Button添加事件方法。

跨域继承适配器Adaptor

当我们在热更程序集中继承其他程序集的类或接口称之为跨域继承,由于热更域的特殊性,需要对继承类编写适配器,来达到热更

下列是ET已经写好的适配器:

  • IAsyncStateMachineAdaptor异步状态机适配器
  • IDisposableAdaptorDisposable适配器
  • IMessageIMessage适配器

适配器编写规则,拿IDisposableAdaptor举例:

using System;
using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;

namespace ETModel
{
    /// <summary>
    /// Disposable适配器封装类
    /// </summary>
    [ILAdapter]//该特性用于ET在程序集中快速获取适配器类,然后进行注册
    public class IDisposableClassInheritanceAdaptor : CrossBindingAdaptor
    {
        public override Type BaseCLRType
        {
            get
            {
                return typeof(IDisposable);//这是你想继承的那个类

            }
        }

        public override Type AdaptorType
        {
            get
            {
                return typeof(IDisposableAdaptor);//这是实际的适配器类类型
            }
        }

        public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
        {
            return new IDisposableAdaptor(appdomain, instance);//创建一个新的适配器类实例
        }

        /// <summary>
        /// 适配器,继承你想继承的那个类和CrossBindingAdaptorType
        /// </summary>
        public class IDisposableAdaptor : IDisposable, CrossBindingAdaptorType
        {
            private ILTypeInstance instance;
            private ILRuntime.Runtime.Enviorment.AppDomain appDomain;

            //需要执行的方法
            private IMethod iDisposable;
            //方法要传入的参数
            private readonly object[] param0 = new object[0];

            public IDisposableAdaptor()
            {
            }

            public IDisposableAdaptor(ILRuntime.Runtime.Enviorment.AppDomain appDomain, ILTypeInstance instance)
            {
                this.appDomain = appDomain;
                this.instance = instance;
            }

            public ILTypeInstance ILInstance
            {
                get
                {
                    return instance;
                }
            }

            //以下方法为你需要重写所有你希望在热更脚本里面重写的方法,并且将控制权转到脚本里去
            public void Dispose()
            {
                //由于Dispose可能多次调用,所以将其写成一个全局遍历,避免每次都重复获取
                if (this.iDisposable == null)
                {
                    this.iDisposable = instance.Type.GetMethod("Dispose");
                }
                this.appDomain.Invoke(this.iDisposable, instance, this.param0);
            }

            public override string ToString()
            {
                IMethod m = this.appDomain.ObjectType.GetMethod("ToString", 0);
                m = instance.Type.GetVirtualMethod(m);
                if (m == null || m is ILMethod)
                {
                    return instance.ToString();
                }

                return instance.Type.FullName;
            }


        }
    }
}

在完成适配器编写完成后,还需要再程序启动后进行注册才能使用

ET在ILHelper.InitILRuntime(this.appDomain);方法中完成了对程序集中所有适配器的注册

核心代码appdomain.RegisterCrossBindingAdaptor(adaptor);

总结

  1. 在热更层Hotfix文件夹中新建Unity程序集定义Assembly Definition File
  2. 建立BuildHotfixEditor编辑器脚本,复制热更程序集
  3. 创建预制体,引用程序集,并将该程序集设置为AB包,方便资源更新
  4. 创建ILRuntimeCLRBinding编辑器脚本,用于创建CLR绑定脚本
  5. 为跨域继承的类写跨域继承适配器CrossBindingAdaptor
  6. 为非Action、Func类型的委托,写委托转换器
  7. 为不同参数类型委托写委托适配器
  8. 在调用热更代码前进行热更程序集的加载(参考Game.Hotfix.LoadHotfixAssembly();)
  9. 进行热更重定向注册委托适配器和跨域继承适配器(参考ILHelper.InitILRuntime(this.appDomain);)

补充

  1. 多线程

    为了防止多线程产生未知错误,可以在执行完适配器注册后进行预热

    appdomain.Prewarm("多线程相关类的类名");

  2. 与ASYNC异步宏的冲突问题,在打上异步宏后需要执行下CLR绑定

  3. 为什么需要clr绑定?

    两个作用:防止热更层用到的框架层代码被裁减, 以及加速热更代码的执行。

    为什么会被裁减呢?因为Unity打包的时候真的不把这个热更dll看做dll,因为这个热更dll是脱离unity框架层的。自然在unity打包的时候,为了包体大小会把认为没有使用的代码全部过滤掉。这种情况下ILRuntime解释执行的时候,去反射调用框架层代码就会被视为错误,因为框架层不存在这些被调用的代码。

    加速热更代码执行其实是ILRuntime解释每条il指令的时候,都会去现有缓存中查找当前指令是否为重定向函数,如果为重定向函数,则直接调用,如果不是重定向函数,则会反射调用,反射这就是效率的隐患。重定向函数有自己的函数签名格式,类似lua的LuaCsFunction。

  4. 为什么需要委托适配器?

    因为ilruntime把热更内部的delegate都看作是action/func的形式,但是框架层可能是自定义的delegate形式,这就需要一层转换。

  5. 为什么需要适配器?

    因为热更层与框架层脱离了关系,至少在Unity看来脱离了关系,那么此时Unity就会开始自己的strip优化,框架层中一些仅仅被热更层继承使用的接口,类等就可能被优化掉。所以第一个原因就是:防裁剪。

    因为脱离了关系,那么如何在框架层中驱动的时候,可以同步驱动到热更层,这就成了一个问题。这就需要框架层引用热更层的相关instance去驱动 ,那么如何引用?这就是适配器的作用。适配器工作在框架层,其显式强调了需要引用驱动的类型实例,然后重写相关函数体内容,去实质调用 热更类型实例 的方法。具体参考MonobahaviourAdaptor即可理解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342