在聊ulua、tolua之前,我们先来看看Unity热更新相关知识。

什么是热更新

举例来说: 游戏上线后,玩家下载第一个版本(70M左右或者更大),在运营的过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新打包,然后让玩家重新下载(浪费流量和时间,体验不好)。 热更新可以在不重新下载客户端的情况下,更新游戏的内容。 热更新一般应用在手机网游上。

为什么要用lua做热更新

其实C#本身的反射机制可以实现热更新,但是在ios平台上:

System.Reflection.Assembly.Load
System.Reflection.Emit
System.CodeDom.Compiler

无法使用,而动态载入dll或者cs的方法就这几个,因此在ios下不能动态载入dll或者cs文件(已经编译进去的没事),就把传统dotnet动态路径封死了。

所以,只能通过把lua脚本打进ab包,玩家通过解压ab包来更新游戏逻辑和游戏界面。

lua热更技术

  • ulua & tolua
  • xlua
  • slua

lua热更新流程

原理

使用assetbundle进行资源的更新,而由于lua运行时才编译的特性,所以lua文件也可以被看成是一种资源文件(与fbx、Image等一样)可以打进ab包中。

流程

  1. 对比files清单文件
  2. 更新文件
  3. 解压AB包中的资源
  4. 初始化

游戏运行时从服务器下载files.txt清单文件,与本地的files.txt清单文件进行对比。如果新下载的files里面的md5值与本地files的md5值不一样,或者本地清单里根本没有这个文件就从服务器下载这个ab包到PersistentDataPath文件夹(可读写)中。下载完毕后解开AB包中的资源,然后完成初始化。

ulua&tolua原理解析

既然使用了lua作为热更脚本,那肯定避免不了lua和C#之间的交互。

C#调用lua

C#调用lua的原理是lua的虚拟机,具体步骤可参见我的博客

也可以看看简单的示例:

private string script = @"
function luaFunc(message)
print(message)
return 42
end
";
void Start () {
LuaState l = new LuaState();
l.DoString(script);
LuaFunction f = l.GetFunction("luaFunc");
object[] r = f.Call("I called a lua function!");
print(r[0]);
}

lua调用C#

反射

旧版本的ulua中lua调用C#是基于C#的反射。

C#中的反射使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。

反射用到的命名空间:

System.Reflection
System.Type
System.Reflection.Assembly

反射用到的主要类:

  • System.Type 类-通过这个类可以访问任何给定数据类型的信息。
  • System.Reflection.Assembly类-它可以用于访问给定程序集的信息,或者把这个程序集加载到程序中。

ulua反射调用C#示例:

 private string script = @"
luanet.load_assembly('UnityEngine')
luanet.load_assembly('Assembly-CSharp')
GameObject = luanet.import_type('UnityEngine.GameObject')
ParticleSystem = luanet.import_type('UnityEngine.ParticleSystem') local newGameObj = GameObject('NewObj')
newGameObj:AddComponent(luanet.ctype(ParticleSystem))
"; //反射调用
void Start () {
LuaState lua = new LuaState();
lua.DoString(script);
}

可看到通过反射(System.Reflection.Assembly)把UnityEngine程序集加入到lua代码中,通过反射(System.Type)把Unity.GameObject和Unity.ParticleSystem类型加入到lua代码中,这样我们便可以在lua中像在C#里一样调用Unity定义的类。

去反射

现版本的ulua(tolua)中lua调用C#是基于去反射。

去反射的意思是:

把所有的c#类的public成员变量、成员函数,都导出到一个相对应的Wrap类中,而这些成员函数通过特殊的标记,映射到lua的虚拟机中,当在lua中调用相对应的函数时候,直接调用映射进去的c# wrap函数,然后再调用到实际的c#类,完成调用过程。

具体调用过程可参考: Unity3d ulua c#与lua交互+wrap文件理解

