找回密码
 立即注册
查看: 313|回复: 4

unity热更方案ILRuntime和Puerts对比?

[复制链接]
发表于 2023-2-14 10:00 | 显示全部楼层 |阅读模式
今天刚看到腾讯的Puerts方案,查了些资料,看起来是XLua得升级版。那么Puerts和ILRuntime对比有哪些优劣呢?
发表于 2023-2-14 10:07 | 显示全部楼层
前言

上一节我们详细的讲解了ILRuntime游戏项目框架的启动过程,以及进入到热更项目中的入口,为我们做框架打下了扎实的基础,逻辑热更项目是用C#来完成的,所以我们在逻辑热更项目这里最大限度的保证开发与普通的Unity C#没有太大的差别,所以今天我们来设计基于逻辑热更项目的组件化开发机制,我们叫它ILRBehaviour,类似与MonoBehaviour,尽量保证所有的开发习惯与MonoBehaviour很像,由于MonoBehaviour是Unity C# 域的数据对象类型,所以我们在逻辑热更项目中无法直接使用,所以我们要自己设计一套类式的机制,主要完成3件事情:


设计ILRBehaviour接口
设计ILRBehaviour接口主要是和MonoBehaviour保持一致,MonoBehaviour常用的开发习惯主要是接口+gameObject+transform对象,后面我们在做好物理引擎的碰撞检测接口等。关于MonoBehaviour的定时器,我们后续单独设计一个定时器模块来做定时器,不去模拟MonoBehaviour的定时器机制。有了这样的考虑后,我们的ILRBehaviour的数据成员与接口就可以设计出来了,
如下:


这里和MonoBehaviour不一样的是,直接把基类的接口做成虚函数,而不是像MonoBehaviour一样通过反射查找看子类是否有这个方法。所以子类重载接口函数的时,加上override关键字。
添加,查找,删除ILRBehaviour组件实例机制




其它的查找,删除接口也类似,我这里就不一一分析了,大家可以对着源码查看。
让ILRBehaviour的特定接口在特定时机被调用
设计完添加,查找,删除ILRBehaviour组件的接口后,接下来就是要让ILRBehaviour组件机制中特定的接口在特定的时期被调用。目前热更新的特定时期,都是从Unity C#中来的,并且入口在main.cs 中,所以我们要让ILRBehaviour组件中的特定接口能调用到,就可以从main.cs 的特定接口开始,让他们调用ILRBehaviourMgr中的特定接口,然后在IlRBehaviourMgr中,我们遍历behaviourMap中的每个gameObject, 找到属于它的ILRBehaviour的List数组列表,遍历里面的每个ILRBehaviour组件实例,调用特定的接口就可以了,接下来我们以update为例,来分析:


整个过程,我整理在如图1.4-2中。


实现完这些机制以后,我们就完整的设计了一套基于ILRBehaviour的组件化开发机制,它尽可能的复合之前Unity c#的Mono的开发习惯,这样其它小伙伴就可以无缝的来在热更项目中做开发。最后上一个架构图,加深一下印象。


今天的分享就到这里了, 关注公众号,可以获取我们的ILRuntime热更教程源码, 对着源码看更酸爽。
两小时带你搞懂ILRuntime热更新

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-2-14 10:13 | 显示全部楼层
前言

ILRuntime 热更框架如何启动
上一节我们分析了ILRuntime基本开发原则与如何做到接口绑定,能使得逻辑热更项目直接调用Unity的引擎API,以及实现这个的技术原理,这一节我们来分析一个ILRuntime热更的游戏项目我们的启动流程是怎么样的?搞懂游戏项目的启动流程,对我们整个项目非常的重要。整个ILRuntime游戏项目的启动过程我们大致分成几个阶段:
对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀


启动与初始化Unity C#框架, 并实例化 ILRuntime 虚拟机




图1.3-1 启动节点与启动脚本
打开GameLanch.cs, Awake函数主要做两件事情,初始化Unity C#的框架模块, 启动检查更新最新的逻辑代码。如图 1.3-2:


图1.3-2 Awake函数做的主要事情
在图1.3-2中,ILRuntimeWrapper作为全局唯一单例的ILRuntime解释器的核心管理对象也是在这里被初始化的,接下来看下ILRuntimeWrapper.cs的启动代码,当实例化一个组件实例到GameApp节点上时,调用ILRuntimeWrapper的Awake函数,这个函数的主要内容就是new 了一个ILRuntime的解释器对象实例appdomain, 如图1.3-3


