首页 > 其他分享 >在Lua中实现Rust对象的绑定tT

在Lua中实现Rust对象的绑定tT

时间:2024-10-21 13:58:34浏览次数:7  
标签:hclua val Lua lua tT field let hc Rust

合集 - 算法(7)1.TimerWheel(计时轮)在Rust中的实现及源码解析06-122.Rust性能分析之测试及火焰图,附(lru,lfu,arc)测试06-183.Lru-k在Rust中的实现及源码解析06-214.带有ttl的Lru在Rust中的实现及源码解析06-24:westworld加速5.Lfu缓存在Rust中的实现及源码解析06-276.Rust宏之derive的设计及实战10-187.在Lua中实现Rust对象的绑定10-21收起
实现目标:能将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(lua: &mut hclua::Lua, name: &str, param: P) |
|  | where |
|  | P: 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'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宏,我们可以快速的构建出想要的功能。

标签:hclua,val,Lua,lua,tT,field,let,hc,Rust
From: https://www.cnblogs.com/westworldss/p/18489356

相关文章

  • 【论文阅读】【IEEE TGARS】RRNet: Relational Reasoning Network WithParallel Multi
    引言任务:光学遥感显著目标检测-关系推理论文地址:RRNet:RelationalReasoningNetworkWithParallelMultiscaleAttentionforSalientObjectDetectioninOpticalRemoteSensingImages|IEEEJournals&Magazine|IEEEXplore代码地址:rmcong/RRNet_TGRS2021(g......
  • 使用http请求jenkins 构建Job
    1.创建专门用于api的用户,授予对应项目的权限2.生成api_tocken3.远程构建打开job的构建界面复制url使用buildWithParameters接口http://192.168.xx.xxx:12000/view/运维工具/job/lijq-test/buildWithParameters授权处填写用户id作为用户名前面生成的api_tocken即为密......
  • 关于maven配置文件settings的参数说明
    Maven的配置文件(Maven的安装目录/conf/settings.xml)和Maven仓库下(默认的Maven仓库的是用户家目录下的.m2文件,可以另行制定)的settings.xml文件在Maven中提供了一个settings.xml文件来定义Maven的全局环境信息。这个文件会存在于Maven的安装目录的conf子目录下面,或者是用户家目......
  • 子域名太多如何实现HTTPS?一张通配符SSL证书全搞定
    在当今数字化时代,网站安全性已经成为网站运营者以及访问者都非常关注的重要问题。部署SSL证书实现HTTPS加密,确保数据传输安全,防止信息被泄露或篡改,消除浏览器“不安全”提示,提高网站安全性以及可信任度已成为必然趋势。然而,随着业务的发展,企业会解析很多子域名以满足日益增长的......
  • 在Lua中实现Rust对象的绑定
    实现目标:能将Rust对象快速的映射到lua中使用,尽可能的简化使用。功能目标以structHcTestMacro为例:类型构建,在lua调用localval=HcTestMacro.new()可构建类型析构,在lua调用HcTestMacro.del(val)可析建,仅限lightuse**rdata字段的映射,假设有字段hc,我们需要能快速的进行字段......
  • uv 基于rust 编写的python 包管理以及项目管理工具
    uv基于rust编写的python包管理以及项目管理工具包含的特性简单工具可以替换pip,pip-tools,pipx,poetry,pyenv等比pip快10-100倍安装以及管理python版本运行以及安装python应用运行脚本支持类似cargo模式的workspace磁盘空间高效说明对于希望提示快速python包下......
  • Codeforces Round 979 (Div. 2) C. A TRUE Battle
    题目链接:题目大意:Alice和Bob先后轮流向一串01字符串中加入or或and,前者想让结果为1后者想让结果为0,问Alice能不能赢。思路:对于相同的两个布尔值,or和and都不能改变它们,而对于Alice,他倾向于向01之间加入or,Bob则想加入and。一开始我想比较1和0的多少不就行了吗,但是不对,当你......
  • http://192.168.14.232/contest/59
    A:创历史新低dalao:d<=5,所以一个位置上只能是[i-d,i+d],考虑状压ljx'scode#include<bits/stdc++.h>usingnamespacestd;constintmaxn=505;constintmod=998244353;intread(){ intret=0,f=1;charch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-f;......
  • HTTPS抓包
    抓包工具BurpSuite:起个代理服务器,拦截和解析http请求Proxifier:给不支持设置代理的程序加上代理抓包对象浏览器CA证书添加到系统就行使用WinInet通信程序直接添加CA到系统就能搞定Proxifier+Fiddler抓取PC客户端数据包https://wiki.wireshark.org/TLSJava程......
  • 如何下载 X(Twitter) 视频?1 分钟学会,以后再也不用找人帮忙了
    最近发现了一个特别好用的小技巧,忍不住想分享给你们。 我们平时刷X(Twitter)时,总会碰到那些特别有意思的视频,有时候真想保存下来,或者发给朋友,但总觉得下载很麻烦。结果我最近终于找到了一个超级简单的方法,真的是三步搞定! 第一步:找到视频的URL 其实很简单,点开你......