因为反射在效率上存在不足,所以通过wrap来提升性能。但是因为wrap需要自己去wrap,所以在大版本更新是可以用到的,小版本更新还是使用反射。

C#与Lua数据交互(lua虚拟栈)

C#与lua的数据交互是基于一个Lua先进后出的虚拟栈:

(1)若Lua虚拟机堆栈里有N个元素,则可以用 1 ~ N 从栈底向上索引,也可以用 -1 ~ -N 从栈顶向下索引,一般后者更加常用。

(2)堆栈的每个元素可以为任意复杂的Lua数据类型(包括table、function等),堆栈中没有元素的空位,隐含为包含一个“空”类型数据

(3)TValue stack[max_stack_len] // 定义在 lstate.c 的stack_init函数

关于Lua虚拟栈入栈的具体操作做可以见下图:

更详细的可见: Lua初学者(四)–Lua调用原理展示(lua的堆栈)

C#与Lua通信(P/Invoke)

  • 所有的通信都是基于P/Invoke模式(性能低)类似JNI
  • P/Invoke:公共语言运行库(CLR)的interop功能(称为平台调用(P/Invoke))
  • 命名空间:System.Runtime.InteropServices

示例:

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr luaL_newstate();

P/Invoke 要求方法被声明为 static。

P/Invoke性能:

为啥P/Invoke看起来这么慢?

(1)寻址方式:调用时指定了CharSet=CharSet.Ansi 那么CLR首先在非托管的DLL中寻找,若找不到,就用带A后缀的函数进行搜索造成开销,可将ExactSpelling的值设为true防止CLR通过修改入口名称进行搜索。

(2)类型转换:在Managed Code和Native Code间传递参数和返回值的过程成为marshalling。托管函数每次调用非托管函数时,都要求执行以下几项操作:

  • 将函数调用参数从CLR封送到本机类型。
  • 执行托管到非托管形式转换。
  • 调用非托管函数(使用参数的本机版本)
  • Interop在进行封送时候,对bittable可以不进行拷贝,而是直接内存锚定。
  • 将返回类型及任何“out”或“in,out”参数从本机类型封送到 CLR 类型。

(3)VC++ 提供自己的互操作性支持,这称为 C++ Interop。 C++ Interop 优于 P/Invoke,因为 P/Invoke 不具有类型安全性,参数传递还需要做类型检查。

Bittable类型(byte,int,uint)与非Bittable类型(char, boolean,array,class)

参考书: NET互操作 P_Invoke,C++Interop和COM Interop.pdf

ulua的优化方式汇总

  • BinderLua太多wrap很慢(反射与去反射共存)
  • Lua代码打入AssetBundle为了绕过苹果检测
  • 动态注册Wrap文件到Lua虚拟机(tolua延伸)
  • ToLuaExport. memberFilter的函数过滤
  • 尽量减少c#调用lua的次数来做主题优化思想
  • 尽量使用lua中的容器table取代c#中的所有容器
  • 例子CallLuaFunction_02里附带了no gc alloc调用方式
  • Lua的bytecode模式性能要低于Lua源码执行
  • 取消动态参数:打开LuaFunction.cs文件,找到函数声明:
public object[] Call(params object[] args){
return call(args, null);
}

取消动态参数args,可用较笨方法,就是定义6-7个默认参数,不够再加。

  • 安卓平台如果使用luajit的话,记得在lua最开始执行的地方请开启 jit.off(),性能会提升N倍。
  • 记得安卓平台上在加上jit.opt.start(3),相当于c++程序-O3,可选范围0-3,性能还会提升。Luajit作者建议-O2

