对老项目进行热更新

项目用纯C#开发的?

眼看Unity引擎热火朝天,无数程序猿加入到了Unity开发的大本营。

一些老项目,在当时ulua/slua还不如今天那样的成熟,因此他们选择了全c#开发;也有一些出于性能考虑,全c#开发;也有一些没有太丰富运营经验的开发团队,没有想太多,用全c#爽爽地开发。

策划和运营要热更新?

用C#开发爽爽的日子一天一天的过去了,直到突然有一天,策划老大说:“我们得做个热更新模块!”;

突然有一天,老板说:“别人游戏用Lua热更新,为什么我们不行?”;

突然有一天,运营说:“线上游戏出了个bug,重新编译出包审核得几天啊!”——嗯,这时候,受伤的总是程序猿。

打补丁方法

有没有亡羊补牢,临危受命的折衷方法?可以不用把C#改成Lua,可以不用区分平台(AndroidDLL重载IOS却不行),可以对任何代码做修复的方法?

有的,并且用很笨的一句代码来概括:

class Fucker {
void Fucking() {
if (PatchScript.HasPatchScript("Fucker.Fucking")) {
// do patch fuck
PatchScript.CallPatchScript("Fucker.Fucking");
return;
}
// do origin fuck
Log.Info("I am a original fuck");
}
}

往所有的函数注入代码,当存在补丁脚本时执行补丁脚本,不存在时执行原代码。

因此,本文的热更新等同于打补丁

什么是热更新?

吐槽一点,虽然我们这个方法确实将热更新做成模块了,但这绝对是迫不得已的。 热更新绝对不是一个功能模块能实现,它是一个底层架构所决定的。要说一个项目不好,无法实现热更新,这归根到底是架构没想好、策划没坚持、程序没执着、运营懒得管等等各种各样复杂原因所导致的。

我理想的热更新是怎样的?

我心目中理想热更新是怎样?要热!

  • 对任意部位的代码进行修改;
  • 运行时,自动下载更新代码,尔后无需重启;
  • 运行时,立即重载代码,并继续运行;
  • 兼顾开发环境与生产环境的简便性;

达到什么目地

热更新在Web开发领域非常普遍,毕竟HTTP是无状态的;而游戏这种高实时性的开发相比,要想做好热更新就确实需要架构层的更多考虑了。怎么做好热更新,我们还是回到主题,接下来介绍方法,可以达到什么目的:

  • 对任意部位的方法体代码进行修改;
  • 运行时,立即重载代码,并继续运行
  • 语言无关:同样的思路可以应用在Java、C#、Go、C++等等
  • 使用起来不太方便
  • 亡羊补牢专用

代码注入补丁的热更新

上面说了很多废话。接下直奔主题,要怎样做到:

注入补丁

class Fucker {
void Fucking() {
if (PatchScript.HasPatchScript("Fucker.Fucking")) {
// do patch fuck
PatchScript.CallPatchScript("Fucker.Fucking");
return;
}
// do origin fuck
Log.Info("I am a original fuck");
}
}

执行Lua脚本

我们要针对Fucker类的Fucking方法进行更新,则新建Lua脚本Fucker.Fucking.lua

-- 文件名Fucker.Fucking.lua

function Func()
print("I am a patch fuck!")
end return Func

一个补丁脚本就此完成,当程序运行到Fucking函数时,实际上它执行的是Lua脚本,变相的实现了热更新的功能——改变代码的执行行为。

热更新大法流程

STEP 1:执行环境

本文针对Unity游戏开发,那么原语言,当然是C#了;而打补丁的语言,使用Lua;

在这里我们使用SLua插件,它的高质量代码和强大的反射功能,非常适合代码注入补丁热更新。

class PatchScript
{
public bool HasPatchScript(string path)
{
return File.Exists("Script/" + path + ".lua");
} public void CallScript(string path)
{
string scriptCode = File.ReadAllString(path);
var luaFunc = this.luaState.doScript(scriptCode) as LuaFunction;
luaFunc.call();
}
}

STEP 2:代码注入

嗯,执行环境,非常的简单,不就是简单的if判断吗? 估计最令人迷惑的部分就是,如何往所有的C#函数体前部分插入代码了。

我们要做的,遍历所有的c#文件,取得class类名,然后再分析函数名,定位函数在代码中的起始位置、获取函数的参数列表、参数类型……等等。看起来很复杂,是不是要对c#做语法分析、词法分析了?感觉工作量很大啊。

