Unity-Lua补充
Unity-Lua补充
反直觉常识
安装不多说去看文档。在IDEA中使用emmylua
插件支持lua(settings->Editor->FileType->luaLanguage虽然里面已经添加了一个*.lua.txt
但是不管用再加一个一模一样的就管用了)
在文件开头加入#!/usr/local/bin/lua
就可以像执行shell一样去执行lua
动态,弱类型语言, 在默认情况下,变量总是认为是全局的。全局变量不需要声明,哪怕是语句块或是函数里,除非用local 显式声明局部变量,否则全是全局的。
在实践中,建议给所有变量加上local,除非你确认一定需要全局变量。不加local所造成的奇怪bug会让你终身难忘,而且local更会快
给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil
如果想删除一个全局变量,只需要将变量赋值为nil
只有nil和false是假;0也是真
从1开始的下标(?从其他语言取的时候一定要注意 一般约定,以下划线开头连接遗传大写字母的名字(比如 _VERSION
)是Lua内部全局变量的保留字
本身有JIT速度还可以,和其他语言交互比较慢。
lua语法老严格了,最好按照强迫症风格来写,lua是纯解释,被调用的必须要比调用者先声明。 Lua是运行在一个单线程虚拟机上运行所以只有单线程
奇怪的语法
userdata是和其他语言交互的核心,存储任意C的数据结构;table看你存什么,它相当于把数组和字典混合了起来,底层用哈希表还是顺序表看心情(看你存啥他自己转换
对字符串进行算数操作,Lua会尝试类型转换为数字然后操作,转不了就报错(?
a,b,c=1 -- 1,nil,nil 多的就nil
a=1,2,3 -- 1 少了就扔
type(x)==nil -- false 因为返回的是"nil"
type(x)=="nil" -- 这回对了
x~=nil -- 不等于也很奇葩
"2" + 6=8.0 -- 神奇.
6 .. 8=68 -- 注意返回的是数字 用 .. 的时候必须要有空格
t.i
和t[i]
本质都是函数调用,类似于getattr- table的自然数索引部分会自己扩容一半按2的倍数扩,hash部分每次重哈希消耗比较大
- array部分和其他语言一样,统计多少,然后扩容+memcpy,但是即使是数组扩容也会把旧表的hash部分重新计算,它有一个重新分配元素是整数索引还是hash索引的过程
- hash部分和array内存中是分开管理的,只有设置metatable的
__eq
或超过阈值才会触发rehash,hash会重新计算一遍key插入新表 - 表在存储的时候连续自然数是放在array部分的,负数,
[1, t->asize]
外的数都会被存放在hash部分,rehash的时候会重新计算是不是放入array部分合适 - 建议使用的时候如果知道大小就提前初始化
t={nil,nil,nil}
,如果过于稀疏了array部分甚至会收缩
- table遍历是按照key的hash来的而不是顺序。
- table的名称引用之间赋值也是浅拷贝
- 使用ipairs只会顺着自然数统计数量,截断就停止,使用#和getn也是统计到自然数截断就停止
- 获取表真的有多少那就老老实实
for k,v in pairs(t) do
统计(有点笨 - 当k被删除的时候如果k被table引用作为key是不会被GC的,只是引用没了此时为强引用
- 给table设置
__mode="k"
此时如果key被删除了,那么table也会移除这个k-v - 有三种模式,k,v,kv看字面意思就懂
- 当我把key的引用删除本质上是table在检测这个值是不是只有在此table中是最后一个被引用的地方,如果是那就删除
- 给table设置
local M={}
funciton M.func1() end -- 这样就可以在外面调用
return M -- 结束
local M = require("moduletest") -- 在另一个文件中获得M调用函数即可
多文件其实和单文件一模一样,非local修饰就对全局空间赋予变量操作, local修饰是表示在当前作用域中是本地的,在函数里面声明就限制在函数里。
- 通常在模块开发中,推荐使用 local 表 然后显式返回,这样可以更好地控制作用域
- 加载多文件不仅会拿到返回的table,还会在全局
package.loaded
中注册此module,热更新原理就是把它置为nil然后重新require替换掉- 也会出现循环依赖
- 这个变量通过环境变量 LUA_CPATH 来初始化没有就用默认的
- 公开函数可以访问私有函数,反之不行
- 私有函数实际上也可以赋值或者闭包返回,但是不要这么做
- 引用了之后没有被local修饰变量就会注册到全局,即便没有导出也会有,如果俩文件声明重名变量,可能会导致变量覆盖
- 使用
function M:test() end
相当于带了个py中的self进去,而且调用的时候不能混着调用,:
定义就:
调用点的就点调用。 function M.test(n, ...) local args={...} end
这样就拿到了可变参数,都是按照自然数索引传的
元表,相当于py中的魔术方法,可重载的函数也是有限的。把元表指定给某个表后,相当于为该表添加了特定的重载行为。
两个变量里面都没有发现对应的行为就会报错,行为会优先调用元表的方法没有再调原生,再没有就报错
经典示例两表合并重载加运算符
- 使用
get/setmetatable
获取或者设置某table的元表,拿到就可以修改他原先元表的行为 - 给全局表
_G
设置metatable重载__newindex
__index
可以阻止团队里滥用全局变量 - 使用rawset和rawget可以绕过元表的index相关方法拿值
local mt={}
mt.__add=function(t1,t2) -- +的重载函数
local temp={}
for _,v in pairs(t1)do table.insert(temp,v)end
for _,v in pairs(t2)do table.insert(temp,v)end
return temp
end
local t1={1,2,3}
local t2={2}
setmetatable(t1,mt)
local t3=t1+t2
local st="{"
for _,v in pairs(t3)do st=st..v.."," end
st=st.."}"
print(st)
-- {1,2,3,2,}
看一下协程基本就是yield那一套,可以暂停函数的执行
常用深拷贝
function deepCopy(object)
local lookup_table = {} -- 创建一个查找表,用于存储已经复制过的对象,避免循环引用。
local function _copy(object)
if type(object) ~= "table" then -- 如果对象不是表,则直接返回该对象。
return object
end
if lookup_table[object] then -- 如果对象已经在查找表中,则直接返回对应的已复制对象。
return lookup_table[object]
end
local new_table = {} -- 创建一个新的表,并将当前对象与新表的对应关系存入查找表。
lookup_table[object] = new_table
for key, value in pairs(object) do -- 遍历原表的每个键值对,递归复制值,并设置到新表中。
new_table[_copy(key)] = _copy(value)
end
setmetatable(new_table, getmetatable(object)) -- 设置新表的metatable为原表的metatable,确保元方法等特性也被复制。
return new_table -- 返回新创建的表。
end
return _copy(object) -- 调用内部复制函数,开始复制过程。
end
实现简单的面向对象 看好他的表,利用表的重载方法和metatable, 实现类似于OOP的东西(有点过于灵活了
lua的闭包和其他语言闭包一样,也是函数里面套函数,内部的函数引用的资源内存调用栈变量等都会存放起来,upvalue就是指向引用外部变量的指针,循环upvalue linked list查找,创建的时候标open用完了就置为close,下次GC就删掉。
整个lua就是被包在function main (...)
中,所以所有的函数实际上都是闭包,同理函数upvalue一开始就是指向全局栈,当被当作闭包返回才会指向指向upvalue linked list。
lua的GC使用标记-删除法,分代回收新申请的更容易被扫描,lua虚拟机提供了collectgarbage函数手动回收,但是一般不用。GC八股看一下
但是小心在lua引用的C#对象如果一直不释放就会导致内存泄漏
Warning
热更代码中old_module已经在运行产生的数据变化和变量等不会被影响吗,热更新被替换的函数,万一还有正在运行的不久有问题了吗。我看市面上的解决方法是让玩家退出然后重新进游戏,是不是就是说重新运行游戏相当于lua虚拟机重启了重新加载了一次
gpt
## 为什么游戏要求重新登录?
你的理解完全正确! 重新登录 = Lua虚拟机重启 = 完全重新加载
### 重启的好处:
1. 状态清理 :所有变量、连接、缓存都重新初始化
2. 避免版本混乱 :不会出现新旧代码混合执行的情况
3. 内存清理 :避免内存泄漏和垃圾数据
4. 确保一致性 :所有模块都是同一版本
## 真正的热更新挑战
### 安全的热更新需要:
1. 状态迁移机制 :保存关键数据,更新后恢复
2. 版本兼容性检查 :确保数据结构兼容
3. 执行时机控制 :在安全的时间点进行更新
4. 回滚机制 :更新失败时能够恢复
### 实际应用中的策略:
- 配置热更 :只更新配置数据,相对安全
- 功能热更 :只在特定安全点(如玩家空闲时)进行
- 分模块更新 :只更新独立性强的模块
- 灰度更新 :先在少数玩家上测试
所以你看到的"要求重新登录"确实是最稳妥的方案,避免了热更新的各种风险。真正的无缝热更新技术门槛很高,需要精心设计的架构支持。
lua和C#在交互的时候值类型也可能会因为拆装箱产生GC。 lua中的字符串分长短字符串40个为界,短字符串创建的时候就计算好了hash如果再创建或者调用相同的字符串会指向同一个,长字符串则每次都重新创建并设置GC相关,而且是用到的时候才赋值。
luaGC完了会 整理内存碎片 执行finalizer析构 更新弱引用 不太明白或者不太熟的有 为什么值传递有GC
Lua的GC机制
C#调用Lua细节
实现简单的面向对象
踩坑
作为一门副语言够用就行,回头整理遇到的坑
Last Edit: 2025-07-14 12:26:27