图1.3-3 实例化AppDomain解释器
这样第一阶段 Unity C#框架+ILRuntiem解释器初始化与启动就完成了。
检查更新最新逻辑热更.dll, 并装载字节码进虚拟机


下载以后,读取逻辑热更新.dll, 获得dll的二进制字节码的文件内容,如图1.3-4:


图1.3-4 加载.dll的二进制数据
然后调用AppDomain解释器接口,装载二进制字节码,进入到ILRuntime解释器。
ILRuntimeWrapper.Instance.LoadHotFixAssembly(dll, pdb);
如图1.3-5,为:


图1.3-5 LoadHotFixAssembly具体实现
自此,第二步就顺利完成了,然后调用ILRuntimeWrapper.EnterGame进入游戏。
引入Unity的生命周期入口到逻辑热更项目入口
完成第二步以后,ILRuntime虚拟机已经做好了解释执行的准备,而且现在装载的是最新的游戏逻辑代码与下载了最新的游戏资源,接下来就是要把代码跑入到热更项目.dll里面,让ILRuntime解释器,开始解释执行游戏逻辑代码,同时游戏逻辑代码调用Unity接口,来完成对应的逻辑功能。这个如何做呢?做游戏其实就是Init+update+FixedUpdate等常用的入口,我们的思路就是在ILRuntimerWrapper.cs里,当C#调用它的相应接口的时候,让它再调用逻辑热更.dll中的接口。这样逻辑热更项目就有了执行入口。我们在逻辑热更项目中,我们编写一个main.cs, 编写一个main类作为入口类,同时入口类里面有入口方法,如Init, Update, LateUpdate, FilxedUpdate等。如图1.3-6:


图1.3-6 逻辑热更项目main.cs中几个入口函数
我们在EnterGame这里调用热更逻辑项目main的Init函数,在Update, LateUpdate, FixedUpdate也是做类似的操作,调用逻辑热更项目main中对应的入口,如图所示1.3-7:


图1.3-7 ILRuntime解释器掉热更项目入口
这样,我们的代码就由Unity C# 跑入了逻辑热更项目的代码中了,同时逻辑热更项目又调用Unity引擎API完成游戏逻辑,这样我们就可以在逻辑热更项目中很愉快的开发游戏了。逻辑热更项目中的其它框架比如:事件管理,定时器管理,UI管理等,都是基于这个机制来实现。
今天的分享就到这里了, 关注公众号,可以获取我们的ILRuntime热更教程源码, 对着源码看更酸爽
附:视频教程
Unity / 热更新+AI专题 两小时带你搞懂ILRuntime热更新www.bycwedu.com/promotion_channels/1188508056www.bycwedu.com/promotion_channels/1188508056

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
 楼主| 发表于 2023-2-14 10:18 | 显示全部楼层
前言

上一节我们讲解了ILRuntime热更新的技术原理,根据技术原理来搭建ILRuntime的开发环境。对ILRuntime有初步的了解,并且安装了ILRuntime的运行环境与实例化了ILRuntime解释器,创建了生成.net字节码的.dll逻辑热更项目, 并把.dll生成到Unity项目的StreammingAssets下方便运行时加载,而热更新能够热更的就是逻辑热更项目生成的.dll。本节分析ILRuntime基本开发原则与如何做到接口绑定,能使得逻辑热更项目直接调用Unity的引擎API。


ILRuntime开发的基本原则





我们上几个截图,来看下一个ILRuntime的热更的案例Demo:


图1.2-1:使用Unity做好的地图资源,不挂任何逻辑脚本


图1.2-2:使用Unity做好的UI资源,不挂任何逻辑脚本


图1.2-3:加载最新的.dll中的二进制数据


图1.2-4:ILRuntime解释器装载二进制代码


图1.2-5:进入逻辑热更入口函数


图1.2-6逻辑热入口函数


图1.2-7调用Unity 的引擎接口来生成游戏内容


图1.2-8运行的游戏效果
如何做到热更项目工程解释使用C#工程的接口