幸好,轮子已经做好了。这里要用到一个重要的库——NRefactory。包括IDE MonoDevelop中的语法智能提示、重构都是基于这个库进行的。有了它,语法分析词法分析仅仅是API的调用而已。

找出C#方法体并插入代码

我们要做的,就是使用NRefactory找出C#的方法体,并插入代码:

            using (var script = new DocumentScript(document, formattingOptions, options))
{
CSharpParser parser = new CSharpParser();
SyntaxTree syntaxTree = parser.Parse(code, srcFilePath);
foreach (var classDec in syntaxTree.Descendants.OfType<TypeDeclaration>())
{
if (classDec.ClassType == ClassType.Class || classDec.ClassType == ClassType.Struct)
{
var className = classDec.Name;
foreach (var method in classDec.Children.OfType<MethodDeclaration>())
{
var returnType = method.ReturnType.ToString();
if (returnType.Contains("IEnumerator") || returnType.Contains("IEnumerable")) // 暂不支持yield!
continue; // 。。。。这里找到了方法体! 开始进行插入!
}
}
}
}

我把使用NRefactory对C#方法体注入的代码,抽象成一个单独的类MethodInjector(C#),看文章底部。

STEP 3:编写Lua补丁

补丁的方法,在上文“代码注入补丁热更新大法流程”中已有提及:

对需要打补丁的函数,创建Lua脚本

如上文中要改变Fucker.Fucking函数的执行行为,则创建Fucker.Fucking.lua脚本文件,脚本末端返回一个Lua函数。

最后

本文着重提供了一种思路,而不提供完整的源代码,毕竟涉及到部分人的商业利益,遗憾点到即止。

使用下面的MethodInjector类,会把函数的参数值也进行解析、预编译指令引入、并且可以在Lua补丁中控制是否在执行补丁后,继续执行原C#代码,基本能达到大部分的需求了。

这里举例一个更好的方案:注入DLL的IL代码,而不是注入c#代码,来确保我们的c#代码不会被改动。

MethodInjector类

完整代码:

https://github.com/zhaoqingqing/blog_samplecode/blob/master/unity-framework/MethodInjector.cs

版权说明

文/公的Kelly[mr-kelly](简书作者)     Email: 23110388@qq.com

原文链接:http://www.jianshu.com/p/481994e8b7df

著作权归作者所有,转载请联系作者获得授权,,并标注“简书作者”。

KSFramework系列

github地址:https://github.com/mr-kelly/KSFramework

欢迎大家到 github提issues



KSFramework:集成U3D热重载框架 - README

KSFramework:Unity3D开发框架快速入门

KEngine策划指南:配置表格的编辑与编译

KEngine:Unity3D资源的打包、加载、调试监控

KSFramework常见问题:Lua脚本热重载,内存状态数据丢失?

KSFramework常见问题:Excel如何进行SVN协作、差异比较?

KSFramework配置表:扩展表格解析类型

另类Unity热更新大法:代码注入式补丁热更新

最新文章

  1. 【python坑记录】
  2. Windows平台下ActiveMQ 安装
  3. Python+Selenium进行UI自动化测试项目中,常用的小技巧3:写入excel表(python,xlsxwriter)
  4. Oracle创建DBLink的方法
  5. JavaScript == VS ===
  6. HDU 1317 XYZZY【Bellman_Ford判断正环】
  7. CUDA与VS2013安装
  8. xml 文件的增删改查
  9. Hierarchical Storage structure
  10. Linux下同步工具inotify+rsync使用详解
  11. 用Qt开发Web和本地混合的应用
  12. HDU 1114 Piggy-Bank 全然背包
  13. React Native 之 数据持久化
  14. Asp.Net Core-----简介与安装
  15. 2018/1/28 RocketMq学习笔记
  16. neutron--ml2 plugin
  17. Cordova打包Apk
  18. svn安装和使用
  19. 023 SpringMVC拦截器
  20. DOM系列基础知识

热门文章

  1. add host bat
  2. 用TypeScript开发了一个网页游戏引擎,开放源代码
  3. 好推二维码如何通过应用宝微下载支持微信自动打开APP下载?
  4. Android ListViewview入门
  5. android 最详细的动画大全,包括如何在代码和在XML中使用
  6. 移动Web开发(二)
  7. 虚拟机安装 Centos6
  8. hypervisor与VMware共存方法
  9. Nginx中文手册
  10. User Word Automation Services and Open XML SDK to generate word files in SharePoint2010