在Lua中实现Rust对象的绑定
实现目标:能将Rust对象快速的映射到lua中使用,尽可能的简化使用。
功能目标
以struct HcTestMacro
为例:
- 类型构建,在lua调用
local val = HcTestMacro.new()
可构建 - 类型析构,在lua调用
HcTestMacro.del(val)
可析建,仅限light use**rdata
- 字段的映射,假设有字段
hc
,我们需要能快速的进行字段的取值赋值
- 取值:
val.hc
或者val:get_hc()
均可进行取值 - 赋值:
val.hc = "hclua"
或者val:set_hc("hclua")
均可进行取值
- 类型方法,注册类方法,比如额外的方法
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
}));
- 静态方法,有些静态类方法,即不实际化对象进行注册可相当于模块
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 实现, 完整代码可进行参考。
- 声明并解析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);
- 解析Config,即判断类名及是否light
let config = config::Config::parse_from_attributes(ident.to_string(), &attrs[..]).unwrap();
- 解析字段并生成相应的函数
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拼接的效果。
- 生成最终的代码
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)
where
P: hclua::LuaPush,
{
hclua::LuaObject::<#ident>::object_def(lua, name, param);
}
#(#functions)*
}
// ...
};
gen.into()
这样子我们通过宏就实现了我们快速的实现方案。
Field映射的实现
Lua对象映射中,type(val)
为一个object变量,在这基础上进行访问的都将会触发元表的操作metatable
Field的获取
我们访问任何对象如val.hc
:
- 查找val中是否有hc的值,若存在直接返回
- 查找object中对应的元表
lua_getmetatable
若为meta - 找到
__index
的key值,若不存在则返回空值 - 调用
__index
函数,此时调用该数第一个参数为val
,第二个参数为hc
- 此时有两种可能,一种是访问函数跳转6,一种是访问变量跳转7,
- 将直接取出meta["hc"]返回给lua,如果是值即为值,为函数则返回给lua的后续调用,通常的形式表达为
val:hc()
即val.hc(val)
实现调用,结束流程 - 因为变量是一个动态值,我们并未存在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"
:
- 查找val中是否有hc的值,若有直接设置该值
- 查找object中对应的元表
lua_getmetatable
若为meta - 找到
__newindex
的key值,若不存在则返回空值 - 调用
__newindex
函数,此时调用该数第一个参数为val
,第二个参数为hc
,第三个参数为字符串"hclua"
- 若此时判断第二个参数不是字段,则直接返回lua错误内容
- 此时我们会在第二个参数的key值后面添加
__set
即为hc__set
,我们查找meta["hc__set"] 若为空则返回失败,若为函数则转到7 - 我们将调用该函数,并将第一个参数
val
,第三个参数hclua
,并进行函数调用
lua_pushvalue(lua, 1);
lua_pushvalue(lua, 3);
lua_call(lua, 2, 1);
此时字段的设置已经完成了。
小结
Lua的处理速度较慢,为了高性能,通常有许多函数会放到Rust层或者底层进行处理,此时有一个快速的映射就可以方便代码的快速使用复用,而通过derive宏,我们可以快速的构建出想要的功能。
在Lua中实现Rust对象的绑定的更多相关文章
- Lua中的userdata
[话从这里说起] 在我发表<Lua中的类型与值>这篇文章时,就有读者给我留言了,说:你应该好好总结一下Lua中的function和userdata类型.现在是时候总结了.对于functio ...
- Win32下 Qt与Lua交互使用(三):在Lua脚本中connect Qt 对象
话接上文.笔者为了方便使用Lua,自己编写了一个Lua的类.主要代码如下: QLua.h #ifndef QLUA_H #define QLUA_H // own #include "inc ...
- 在lua中创建字段安全的对象
lua萌新,刚刚学习和使用不到一个月.有不对的地方,还望各路大神不吝赐教. lua中可以用table来模拟对象,但table是可以任意增加键值的.在对象模拟中,暂且也叫它为字段(field)吧.如果在 ...
- 开源基于lua gc管理c++对象的cocos2dx lua绑定方案
cocos2dx目前lua对应的c++对象的生命周期管理,是基于c++析构函数的,也就是生命周期可能存在不一致,比如c++对象已经释放,而lua对象还存在,如果这时候再使用,会有宕机的风险,为此我开发 ...
- [译] Closures in Lua - Lua中的闭包
原文:(PDF) . 摘要 一等(first-class)函数是一种非常强大的语言结构,并且是函数式语言的基础特性.少数过程式语言由于其基于栈的实现,也支持一等函数.本文讨论了Lua 5.x用于实现一 ...
- [转][译] Closures in Lua - Lua中的闭包
http://www.cnblogs.com/plodsoft/p/5900270.html?utm_source=tuicool&utm_medium=referral 原文:(PDF) . ...
- Lua中闭包详解 来自RingOfTheC[ring.of.the.c@gmail.com]
这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[ring.of.the.c@gmail.c ...
- cocos2d-x lua 中使用protobuf并对http进行处理
cocos2d-x lua 中使用protobuf并对http进行处理 本文介绍 cocos2d-x lua 中使用http 和 基于cocos2d-x 对lua http的封装(部分ok) 本博客链 ...
- lua中基类和“继承机制”
基类:基类定义了所有对于派生类来说普通的属性和方法,派生类从基类继承所需的属性和方法,且在派生类中增加新的属性和方法. 继承:继承是C++语言的一种重要机制,它允许在已定义的类的基础上产生新类. lu ...
- 关于javascript闭包中的this对象
我们知道, this对象是运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象.<Javascript高级程序设计&g ...
随机推荐
- NVIDIA中的cupti的作用及设置: CUDA profiling tools interface —— Could not load dynamic library 'libcupti.so.10.1' —— failed with error CUPTI_ERROR_INSUFFICIENT_PRIVILEGES
NVIDIA官方给出的说明: 可以知道,这个组件的作用是对NVIDIA的CUDA进程进行性能分析的,通过对这个组件的调用可以实现对CUDA进程的性能监测. 在使用深度学习框架时有时需要对运行的代码的C ...
- 支持NVIDIA GPU —— 如何运行docker环境下的Jax环境
项目地址: https://github.com/NVIDIA/JAX-Toolbox 具体的安装命令: 地址: https://github.com/NVIDIA/JAX-Toolbox/pkgs/ ...
- 如何将 Vim 剪贴板里面的东西粘贴到 Vim 之外的地方? (Ubuntu18.04系统亲测)
主要参考内容: https://www.zhihu.com/question/19863631 在vim中剪贴中的内容是难以在vim之外使用的,那么怎么修改这个问题呢? =============== ...
- 【转载】 vim中常用折叠命令
原文地址: https://www.cnblogs.com/litifeng/p/11675547.html 个人推荐的一个视频教程地址: 上古神器Vim:从恶言相向到爱不释手 - 终极Vim教程01 ...
- jQuery Eazyui的学习和使用(一)
工作需要,需要学习使用据说非常简单好用的前端框架-----Eazyui 先看看简介吧:"jQuery EasyUI 是一个基于 jQuery 的框架,集成了各种用户界面插件.jQuery E ...
- 【入门教程】5分钟教你快速学会集成Java springboot ~
介绍 Apache DolphinScheduler是一个分布式易扩展的开源分布式调度系统,支持海量数据处理,具有任务流程调度.任务流程编排.任务监控告警.工作流引擎等功能. 本文将介绍如何将Apac ...
- 系统IO常用函数接口
本文整理归纳了几种常用的系统IO的函数借口,以供读者查阅使用 目录 系统IO与标准IO的区别 打开文件:open 关闭文件:close 文件读取:read 文件写入:write 位置偏移:lseek ...
- 《最新出炉》系列小成篇-Python+Playwright自动化测试-67 - 模拟手机浏览器兼容性测试
1.简介 在日常工作中,我们会遇到需要使用不同的硬件设备测试兼容性的问题,尤其是现在手机型号基本上是每个厂家每年发布一款新机型,而且手机的屏幕大小分辨率五花八门的,我们基本不可能全部机型都用真机测试一 ...
- [题解] [ABC221H] Count Multiset - DP
[ABC221H] Count Multiset 题面翻译 输入两个正整数 \(N,M\),并存在一个集合,问你一个长度为 \(k\) 的合法集合存在多少个?你需要回答 \(k\) 的值为 \(1\) ...
- 禁止 SSH 传递 locale 环境变量
SSH 在连接远程机器时默认会传递一些环境变量,其中就包括你本机的 locale 变量.这会导致远程机器的 locale 配置变成和你本地主机一样.有时候我们不希望这种行为,我们可以通过修改 SSH ...