UnLua项目中踩过坑有那些?
1、类型不安全:比如将Vector, FQuat, FTransform这些结构体传错导致Crash, 还是偶现的,为了解决这个问题,不得将所有引用的地方增加类型检查,这个应该是UnLua原始的设计是速度优先吧
2、UObject指针Crash:UObject指针释放了,但UnLua中还在持有它,增加了IsPendingKill的检查,解决了大部分的问题,但还是有较低概率崩溃在这个检查函数里面。
3、UObject 属性访问Crash:这个就比较容易理解了,UObject释放时,Unlua没有这个数据追踪系统,无法正确识别,特别是引用TArray, TSet这些属性时。
4、UClass访问Crash:一般访问UClass前,会先调用IsPendingKill检查UObject是不是有效,但这个检查并一定正确,所以在访问UClass会Crash
5、常出现UMG释放不完全,有 残留,总是需要手动释放,管理麻烦。
6、常出现脚本无法绑定(现在还没有彻底解决)
7、UFunction 的问题,UFunction由于在Lua中引用了,常导致UObject指针无效,或无法GC的问题
问题的根源
我觉得这个根源来自于设计思想吧,速度优先,不做过多的检查,这个风险完全交由上层使用者,这个就好比用C语言写脚本,你要自己管理指针的生命周期。
改进方案
Lua中所有访问到的UE对象,都使用一个对象管理器来管理,Lua 的UserData不再记录一个原始C 对象指针,而是记录一个对象ID,所有获取参数的地方都要通过管理器来获取。这个就避免了野指针的问题。另外UObject的属性引用时,也记录了该对象自己。所以这个管理器对象是一个树型结构,能追踪所有引用的子对象。当UObect父对象释放时,能自动释放所有关联的子属性对象,解决子属性引用非法的问题。
解决了那些问题
1、解决UnLuaGC的问题,新的方案,在Lua里面引用UE对象,不会增加GC计数,也不用担心这个指针合法性的问题
2、UMG控件的委托更为好用,无需要担心GC的问题,并且所有类型的接口都统计,都支持多播,可以支持任意多个回调,任意多个自定义回调参数,并且能自动去重,在界面释放时会自动释放这些回调的引用; 自动去重,如果反复添加同一个回调,只有第一个生效。
3、解决脚本绑定的问题,永远不会出现绑不上脚本的情况。最关键的是支持同一个对象类型,实例化时可以动态绑定不同的脚本,是1对N的关系。并且不限蓝图对象,只要是UObject对象,都可以绑定脚本。
性能问题
也许你会觉得,增加了一个对象管理器中转,会不会出现性能问题?是不是变得更慢了?
答案是完全不用担心,新的方案比UnLua的快了10倍,没有错,你没有看错,是比UnLua快了10倍。
在新方案中,对比测试读写蓝图的属性(int, FString, Vector三个),读写均比UnLua快10倍以上
TArray的Get, Set方法,也比UnLua快了一倍
当然,其他的我还没有对比测试,如果你此感兴趣,欢迎测试
补充测试
补充一下,这个10倍只有绑定脚本的反射读写才是这结果。对于没有绑定的蓝图对象(UObject)的属性读取,只有90%左右的提升,不到2倍。函数调用部分类型的调用有1倍的提升,虽略快一些,但基本上可以认为同等量级的。
这里给出的方案主要是为了提升安全性,性能在其次。
为什么新方案反射是UnLua的10倍?
这里补充一下两个方案的属性查询流程图, 新方案快,是因为在绑定脚本里面直接返回属性对应的lua对象
UnLua是不管什么对象,都会返回一个UserData, 再通过Global_Index触发UserData的检查,走Global_GetUProperty,
多走了一个弯路, 而且中间有大量的与lua的交互API
新的方案查询只有一步,并且不需要与lua交互,查询元表,查询类型类型这些操作。
新方案,蓝图属性的查询与lua table rawget是一个量级,没有多余的操作。
Unlua的流程需要执行十几个步骤,需要还有各种检查,所以慢。
属性查询流程图(UnLua)
新方案的属性查询流程图(FUnLua)
GIT工程
最后送上Git工程地址:https://github.com/fcscript/FUnLua