当前位置: 首页 > news >正文

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#维护了两个数据结构&#xff0c;ObjectPool和ReverseMap。 首次传递一个C#对象obj到Lua时&#xff0c;对象被加入到ObjectPool中&#xff0c;并为它创建一个唯一标识objId&#xff0c;建立obj和objId的双向映射。 ObjectPool: objId-…...

新手小白怎么选择配音软件?

现在的配音软件软件很多&#xff0c;各种类型的都比较多&#xff0c;对于新手小白来说不知该如何选择&#xff0c;今天就来给你分享几款好用的配音软件。不论是制作短视频还是制作平常音频都完全可以。 第一款&#xff1a;悦音配音 这是一款专业的视频配音软件&#xff0c;多端…...

linux查看硬件信息命令

文章目录 cpu内核版本内存硬盘主板服务器参考链接 cpu cat /proc/cpuinfo 一个物理CPU可以有1个或者多个物理内核&#xff0c;一个物理内核可以作为1个或者2个逻辑CPU。 物理CPU数就是主板上实际插入的CPU数量。 在Linux上cat /proc/cpuinfo&#xff0c;会打印每个cpu的信息 …...

TSINGSEE青犀省级高速公路视频上云联网方案:全面实现联网化、共享化、智能化

一、需求背景 随着高速铁路的建设及铁路管理的精细化&#xff0c;原有的模拟安防视频监控系统已经不能满足视频监控需求&#xff0c;越来越多站点在建设时已开始规划高清安防视频监控系统。高速公路视频监控资源非常丰富&#xff0c;需要对其进行综合管理与利用。通过构建监控…...

知识图谱相关的操作

微软生成自己的图谱&#xff1a;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&#xff1f; 书写格式 json 序列化和反序列化 序列化 反序列化 什么是json&#xff1f; JSON(JavaScript Object Notation)是⼀种轻量级的数据交换格式&#xff0c;它基于JavaScript的⼀个⼦集&#xff0c;易于⼈的编写和阅读&#xff0c;也易于机器解析…...

零资源的大语言模型幻觉预防

零资源的大语言模型幻觉预防 摘要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 总结 摘要 大语言模型&#xff08;LLMs&#xff09;在各个领域…...

智能终端界面自动化测试操作工具 - Appium常见用法

1. Appium 是什么可以做什么&#xff1f; Appium 是一款开源的移动应用自动化测试框架&#xff0c;用于测试移动应用程序的功能和用户界面。它支持多种移动平台&#xff0c;包括 Android 和 iOS&#xff0c;可以使用多种编程语言进行脚本编写&#xff0c;如 Python、Java、Jav…...

结构体数组经典运用---选票系统

结构体的引入 1、概念&#xff1a;结构体和其他类型基础数据类型一样&#xff0c;例如int类型&#xff0c;char类型&#xff0c;float类型等。整型数&#xff0c;浮点型数&#xff0c;字符串是分散的数据表示&#xff0c;有时候我们需要用很多类型的数据来表示一个整体&#x…...

code too large

描述&#xff1a;比较尴尬&#xff0c;一个方法的代码接近10000行了&#xff0c;部署服务器的时候提示(java :code[255,21] too large),提示代码过长&#xff0c;无法运行。 查看了一下百度&#xff1a;解决的思路 JVM规范&#xff1a;「类或接口可以声明的字段数量限制在 655…...

vue中把弹出层.vue文件注册成组件供其他.vue文件调用的写法

背景&#xff1a;因弹出层多个页面的详情都是一样的&#xff0c;因此把弹出层定义成组件&#xff0c;多次调用 定义组件的过程中出现很多问题&#xff0c;因此再次记录最终成功的写法 一、 简单实现页面调用弹出层组件的打开弹出层方法&#xff1a; 1. 弹出层组件 (in…...

mac 查看GPU使用

首先搜索活动监视器 然后 点击窗口->gpu历史记录 记住不是立马出结果&#xff0c;而是 需要等半分钟左右的...

工业4.0的安全挑战与解决方案

在当今数字化时代&#xff0c;工业4.0已经成为制造业的核心趋势。工业4.0的兴起为生产企业带来了前所未有的效率和灵活性&#xff0c;但与之伴随而来的是一系列的安全挑战。本文将深入探讨工业4.0的安全挑战&#xff0c;并提供一些解决方案&#xff0c;以确保制造业的数字化转型…...

如何查找特定基因集合免疫基因集 炎症基因集

温故而知新&#xff0c;再次看下Msigdb数据库。它更新了很多内容。给我们提供了一个查询基因集的地方。 关注微信&#xff1a;生信小博士 比如纤维化基因集&#xff1a; 打开网址&#xff1a;https://www.gsea-msigdb.org/gsea/msigdb/index.jsp 2.点击search 3.比如我对纤维…...

轮转数组(Java)

大家好我是苏麟 , 这篇文章是凑数的 ... 轮转数组 描述 : 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 题目 : 牛客 NC110 旋转数组: 这里牛客给出了数组长度我们直接用就可以了 . LeetCode 189.轮转数组 : 189. 轮…...

Spring体系结构

Spring体系结构 核心容器 核心容器由 spring-core&#xff0c;spring-beans&#xff0c;spring-context&#xff0c;spring-context-support和spring-expression&#xff08;SpEL&#xff0c;Spring 表达式语言&#xff0c;Spring Expression Language&#xff09;等模块组成&…...

PostgreSQL basebackup备份和恢复

一、概述 备份和恢复分为逻辑和物理&#xff0c;这里指物理备份和恢复。 PG的物理备份依赖basebackup&#xff0c;这差不多就是数据目录的拷贝&#xff0c;还依赖归档日志。 恢复分为完全恢复和PITR恢复&#xff0c;它们都需要归档日志&#xff0c;它们关键的差别是&#xf…...

XTU-OJ 1248-Alice and Bob

Alice和Bob在玩骰子游戏&#xff0c;他们用三颗六面的骰子&#xff0c;游戏规则如下&#xff1a; 点数的优先级是1点最大&#xff0c;其次是6,5,4,3,2。三个骰子点数相同&#xff0c;称为"豹子"&#xff0c;豹子之间按点数优先级比较大小。如果只有两个骰子点数相同&…...

第四章 文件管理 十、文件系统的全局结构

目录 一、文件系统的建立 1、原始磁盘 2、物理格式化后 3、逻辑格式化后 二、文件系统在内存中的结构 三、系统调用背后的过程 一、文件系统的建立 1、原始磁盘 2、物理格式化后 物理格式化&#xff0c;即低级格式化――划分扇区&#xff0c;检测坏扇区&#xff0c;并用…...

【PythonGIS】基于高德Api实现批量地址查询经纬度

之前因为同事需要几千个小区的经纬度信息&#xff0c;所以就帮同事写了一段Python代码&#xff0c;通过调取高德地图的api实现地址查询经纬度这个功能。对于如何使用经纬度查询地址的方法&#xff0c;我之前分享过博文&#xff1a;【Python入门教程】获取图片可视化精准定位&am…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

#Uniapp篇:chrome调试unapp适配

chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器&#xff1a;Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...

在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南

在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...

【51单片机】4. 模块化编程与LCD1602Debug

1. 什么是模块化编程 传统编程会将所有函数放在main.c中&#xff0c;如果使用的模块多&#xff0c;一个文件内会有很多代码&#xff0c;不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里&#xff0c;在.h文件里提供外部可调用函数声明&#xff0c;其他.c文…...