在Lua中实现Rust对象的绑定

news/2024/10/21 9:51:03

实现目标:能将Rust对象快速的映射到lua中使用,尽可能的简化使用。

功能目标

struct HcTestMacro为例:

  1. 类型构建,在lua调用local val = HcTestMacro.new()可构建
  2. 类型析构,在lua调用HcTestMacro.del(val)可析建,仅限light use**rdata
  3. 字段的映射,假设有字段hc,我们需要能快速的进行字段的取值赋值
  • 取值val.hc或者val:get_hc()均可进行取值
  • 赋值val.hc = "hclua"或者val:set_hc("hclua")均可进行取值
  1. 类型方法,注册类方法,比如额外的方法call1,那我们就可以通过注册到lua虚拟机,由于lua虚拟机可能不会是全局唯一的,所以不好通过宏直接注册
// 直接注册函数注册
HcTestMacro::object_def(&mut lua, "ok", hclua::function1(HcTestMacro::ok));
// 闭包注册单参数
HcTestMacro::object_def(&mut lua, "call1", hclua::function1(|obj: &HcTestMacro| -> u32 {obj.field
}));
// 闭包注册双参数
HcTestMacro::object_def(&mut lua, "call2", hclua::function2(|obj: &mut HcTestMacro, val: u32| -> u32 {obj.field + val
}));
  1. 静态方法,有些静态类方法,即不实际化对象进行注册可相当于模块
HcTestMacro::object_static_def(&mut lua, "sta_run", hclua::function0(|| -> String {"test".to_string()
}));

完整示列代码

use hclua_macro::ObjectMacro;#[derive(ObjectMacro, Default)]
#[hclua_cfg(name = HcTest)]
#[hclua_cfg(light)]
struct HcTestMacro {#[hclua_field]field: u32,#[hclua_field]hc: String,
}impl HcTestMacro {fn ok(&self) {println!("ok!!!!");}
}fn main() {let mut lua = hclua::Lua::new();let mut test = HcTestMacro::default();HcTestMacro::register(&mut lua);// 直接注册函数注册HcTestMacro::object_def(&mut lua, "ok", hclua::function1(HcTestMacro::ok));// 闭包注册单参数HcTestMacro::object_def(&mut lua, "call1", hclua::function1(|obj: &HcTestMacro| -> u32 {obj.field}));// 闭包注册双参数HcTestMacro::object_def(&mut lua, "call2", hclua::function2(|obj: &mut HcTestMacro, val: u32| -> u32 {obj.field + val}));HcTestMacro::object_static_def(&mut lua, "sta_run", hclua::function0(|| -> String {"test".to_string()}));lua.openlibs();let val = "print(aaa);print(\"cccxxxxxxxxxxxxxxx\");print(type(HcTest));local v = HcTest.new();print(\"call ok\", v:ok())print(\"call1\", v:call1())print(\"call2\", v:call2(2))print(\"kkkk\", v.hc)v.hc = \"dddsss\";print(\"kkkk ok get_hc\", v:get_hc())v.hc = \"aa\";print(\"new kkkkk\", v.hc)v:set_hc(\"dddddd\");print(\"new kkkkk1\", v.hc)print(\"attemp\", v.hc1)print(\"vvvvv\", v:call1())print(\"static run\", HcTest.sta_run())HcTest.del(v);";let _: Option<()> = lua.exec_string(val);
}

源码地址

hclua Rust中的lua绑定。

功能实现剥析

通过derive宏进行函数注册:#[derive(ObjectMacro, Default)]
通过attrib声明命名:#[hclua_cfg(name = HcTest)],配置该类在lua
中的名字为HcTest,本质上在lua里注册全局的table,通过在该table下注册
HcTest { new = function(), del = function() }
通过attrib注册生命:#[hclua_cfg(light)],表示该类型是light userdata即生命周期由Rust控制,默认为userdata即生命周期由Lua控制,通过__gc进行回收。
通过attrib声明字段:#[hclua_field]放到字段前面,即可以注册字段使用,在derive生成的时候判断是否有该字段,进行字段的映射。

derive宏实现

主要源码在 hclua-macro 实现, 完整代码可进行参考。

  1. 声明并解析ItemStruct