很多读者很好奇,明明的热更项目与Unity C#是两个不同的”域”,为什么热更项目可以直接调用Unity C#提供的接口呢?其实这个的技术原理主要依赖于.net的.dll。我们在做.dll开发的时候,如果调用了别的.dll库,我们生成.dll的时候,会给调用别的库的接口做一个”符号”,当最终运行的时候,先根据符号,加载我们依赖的别的库的.dll,这样就运行时定位到”这些接口符号所在的地址”,在加载我们开发的.dll, 然后对我们使用别的库的”符号”进行重定向,这样当运行的时候,在自己开发的这个.dll,调用别人开发的.dll库中函数的时候,由于启动做了重定向地址,就能在运行中跳到别人开发的.dll库中的函数去。
当我们使用逻辑热更项目进行逻辑开发的时候,我们要把使用的Unity引擎相关的.dll的引用用,要拉入到我们逻辑热更项目中,这样当我们在逻辑热更项目中调用Unity引擎相关的组件函数的时候,会生成”依赖符号”到生成的 逻辑热更.dll中。这样也就解释了为什么逻辑热更项目中要添加Unity .dll库的引用,其实就是为逻辑热更.dll生成Unity引擎的引用符号。


图1.2-9热更项目引用系统库与Unity引擎库
当ILRuntime加载逻辑热更.dll的时候,会把逻辑热更.dll的符号表重新定向到运行中的对应的位置,这样在用户毫无察觉的情况下完成了逻辑热更项目可以调用Unity 引擎接口的绑定。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-2-14 10:22 | 显示全部楼层
1.介绍
ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现,快速、方便且可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新。学习交流聚集地
介绍 — ILRuntime (http://ourpalm.github.io)
https://ourpalm.github.io/ILRuntime/public/v1/guide/index.html
ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp websocket协议,支持服务端3D recast寻路等等 。
GitHub - egametang/ET: Unity3D Client And C# Server Framework
https://github.com/egametang/ET.git
2.接入ILRuntime
1.BuildAssemblieEditor.cs
构建codes.dll和codes.pdb到unity工程中并打上ab标签


Unitywww.bycwedu.com/promotion_channels/2146264125
public static class BuildAssemblieEditor
    {
        //dll复制到unity工程的路径
        private const string CodeDir = "Assets/Bundles/Code/";

        [MenuItem("Tools/BuildCode _F5")]
        public static void BuildCode()
        {
            //将codes目录下的所有cs文件打成code.dll
            BuildAssemblieEditor.BuildMuteAssembly("Code", new []
            {
                "Codes/Model/",
                "Codes/ModelView/",
                "Codes/Hotfix/",
                "Codes/HotfixView/"
            }, Array.Empty<string>());
            //将code.dll复制到unity工程路径下并打上ab标签
            AfterCompiling();
            //刷新资源
            AssetDatabase.Refresh();
        }

private static void BuildMuteAssembly(string assemblyName, string[] CodeDirectorys, string[] additionalReferences)
        {
            //获取CodeDirectorys路径下的所有cs文件
            List<string> scripts = new List<string>();
            for (int i = 0; i < CodeDirectorys.Length; i++)
            {
                DirectoryInfo dti = new DirectoryInfo(CodeDirectorys);
                FileInfo[] fileInfos = dti.GetFiles("*.cs", System.IO.SearchOption.AllDirectories);
                for (int j = 0; j < fileInfos.Length; j++)
                {
                    scripts.Add(fileInfos[j].FullName);
                }
            }

            //编译dll的路径
            string dllPath = Path.Combine(Define.BuildOutputDir, $"{assemblyName}.dll");
            string pdbPath = Path.Combine(Define.BuildOutputDir, $"{assemblyName}.pdb");
            File.Delete(dllPath);
            File.Delete(pdbPath);

            Directory.CreateDirectory(Define.BuildOutputDir);

            AssemblyBuilder assemblyBuilder = new AssemblyBuilder(dllPath, scripts.ToArray());
            
            //启用UnSafe
            //assemblyBuilder.compilerOptions.AllowUnsafeCode = true;

            BuildTargetGroup buildTargetGroup = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);

            assemblyBuilder.compilerOptions.ApiCompatibilityLevel = PlayerSettings.GetApiCompatibilityLevel(buildTargetGroup);
            // assemblyBuilder.compilerOptions.ApiCompatibilityLevel = ApiCompatibilityLevel.NET_4_6;

            //传递给程序集编译的其他程序集引用。
            assemblyBuilder.additionalReferences = additionalReferences;
            
            assemblyBuilder.flags = AssemblyBuilderFlags.DevelopmentBuild;
            //AssemblyBuilderFlags.None                 正常发布
            //AssemblyBuilderFlags.DevelopmentBuild     开发模式打包
            //AssemblyBuilderFlags.EditorAssembly       编辑器状态
            assemblyBuilder.referencesOptions = ReferencesOptions.UseEngineModules;

            assemblyBuilder.buildTarget = EditorUserBuildSettings.activeBuildTarget;

            assemblyBuilder.buildTargetGroup = buildTargetGroup;

            //编译开始回调
            assemblyBuilder.buildStarted += delegate(string assemblyPath) { Debug.LogFormat("build start:" + assemblyPath); };
            //编译结束回调
            assemblyBuilder.buildFinished += delegate(string assemblyPath, CompilerMessage[] compilerMessages)
            {
                int errorCount = compilerMessages.Count(m => m.type == CompilerMessageType.Error);
                int warningCount = compilerMessages.Count(m => m.type == CompilerMessageType.Warning);

                Debug.LogFormat("Warnings: {0} - Errors: {1}", warningCount, errorCount);

                if (warningCount > 0)
                {
                    Debug.LogFormat("有{0}个Warning!!!", warningCount);
                }

                if (errorCount > 0)
                {
                    for (int i = 0; i < compilerMessages.Length; i++)
                    {
                        if (compilerMessages.type == CompilerMessageType.Error)
                        {
                            Debug.LogError(compilerMessages.message);
                        }
                    }
                }
            };
            
            //开始构建
            if (!assemblyBuilder.Build())
            {
                Debug.LogErrorFormat("build fail:" + assemblyBuilder.assemblyPath);
                return;
            }
        }

        private static void AfterCompiling()
        {
            //编译中
            while (EditorApplication.isCompiling)
            {
                Debug.Log("Compiling wait1");
                // 主线程sleep并不影响编译线程
                Thread.Sleep(1000);
                Debug.Log("Compiling wait2");
            }
            
            Debug.Log("Compiling finish");

            //将dll和pdb拷贝到unity工程中
            Directory.CreateDirectory(CodeDir);
            File.Copy(Path.Combine(Define.BuildOutputDir, "Code.dll"), Path.Combine(CodeDir, "Code.dll.bytes"), true);
            File.Copy(Path.Combine(Define.BuildOutputDir, "Code.pdb"), Path.Combine(CodeDir, "Code.pdb.bytes"), true);
            AssetDatabase.Refresh();
            Debug.Log("copy Code.dll to Bundles/Code success!");
            
            // 设置ab包
            AssetImporter assetImporter1 = AssetImporter.GetAtPath("Assets/Bundles/Code/Code.dll.bytes");
            assetImporter1.assetBundleName = "Code.unity3d";
            AssetImporter assetImporter2 = AssetImporter.GetAtPath("Assets/Bundles/Code/Code.pdb.bytes");
            assetImporter2.assetBundleName = "Code.unity3d";
            AssetDatabase.Refresh();
            Debug.Log("set assetbundle success!");
            
            Debug.Log("build success!");
        }
}2.CodeLoader.cs
初始化ILRuntime并启动热更层开始函数
case Define.CodeMode_ILRuntime:
                                {
                                        //从ab包中加载dll和pdb
                                        Dictionary<string, UnityEngine.Object> dictionary = AssetsBundleHelper.LoadBundle("code.unity3d");
                                        byte[] assBytes = ((TextAsset)dictionary["Code.dll"]).bytes;
                                        byte[] pdbBytes = ((TextAsset)dictionary["Code.pdb"]).bytes;
                               
                                        AppDomain appDomain = new AppDomain();
                                        MemoryStream assStream = new MemoryStream(assBytes);
                                        MemoryStream pdbStream = new MemoryStream(pdbBytes);
                                        //ILRuntime加载程序集
                                        appDomain.LoadAssembly(assStream, pdbStream, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
                                        //注册委托适配器等
                                        ILHelper.InitILRuntime(appDomain);
                                        //缓存所有热更反射类型
                                        this.allTypes = appDomain.LoadedTypes.Values.Select(x => x.ReflectionType).ToArray();
                                        //调用到热更层的entry类的start方法
                                        IStaticMethod start = new ILStaticMethod(appDomain, "ET.Entry", "Start", 0);
                                start.Run();
                                        break;
                                }3.ILHelper.cs
注册重定向函数,委托,适配器,clr绑定
public static class ILHelper
    {
        public static List<Type> list = new List<Type>();

        public static void InitILRuntime(ILRuntime.Runtime.Enviorment.AppDomain appdomain)
        {
            // 注册重定向函数
            list.Add(typeof(Dictionary<int, ILTypeInstance>));
            list.Add(typeof(Dictionary<int, int>));
            list.Add(typeof(Dictionary<object, object>));
            list.Add(typeof(Dictionary<int, object>));
            list.Add(typeof(Dictionary<long, object>));
            list.Add(typeof(Dictionary<long, int>));
            list.Add(typeof(Dictionary<int, long>));
            list.Add(typeof(Dictionary<string, long>));
            list.Add(typeof(Dictionary<string, int>));
            list.Add(typeof(Dictionary<string, object>));
            list.Add(typeof(List<ILTypeInstance>));
            list.Add(typeof(List<int>));
            list.Add(typeof(List<long>));
            list.Add(typeof(List<string>));
            list.Add(typeof(List<object>));
            list.Add(typeof(ListComponent<ILTypeInstance>));
            list.Add(typeof(ETTask<int>));
            list.Add(typeof(ETTask<long>));
            list.Add(typeof(ETTask<string>));
            list.Add(typeof(ETTask<object>));
            list.Add(typeof(ETTask<AssetBundle>));
            list.Add(typeof(ETTask<UnityEngine.Object[]>));
            list.Add(typeof(ListComponent<ETTask>));
            list.Add(typeof(ListComponent<Vector3>));
            
            

            // 注册委托
            appdomain.DelegateManager.RegisterMethodDelegate<List<object>>();
            appdomain.DelegateManager.RegisterMethodDelegate<object>();
            appdomain.DelegateManager.RegisterMethodDelegate<bool>();
            appdomain.DelegateManager.RegisterMethodDelegate<string>();
            appdomain.DelegateManager.RegisterMethodDelegate<float>();
            appdomain.DelegateManager.RegisterMethodDelegate<long, int>();
            appdomain.DelegateManager.RegisterMethodDelegate<long, MemoryStream>();
            appdomain.DelegateManager.RegisterMethodDelegate<long, IPEndPoint>();
            appdomain.DelegateManager.RegisterMethodDelegate<ILTypeInstance>();
            appdomain.DelegateManager.RegisterMethodDelegate<AsyncOperation>();
            
            
            appdomain.DelegateManager.RegisterFunctionDelegate<UnityEngine.Events.UnityAction>();
            appdomain.DelegateManager.RegisterFunctionDelegate<System.Object, ET.ETTask>();
            appdomain.DelegateManager.RegisterFunctionDelegate<ILTypeInstance, bool>();
            appdomain.DelegateManager.RegisterFunctionDelegate<System.Collections.Generic.KeyValuePair<System.String, System.Int32>, System.String>();
            appdomain.DelegateManager.RegisterFunctionDelegate<System.Collections.Generic.KeyValuePair<System.Int32, System.Int32>, System.Boolean>();
            appdomain.DelegateManager.RegisterFunctionDelegate<System.Collections.Generic.KeyValuePair<System.String, System.Int32>, System.Int32>();
            appdomain.DelegateManager.RegisterFunctionDelegate<List<int>, int>();
            appdomain.DelegateManager.RegisterFunctionDelegate<List<int>, bool>();
            appdomain.DelegateManager.RegisterFunctionDelegate<int, bool>();//Linq
            appdomain.DelegateManager.RegisterFunctionDelegate<int, int, int>();//Linq
            appdomain.DelegateManager.RegisterFunctionDelegate<KeyValuePair<int, List<int>>, bool>();
            appdomain.DelegateManager.RegisterFunctionDelegate<KeyValuePair<int, int>, KeyValuePair<int, int>, int>();
            
            appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((act) =>
            {
                return new UnityEngine.Events.UnityAction(() =>
                {
                    ((Action)act)();
                });
            });
            
            appdomain.DelegateManager.RegisterDelegateConvertor<Comparison<KeyValuePair<int, int>>>((act) =>
            {
                return new Comparison<KeyValuePair<int, int>>((x, y) =>
                {
                    return ((Func<KeyValuePair<int, int>, KeyValuePair<int, int>, int>)act)(x, y);
                });
            });
            
            // 注册适配器
            RegisterAdaptor(appdomain);
            
            //注册Json的CLR
            LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appdomain);
            //注册ProtoBuf的CLR
            PType.RegisterILRuntimeCLRRedirection(appdomain);
           
            //clr绑定初始化
            CLRBindings.Initialize(appdomain);
        }
        
        public static void RegisterAdaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain)
        {
            //注册自己写的适配器
            appdomain.RegisterCrossBindingAdaptor(new IAsyncStateMachineClassInheritanceAdaptor());
        }
    }
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 08:04 , Processed in 0.091995 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表