Lua重点语法
数据结构
有八种:
- 值类型:nil\string\boolean\number
- 引用类型:function\thread\userdata\table
表
(1)基本概念
- 本质上是键值对,下标从1开始;数组的索引可以是数组或者字符串。
- table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。
case 01:
输出效果:
知识点:
- table中nil不可以做为键
- ipairs和pairs的区别
- 两者都可以用来遍历,但是ipairs只能遍历数组部分,pairs可以遍历数组部分和hash部分
- 对于nil的处理不同,pairs遇到nil会跳过之后继续执行,ipairs会停止遍历
case 02:
输出效果:
case 2:
输出效果:
(2)底层实现
table的底层分为数组部分+哈希表部分
每个table结构最多由3块连续内存组成:Table结构+数组(存放了连续的整数索引)+哈希表(大小为2的整数次幂)
(3)表的索引
输出效果:
解读:有两种方式,[]可以实现所有的索引,.只能实现索引为字符的索引
string
(1)基本概念
lua中有8种基本的数据类型
nil, boolean, number, string, function, userdata, thread, and table
string有三种表示形式
输出效果:
C#中字符char用单引号来表示,lua中没有char这种数据结构,单个字符的string就用来表示字符了。
(2)底层实现
string库中的function都是返回一个数据对象,而不是直接对原来的string进行操作。
变量和表达式
声明方法的方式
点和冒号的区别
深拷贝 & 浅拷贝
“=”表示浅拷贝,传递值或者地址
深拷贝表示复制对象的基本类型,也复制源对象中的对象。一般需要用到table来实现,通过循环将值赋给新表中的值,还需要将原来表的元表赋值给新表作为元表。
元表和元方法
元表的本质:一个普通的table+定义了特定事件下的值。由key(一个__开头的string)和metavalue(大多数是一个function,被叫做元方法)组成。
操作:
- 获取:getmetatable
- 改变:setmetatable
每一种类型的数据共享同一个metatable;除了string类型,其他类型都是默认没有元表的。
__index & __newindex
- 在表中查找一个值,如果没找到,则判断其是否有元表
- 如果没有元表,查找结束,返回nil;如果有元表,则调用__index去父脚本里查找
相关的key值
- __rawget & __rawset
- __rawget:不想从__index对应的元方法中查找值
- __rawset:不想执行__newindex对应的元方法
代码示例:
输出效果:
知识点总结:
- __index是用来做查询的,如果在一个table中没有找到,就去这个表的元表里面找;元表的value可以是function也可以是table;
- __newindex是用来新创建表的值的,如果用这种赋值方式t[k] = v,会因为递归导致堆栈溢出,只能使用rawset
推崇的做法:
- 在将一个表设置为另一个对象的元表之前,先将所需的所有元方法写好
- 在对象创建之后立即为其设置元表
实现面向对象
要求:
- 实现对象:利用table和function来实现类,利用元表来实现继承和多态
- 实现继承:设置元表,然后要将子类的__index设置好
代码示例:
自己写的代码示例:
这里是一个思路:
实现私有对象
实现一个只读的表
只读表需要满足的条件:
- 禁止在表中创建新的值
- 禁止改变已有的值
- 子表也只是可读
代码实现:
参照了这篇博文,但是对于其中的一些写法做出了改进,应该是更好理解了。
输出效果:
闭包
闭包 = 函数 + 引用环境
在Lua中function是一种first-class value,具有如下特点:
- ①
函数
可以存储
在变量或table
中。 - ②可以作为
实参
传递给其他函数。 - ③可以作为其他函数的
返回值
。 - ④可以在
运行期间
被创建(C语言的函数就没有这个特点)。
在运行时,每当Lua执行一个形如function…end这样的函数时,它就会创建一个新的数据对象,其中包含但不只限于相应函数原型的引用和一个由所有upvalue引用组成的数组,而这个数据对象就称为闭包。
函数是编译期概念,是静态的,而闭包是运行期概念,是动态的。
Upvalue
upvalue是让lua来模拟实现,类似c语言中,静态变量机制的一种机制。一个带上upvalue的函数,我们称之为闭包(closure)。
GC
参考:https://blog.csdn.net/fwb330198372/article/details/104263213
lua有自动的内存管理,会有一个垃圾收集器来自动的回收垃圾。当一个对象被垃圾收集器确认不会再被访问到的时候,就会被认为是死对象。垃圾收集器有两种模式:incremental & generational。
GC的大致演变过程:
- 两色增量标记清除法-三色增量标记清除法
- 分步GC-分代GC(5.2版本的分代GC-5.4版本的分代GC)
三色增量标记清除法
采用三色增量标记清除法的优势:GC不需要额外的等待
两色增量标记清除法的问题:需要停下来等待GC
三色含义:
- 白色:新建对象的初始状态。如果在一轮GC扫描结束后,对象还是为白色,该对象可以被进行回收了。
- 灰色:表示对象已经被GC扫描过,但该对象引用的对象还没被扫描到。
- 黑色:表示对象已经被GC扫描过,同时该对象引用的对象也被扫描过了。
采用三色增量标记清除法GC的流程:
- 开始:将新创建的对象置为白色
- 初始化:遍历根结点中引用的对象,从白色置为灰色,放到灰色节点列表中
- 标记阶段:扫描判断这个对象引用的对象是否已经被扫描到
- 回收阶段:遍历对象,如果还是白色,说明是没有被引用的对象,那么回收
分代GC
为了减少遍历,5.2中引入了分代GC,但是那时候只要存货过一轮就容易被标记为老年代,这样minorGC的时候就不会再扫描到。这使得局部变量需要等到majorGC的时候才会被扫描到。5.4的GC是需要存活过两轮之后才会被标记为老年代。
分代GC中有两个参数the minor multiplier & the the major multiplier。表示当内存增长到上一轮majorGC的x%之后,就进行下一代的GC。其中minor multiplier的范围是20-200,major multiplier的范围是100-1000。
协程
进程、线程、协程
- 进程:
- 是操作系统中程序运行的基本单位
- 每个进程都有自己独立的内存空间,所以进程间切换的开销比较大
- 通信:不同的进程通过进程间通信
- 优势:稳定;劣势:切换开销大
- 线程:
- 一个进程至少包含一个线程,可能有多个
- 又叫轻量级进程,是CPU调度的最小单位
- 通信:通过共享内存
- 优势:切换快,开销小;劣势:没有进程稳定,容易丢失数据
- 协程:
- 一个线程可以有多个协程,是一种用户态的轻量级线程
- 协程的调度是不被操作系统控制的,由程序来控制
Lua中的协程
协程中的状态:
- running
- suspended
Lua提供的是非对称协程(asymmertric coroutine
),也就是说需要两个函数来控制协程的运行,一个用于挂起协程的执行,另一个用于恢复它的执行。yiled
函数让一个运行中的协程挂起自己,然后通过resume
函数让其恢复运行。
协程案例:
以下是输出效果:每一次yield之后协程都进入了suspended状态,最后协程运行完毕之后就进入了dead状态。
排序算法
冒泡排序
代码:
输出效果:
快速排序
代码:
输出效果:
热更新
C#和Lua交互
底层:
- C#调用Lua:C#文件先调用Lua解析器底层的dll库,然后由dll文件执行lua文件。
- Lua调用C#:
- Wrap方式:C#生成Wrap文件,Lua调用生成的Wrap文件,再由wrap文件去调用C#文件。
- 反射方式:这种方式的执行效率比wrap模式低。
AssetBundle
含义:Unity可以在运行的时候加载的非代码资产。
Unity自带一个BuildPipeline.BuildAssetBundles()可以用来创造AB包。
Lua如何实现热更新
通过Lua的模块加载机制。热更新的核心就是替换package.loaded表中的模块。
- 🔑导出函数require(mode_name)
- 🔑查询全局缓存表package.loaded
- 🔑通过package.searchers查找加载器
原理
Unity游戏热更新包含两个方面:资源+脚本
热更新的方案:AssetsBundle,AB包的本质是缺省的resources
涉及3个目录:
- 游戏资源目录(仅可读):
- windos:Application.dataPath + "/StreamingAsset"
- ios: Application.dataPath + "/Raw"
- android: jar:file://Application.dataPath + "!/assets"
- 数据目录: 可读可写,安卓和ios上的游戏资源目录是仅可读的
- 网络资源地址:服务器地址,用来存放游戏资源的网址
热更的步骤:
- 第一次开启游戏:将游戏资源目录中的内容复制到数据目录中
- 游戏开启后:从网络资源地址下载更新的文件到数据目录中;过程中会先下载服务器上的files.txt,和本地的MD5做比较,更新有变化的文件
- 游戏过程中:从数据目录中获取、解包。
数据目录中包含:不同版本的资源文件+用于版本控制的file.txt(包含:资源文件的名称+MD5)
热更新的过程
Unity3D 热更新的一般方法:
- 导出热更资源
- 打包md5信息
- 上传热更ab到热更服务器
- 上传版本信息到版本服务器
- 游戏流程热更
- 启动游戏
- 根据版本号和平台号去版本服务器上检查是否有热更
- 从热更服务器上下载MD5文件
- 从热更服务器上下载需要热更的资源,解压到热更资源目录
- 游戏运行加载资源,优先到热更目录,然后才是母包资源目录
MD5:通过MD5算法生成电子签名,类似于数字指纹;Unity热更中MD5文件存储的信息包括:AB路径、MD5值、未压缩文件大小、压缩文件大小
版本号
一般是四位:
- 巨大版本号:有巨大的变化才会变
- 整包更新版本号:走应用商店的大的版本迭代
- 服务器协议版本号:需要更新商店的版本号
- 编译/热更版本号:每次热更都+1
参考文献
http://www.lua.org/manual/5.4/manual.html#2.5