#[proc_macro_derive(ObjectMacro, attributes(hclua_field, hclua_cfg))]
pub fn object_macro_derive(input: TokenStream) -> TokenStream {let ItemStruct {ident,fields,attrs,..} = parse_macro_input!(input);
  1. 解析Config,即判断类名及是否light
let config = config::Config::parse_from_attributes(ident.to_string(), &attrs[..]).unwrap();
  1. 解析字段并生成相应的函数
let functions: Vec<_> = fields.iter().map(|field| {let field_ident = field.ident.clone().unwrap();if field.attrs.iter().any(|attr| attr.path().is_ident("hclua_field")) {let get_name = format_ident!("get_{}", field_ident);let set_name = format_ident!("set_{}", field_ident);let ty = field.ty.clone();quote! {fn #get_name(&mut self) -> &#ty {&self.#field_ident}fn #set_name(&mut self, val: #ty) {self.#field_ident = val;}}} else {quote! {}}}).collect();let registers: Vec<_> = fields.iter().map(|field| {let field_ident = field.ident.clone().unwrap();if field.attrs.iter().any(|attr| attr.path().is_ident("hclua_field")) {let ty = field.ty.clone();let get_name = format_ident!("get_{}", field_ident);let set_name = format_ident!("set_{}", field_ident);quote!{hclua::LuaObject::add_object_method_get(lua, &stringify!(#field_ident), hclua::function1(|obj: &mut #ident| -> &#ty {&obj.#field_ident}));// ...}} else {quote!{}}
}).collect();

通过生成TokenStream数组,在最终的时候进行源码展开#(#functions)*即可以得到我们的TokenStream拼接的效果。

  1. 生成最终的代码
let name = config.name;
let is_light = config.light;
let gen = quote! {impl #ident {fn register_field(lua: &mut hclua::Lua) {#(#registers)*}fn register(lua: &mut hclua::Lua) {let mut obj = if #is_light {hclua::LuaObject::<#ident>::new_light(lua.state(), &#name)} else {hclua::LuaObject::<#ident>::new(lua.state(), &#name)};obj.create();Self::register_field(lua);}fn object_def<P>(lua: &mut hclua::Lua, name: &str, param: P)whereP: hclua::LuaPush,{hclua::LuaObject::<#ident>::object_def(lua, name, param);}#(#functions)*}// ...
};
gen.into()

这样子我们通过宏就实现了我们快速的实现方案。

Field映射的实现

Lua对象映射中,type(val)为一个object变量,在这基础上进行访问的都将会触发元表的操作metatable

Field的获取

我们访问任何对象如val.hc

  1. 查找val中是否有hc的值,若存在直接返回
  2. 查找object中对应的元表lua_getmetatable若为meta
  3. 找到__index的key值,若不存在则返回空值
  4. 调用__index函数,此时调用该数第一个参数为val,第二个参数为hc
  5. 此时有两种可能,一种是访问函数跳转6,一种是访问变量跳转7,
  6. 将直接取出meta["hc"]返回给lua,如果是值即为值,为函数则返回给lua的后续调用,通常的形式表达为val:hc()val.hc(val)实现调用,结束流程
  7. 因为变量是一个动态值,我们并未存在metatable中,所以需要额外的调用取出正确值,我们将取出的函数手动继续在调用lua_call(lua, 1, 1);即可以实现字段的返回

注:在变量中该值是否为字段处理过程会有相对的差别,又需要高效的进行验证,这里用的是全局的静态变量来存储是否为该类型的字段值。

lazy_static! {static ref FIELD_CHECK: RwLock<HashSet<(TypeId, &'static str)>> = RwLock::new(HashSet::new());
}

完整源码:

extern "C" fn index_metatable(lua: *mut sys::lua_State) -> libc::c_int {unsafe {if lua_gettop(lua) < 2 {let value = CString::new(format!("index field must use 2 top")).unwrap();return luaL_error(lua, value.as_ptr());}}if let Some(key) = String::lua_read_with_pop(lua, 2, 0) {let typeid = Self::get_metatable_real_key();unsafe {sys::lua_getglobal(lua, typeid.as_ptr());let is_field = LuaObject::is_field(&*key);let key = CString::new(key).unwrap();let t = lua_getfield(lua, -1, key.as_ptr());if !is_field {if t == sys::LUA_TFUNCTION {return 1;} else {return 1;}}lua_pushvalue(lua, 1);lua_call(lua, 1, 1);1}} else {0}
}

此时字段的获取已经完成了。

Field的设置

此时我们需要设置对象val.hc = "hclua"

  1. 查找val中是否有hc的值,若有直接设置该值
  2. 查找object中对应的元表lua_getmetatable若为meta
  3. 找到__newindex的key值,若不存在则返回空值
  4. 调用__newindex函数,此时调用该数第一个参数为val,第二个参数为hc,第三个参数为字符串"hclua"
  5. 若此时判断第二个参数不是字段,则直接返回lua错误内容
  6. 此时我们会在第二个参数的key值后面添加__set即为hc__set,我们查找meta["hc__set"] 若为空则返回失败,若为函数则转到7
  7. 我们将调用该函数,并将第一个参数val,第三个参数hclua,并进行函数调用
lua_pushvalue(lua, 1);
lua_pushvalue(lua, 3);
lua_call(lua, 2, 1);

此时字段的设置已经完成了。

小结

Lua的处理速度较慢,为了高性能,通常有许多函数会放到Rust层或者底层进行处理,此时有一个快速的映射就可以方便代码的快速使用复用,而通过derive宏,我们可以快速的构建出想要的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/74059.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

20222323 2024-2025-1 《网络与系统攻防技术》实验三实验报告

1.实验内容 1.1实践目标 (1)正确使用msf编码器,veil-evasion,自己利用shellcode编程等免杀工具或技巧 正确使用msf编码器,使用msfvenom生成如jar之类的其他文件 veil,加壳工具 使用C + shellcode编程 (2)通过组合应用各种技术实现恶意代码免杀 (3)用另一电脑实测,在杀软开…

Day10 备战CCF-CSP练习

202303-4Day10 题目描述 十滴水是一个非常经典的小游戏。小 \(C\) 正在玩一个一维版本的十滴水游戏。 我们通过一个例子描述游戏的基本规则。 游戏在一个$ 1c$ 的网格上进行,格子用整数$ x(1≤x≤c)$ 编号,编号从左往右依次递增。 网格内 \(m\) 个格子里有 \(1∼4\) 滴水,其…

YOLOv11环境搭建推理测试

引子 2024年9月30日,Ultralytics在其活动YOLOVision中正式发布了YOLOv 11。YOLOv 11是由位于美国和西班牙的Ultralytics团队开发的YOLO的最新版本。几个月前YOLOv10发布(感兴趣的童鞋可以移步https://blog.csdn.net/zzq1989_/article/details/139408779?spm=1001.2014.3001.…

SDCN:《Structural Deep Clustering Network》

代码:https://github.com/461054993/SDCN 摘要 聚类是数据分析中的一项基本任务。 最近,主要从深度学习方法中获得灵感的深度聚类实现了最先进的性能,并引起了相当大的关注。 当前的深度聚类方法通常借助深度学习强大的表示能力(例如自动编码器)来提高聚类结果,这表明学习…

TCP和UDP的报文格式

TCP和UDP的报文格式概要了解TCP和UDP的报文格式对于网络通信、系统设计、故障排查和安全性等多个方面都非常重要。一、TCP 报文格式(Transmission Control Protocol)TCP是面向连接、可靠的传输协议,其报文格式较复杂。TCP报文的格式如下: 上图简化如下:| 源端口(16位…

008数据绑定

v-bind 单向数据绑定 v-model 双向数据绑定

极速、便捷!一个接入 AI 的匿名在线即时聊天室!

AQChat —— 一个已接入 AI 的极速、便捷的匿名在线即时 AI 聊天室。基于 Netty 以及 protobuf 协议实现高性能,对标游戏后端开发。大家好,我是 Java陈序员。 之前给大家推荐过一款基于 livekit 和 Next.js 的匿名聊天室。 今天,再给大家介绍一个便捷开源的匿名在线聊天室,…

MoH:融合混合专家机制的高效多头注意力模型及其在视觉语言任务中的应用

在深度学习领域,多头注意力机制一直是Transformer模型的核心组成部分,在自然语言处理和计算机视觉任务中取得了巨大成功。然而,研究表明并非所有的注意力头都具有同等重要性,许多注意力头可以在不影响模型精度的情况下被剪枝。基于这一洞察,这篇论文提出了一种名为混合头注意力…