ulua、tolua原理解析的更多相关文章

  1. [原][Docker]特性与原理解析

    Docker特性与原理解析 文章假设你已经熟悉了Docker的基本命令和基本知识 首先看看Docker提供了哪些特性: 交互式Shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上, ...

  2. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  3. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  4. Web APi之过滤器创建过程原理解析【一】(十)

    前言 Web API的简单流程就是从请求到执行到Action并最终作出响应,但是在这个过程有一把[筛子],那就是过滤器Filter,在从请求到Action这整个流程中使用Filter来进行相应的处理从 ...

  5. GeoHash原理解析

    GeoHash 核心原理解析       引子 一提到索引,大家脑子里马上浮现出B树索引,因为大量的数据库(如MySQL.oracle.PostgreSQL等)都在使用B树.B树索引本质上是对索引字段 ...

  6. alibaba-dexposed 原理解析

    alibaba-dexposed 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49821413 原理参考地址: htt ...

  7. 支付宝Andfix 原理解析

    支付宝Andfix 原理解析 使用参考地址: http://blog.csdn.net/qxs965266509/article/details/49802429 原理参考地址: http://blo ...

  8. JavaScript 模板引擎实现原理解析

    1.入门实例 首先我们来看一个简单模板: <script type="template" id="template"> <h2> < ...

  9. Request 接收参数乱码原理解析三:实例分析

    通过前面两篇<Request 接收参数乱码原理解析一:服务器端解码原理>和<Request 接收参数乱码原理解析二:浏览器端编码原理>,了解了服务器和浏览器编码解码的原理,接下 ...

随机推荐

  1. ReactNative实现GridView

    ReactNative内置了ListView组件但是没有类似GridView这样的组件.利用一些已经有的属性是可以实现GridView的,利用ContentContainerStyle的属性然后配合样 ...

  2. shell 获取当前目录下的jar文件

    1.示例 function getDir() { ` do fileName=$"/"$item if [ -d $fileName ] then echo $fileName&q ...

  3. 跟文档学习next.js

    前言:Next.js 是一个轻量级的 React 服务端渲染应用框架. Next.js中文点击这里 Next.js中文站Github点击这里 新建文件夹安装它: npm install --save ...

  4. Spring Boot 2.x基础教程:JSR-303实现请求参数校验

    请求参数的校验是很多新手开发非常容易犯错,或存在较多改进点的常见场景.比较常见的问题主要表现在以下几个方面: 仅依靠前端框架解决参数校验,缺失服务端的校验.这种情况常见于需要同时开发前后端的时候,虽然 ...

  5. CSS技巧 (1) · 结构和布局

     前言 这一篇主要是总结关于结构和布局的一些技巧,不管什么,一个网页上来,最重要的是先确定他的结构和布局,实现基本的布局之后,我们再进行局部的优化和交互特效. 这一篇主要讲 关于 自适应内部元素 的内 ...

  6. JAVA的List接口的remove重载方法调用原理

    前言 说真的,平常看源码都是自己看完自己懂,很少有写出来的冲动. 但是在写算法的时候,经常用到java中各种集合,其中也比较常用到remove方法. remove有重载函数,分别传入参数是索引inde ...

  7. Object.keys方法详解

    一.官方解释 Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 .如果对象的键-值都不 ...

  8. 线程安全-JUC

    在多线程开发中,我们常遇到的问题就是并发数据,怎么保证线程安全.怎么保证数据不重复. 1. volatile volatile是一个java关键字,常用于在多线程中共享变量 volatile原理 每个 ...

  9. Django学习之文件下载

    在实际的项目中很多时候需要用到下载功能,如导excel.pdf或者文件下载,当然你可以使用web服务自己搭建可以用于下载的资源服务器,如nginx,这里我们主要介绍django中的文件下载. 我们这里 ...

  10. python+selenium遇到元素定位不到的问题,顺便记录一下自己这次的错误(报错selenium.common.exceptions.NoSuchElementException)

    今天在写selenium一个发送邮件脚本时,遇到一些没有找到页面元素的错误.经过自己反复调试,找原因百度,终于解决了.简单总结一下吧,原因有以下几点: 一:Frame控件嵌套,.Frame/Ifram ...