XLua中lua读写cs对象的原理
LuaCallCS
1. 传递C#对象到Lua
XLua在C#维护了两个数据结构,ObjectPool和ReverseMap。
首次传递一个C#对象obj到Lua时,对象被加入到ObjectPool中,并为它创建一个唯一标识objId,建立obj和objId的双向映射。
ObjectPool: objId->obj ReverseMap: obj->objId
如果该对象的类型是第一次传到Lua,还会为类型创建一个元表typeMetatable。
typeMetatable:包含类成员的访问方法。
把typeMetatable注册到Lua的全局表中,这样就不会被lua gc掉。
LUA_REGISTRY: typeFullName->typeMetatable
创建一个userdata对象放到C#和Lua交互的栈上,userdata的值设为objId,userdata的元表设为typeMetatable。这样userdata就可以通过value找到C#对应的对象,通过元表找到类成员的访问方法。
userdata: value = objId, metatable = typeMetatable
至此,完成了C#对象到Lua的传递,lua从栈上拿到这个userdata对象。
对于引用类型,还会把这个userdata记录到Lua的全局表cache_ref。
cache_ref:objId->userdata
下次再传递同一个对象时,通过obj在ReverseMap里找到objId,通过objId去cache_ref里找到userdata,放到lua栈上。
cache_ref表的value是弱引用,userdata没有其他引用后,gc时会把cache_ref里的键值对删除。
2. 对象释放
对象的释放有两种方式。
Lua gc
在userdata的元表里,设置了__gc方法
typeMetatable{ __gc = LuaGC, }
当一个userdata被gc时,触发LuaGC方法。从userdata上拿到objId, 从ObjectPool和ReverseMap中移除对应的C#对象。cache_ref里的userdata是弱引用,会被gc清理掉。
Destroy UnityEngine.Object
XLua可以定期检查ObjectPool里的UnityEngine.Object对象,如果已经被Destroy,则主动从ObjectPool和ReverseMap中删除,C#对象可以被GC。userdata对象等待lua gc再释放。
3. Lua访问C#成员
一个C#对象在Lua中有一个对应的userdata对象。比如一个GameObject对象go,访问它的transform成员。
local transform = go.transform
go的元表是GameObject的typeMetatable,XLua为GameObject生成了一个_g_get_transform C#函数,并注册到typeMetatable中,
这部分比较复杂,可以简单的认为类的元表中生成了类成员的访问方法,通过成员变量的名字可以查询到对应的方法。GameObject_metatable{__index = function(key)field = {transform = _g_get_transform,...},...if fields[key] ~= nil then return fields[key]end ...end,__newIndex同理。
}
访问字段transform会触发__index方法,查询到gget_transform,这是xlua生成的一段C#函数,该函数通过userdata拿到objId,从而在ObjectPool中拿到C#对象go,将go.transform传给lua。
4. Struct对象的传递
默认情况下,将一个Struct对象传递给Lua,首先会装箱成一个Object对象,记录到ObjectPool中,生成objId,再创建一个userdata给lua,userdata中记录着objId。跟引用类型不同,值类型的userdata不会存到cache_ref中。 所以lua每读取一个struct,都会创建一个C#对象和一个userdata。XLua提供了一个优化方案:GCOptimize。
5. GCOptimize
可以对指定的Struct类型加上GCOptimize标签,XLua会为其生成专门的Push,Get,Update代码。
以Vector3为例。
Push
local pos = transform.position
C#传递一个Vector3到Lua时,创建一个userdata并设置它的元表。
userdata申请的value申请12个字节(Vector3有3个float),把Vector3对象的xyz的值依次复制到userdata的三个float上。userdata不再记录objId,而是直接存储struct的值。
通过GCOptimize的方式,省掉了C#的gc消耗,不过每次push一个vector3对象还会创建一个userdata。
Get
transform.positon = pos
XLua支持两种从lua传值对象到C#。
第一种是传userdata,即这个对象本来就是从C#传到lua的,lua再传回去。这时会直接把userdata里的值取出来,赋给C#的Vector3对象。没有gc,性能比较好。
第二种是构建一个字段相同的table传给C#,transform.positon = {x=0,y=0,z=0}。xlua会查询table里同名的字段,复制值给c#的成员变量。由于根据变量名字的字符串去查表,这个性能没有第一种好。
Update
pos.x = 1
lua修改一个GCOptimize的userdata。
先Get,上文讲了,会创建一个Vector3对象,复制userdata的值。然后在C#修改它的值x。
接下来Update操作,拿到栈顶的userdata,把Vector3对象的值复制到userdata。
6. 在Lua中读写transform position
修改transform的position是一个很常见的需求,看看下面这段lua代码正确吗:
transform.position.x = 1
这段代码可以执行,但并没有效果,transform的position不会被改变。
如果这段代码用c#写,会直接编译器报错。来看看Transform里的position源码:
public Vector3 position
{get {Vector3 ret;this.get_position_Injected(out ret);return ret;}set => this.set_position_Injected(ref value);
}
position并不是一个变量,而是一个属性。所以在C#中,transform.position是一个返回值,C#不允许修改一个返回值,因为修改一个临时变量并没有意义。所以在C#中你得这么写:
var pos = transform.position;
pos.x = 1;
transform.position = pos;
再回头看看那句lua代码 transform.position.x = 1 ,它不过是把get出来的值修改了,要想把位置修改,只能通过position的set访问器。
local pos = transform.position;
pos.x = 1;
transform.position = pos;
看起来很繁琐?但这不是lua的问题,在c#里就这么繁琐。
再来分下这段代码的性能,对Vector3开启GCOptimize,
local pos = transform.position; --transform Get position: Push Vector3,有一个userdata gc
pos.x = 1; --Vector3 set x: 1.Get Vector3,复制userdata的值到Vector3,2.修改Vector3的x,3.Update Vector3,把Vector3的值复制回userdata
transform.position = pos; --transform Set position: Get Vector3,复制userdata的值给Vector3,这个性能不差。
接下来,介绍一种性能比较好的方式。
7. 不带来GC的值类型传递方式
可以看到,引入GCOptimize之后,除了Push操作有lua GC,Get和Update是没有GC的。
传递一个userdata到C#比传递table效率更高,因为传递table会通过字符串查表。
修改一个table的值比修改一个userdata的值效率更高(看起来,但没测过),因为每修改userdata的一个成员,就会拷贝两次Vector3,而修改一个table只用一次查表。
常见的一个需求是,在lua维护一个位置信息,lua会更新这个位置数据,并设置到c#对象上。如果创建一个Vector3的userdata对象,传值给C#比较快,但更新它的值比较低效。如果创建一个table对象,更新它的值比较高效,但传给C#的时候会有3次查表。
综上所述,推荐最简单的方案,在C#封装一些Util函数,把对象的成员变量通过参数的方式进行传递,在lua维护x,y,z。
public static void GetPosition(Transform t, out float x, out float y, out float z) {Vector3 v = t.position;x = v.x;y = v.y;z = v.z;
}
public static void SetPosition(Transform t, float x, float y, float z) {t.position = new Vector3(x, y, z);
}
CSCallLua
1.传递Lua函数到c#
首先在lua全局表注册lua函数,避免被GC
LUA_REGISTRY: luaReference->luaFunction
创建DelegateBridgeBase对象,记住luaReference
bridge:luaReference
bridge对象里根据类型创建Delegate,一个lua函数可能在C#侧被用作不同类型的Delegate
bridge:luaReference<DelegateType,Delegate>
Delegate 的实例是生成的C#代码,通过luaReference访问lua函数,这里面有对bridge的引用,所以持有这个delegate对象就不会释放bridge对象。
最后返回Delegate给使用者持有。bridge是局部变量,没有被其他对象引用。
2. DelegateBridge gc过程
c#侧释放对delegate对象,delegate对象释放后,bridge的引用计数归零,被GC,在gc方法中,从lua全局注册表清理luaReference,对应的lua函数引用计数减一。
交互总结
无论是cs call lua还是lua call cs,原理是一样的。
从A环境传递一个对象a到B环境,会先在A环境的全局存储这个对象并建立一个a_id,保证这个对象不被GC,把a_id传递到B环境并构建一个包装对象a_wrapper,包含a_id,这样a_wrapper可以通过a_id访问到a。a_wrapper的GC方法里,需要把A全局表里的a清除掉,这样就能触发a的GC。
对于引用对象,还会记录到一个弱引用表,<a_id,a_wrapper>,下次传递a对象时,就能找到a_wrapper不用再创建。
globalReg: a_id-a
[a_wrapper{a_id,GC={delete in globalReg}}]
weaktable:<aid,a_wrapper>
两个GC系统的延迟GC问题
一个C#对象被lua持有着引用,当lua释放引用时,c#对象不会直接gc,需要等lua gc,lua gc可能会很慢触发,但它背后的引用的c#对象可能占用着较大的内存。
解决方案是lua对象释放的时候,手动调用一下c#对象的释放,把较大的内存释放掉,比如RawFileAsset对象中的byte数组data,lua释放这个对象时,手动把data置null,这样data就可以及时被GC,不用等lua gc。
相关文章:
XLua中lua读写cs对象的原理
LuaCallCS 1. 传递C#对象到Lua XLua在C#维护了两个数据结构,ObjectPool和ReverseMap。 首次传递一个C#对象obj到Lua时,对象被加入到ObjectPool中,并为它创建一个唯一标识objId,建立obj和objId的双向映射。 ObjectPool: objId-…...
新手小白怎么选择配音软件?
现在的配音软件软件很多,各种类型的都比较多,对于新手小白来说不知该如何选择,今天就来给你分享几款好用的配音软件。不论是制作短视频还是制作平常音频都完全可以。 第一款:悦音配音 这是一款专业的视频配音软件,多端…...
linux查看硬件信息命令
文章目录 cpu内核版本内存硬盘主板服务器参考链接 cpu cat /proc/cpuinfo 一个物理CPU可以有1个或者多个物理内核,一个物理内核可以作为1个或者2个逻辑CPU。 物理CPU数就是主板上实际插入的CPU数量。 在Linux上cat /proc/cpuinfo,会打印每个cpu的信息 …...
TSINGSEE青犀省级高速公路视频上云联网方案:全面实现联网化、共享化、智能化
一、需求背景 随着高速铁路的建设及铁路管理的精细化,原有的模拟安防视频监控系统已经不能满足视频监控需求,越来越多站点在建设时已开始规划高清安防视频监控系统。高速公路视频监控资源非常丰富,需要对其进行综合管理与利用。通过构建监控…...
知识图谱相关的操作
微软生成自己的图谱:GitHub - microsoft/SmartKG: This project accepts excel files as input which contains the description of a Knowledge Graph (Vertexes and Edges) and convert it into an in-memory Graph Store. This project implements APIs to searc…...
【Javascript】json
目录 什么是json? 书写格式 json 序列化和反序列化 序列化 反序列化 什么是json? JSON(JavaScript Object Notation)是⼀种轻量级的数据交换格式,它基于JavaScript的⼀个⼦集,易于⼈的编写和阅读,也易于机器解析…...
零资源的大语言模型幻觉预防
零资源的大语言模型幻觉预防 摘要1 引言2 相关工作2.1 幻觉检测和纠正方法2.2 幻觉检测数据集 3 方法论3.1 概念提取3.2 概念猜测3.2.1 概念解释3.2.2 概念推理 3.3 聚合3.3.1 概念频率分数3.3.2 加权聚合 4 实验5 总结 摘要 大语言模型(LLMs)在各个领域…...
智能终端界面自动化测试操作工具 - Appium常见用法
1. Appium 是什么可以做什么? Appium 是一款开源的移动应用自动化测试框架,用于测试移动应用程序的功能和用户界面。它支持多种移动平台,包括 Android 和 iOS,可以使用多种编程语言进行脚本编写,如 Python、Java、Jav…...
结构体数组经典运用---选票系统
结构体的引入 1、概念:结构体和其他类型基础数据类型一样,例如int类型,char类型,float类型等。整型数,浮点型数,字符串是分散的数据表示,有时候我们需要用很多类型的数据来表示一个整体&#x…...
code too large
描述:比较尴尬,一个方法的代码接近10000行了,部署服务器的时候提示(java :code[255,21] too large),提示代码过长,无法运行。 查看了一下百度:解决的思路 JVM规范:「类或接口可以声明的字段数量限制在 655…...
vue中把弹出层.vue文件注册成组件供其他.vue文件调用的写法
背景:因弹出层多个页面的详情都是一样的,因此把弹出层定义成组件,多次调用 定义组件的过程中出现很多问题,因此再次记录最终成功的写法 一、 简单实现页面调用弹出层组件的打开弹出层方法: 1. 弹出层组件 (in…...
mac 查看GPU使用
首先搜索活动监视器 然后 点击窗口->gpu历史记录 记住不是立马出结果,而是 需要等半分钟左右的...
工业4.0的安全挑战与解决方案
在当今数字化时代,工业4.0已经成为制造业的核心趋势。工业4.0的兴起为生产企业带来了前所未有的效率和灵活性,但与之伴随而来的是一系列的安全挑战。本文将深入探讨工业4.0的安全挑战,并提供一些解决方案,以确保制造业的数字化转型…...
如何查找特定基因集合免疫基因集 炎症基因集
温故而知新,再次看下Msigdb数据库。它更新了很多内容。给我们提供了一个查询基因集的地方。 关注微信:生信小博士 比如纤维化基因集: 打开网址:https://www.gsea-msigdb.org/gsea/msigdb/index.jsp 2.点击search 3.比如我对纤维…...
轮转数组(Java)
大家好我是苏麟 , 这篇文章是凑数的 ... 轮转数组 描述 : 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 题目 : 牛客 NC110 旋转数组: 这里牛客给出了数组长度我们直接用就可以了 . LeetCode 189.轮转数组 : 189. 轮…...
Spring体系结构
Spring体系结构 核心容器 核心容器由 spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL,Spring 表达式语言,Spring Expression Language)等模块组成&…...
PostgreSQL basebackup备份和恢复
一、概述 备份和恢复分为逻辑和物理,这里指物理备份和恢复。 PG的物理备份依赖basebackup,这差不多就是数据目录的拷贝,还依赖归档日志。 恢复分为完全恢复和PITR恢复,它们都需要归档日志,它们关键的差别是…...
XTU-OJ 1248-Alice and Bob
Alice和Bob在玩骰子游戏,他们用三颗六面的骰子,游戏规则如下: 点数的优先级是1点最大,其次是6,5,4,3,2。三个骰子点数相同,称为"豹子",豹子之间按点数优先级比较大小。如果只有两个骰子点数相同&…...
第四章 文件管理 十、文件系统的全局结构
目录 一、文件系统的建立 1、原始磁盘 2、物理格式化后 3、逻辑格式化后 二、文件系统在内存中的结构 三、系统调用背后的过程 一、文件系统的建立 1、原始磁盘 2、物理格式化后 物理格式化,即低级格式化――划分扇区,检测坏扇区,并用…...
【PythonGIS】基于高德Api实现批量地址查询经纬度
之前因为同事需要几千个小区的经纬度信息,所以就帮同事写了一段Python代码,通过调取高德地图的api实现地址查询经纬度这个功能。对于如何使用经纬度查询地址的方法,我之前分享过博文:【Python入门教程】获取图片可视化精准定位&am…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
