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

在Lua中实现Rust对象的绑定

时间:2024-10-21 09:44:34浏览次数:8  
标签:hclua val field lua 绑定 Lua let hc Rust

实现目标:能将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)
        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<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宏,我们可以快速的构建出想要的功能。

标签:hclua,val,field,lua,绑定,Lua,let,hc,Rust
From: https://www.cnblogs.com/wmproxy/p/18488371

相关文章

  • uv 基于rust 编写的python 包管理以及项目管理工具
    uv基于rust编写的python包管理以及项目管理工具包含的特性简单工具可以替换pip,pip-tools,pipx,poetry,pyenv等比pip快10-100倍安装以及管理python版本运行以及安装python应用运行脚本支持类似cargo模式的workspace磁盘空间高效说明对于希望提示快速python包下......
  • POJ 3737 UmBasketella & printf占位符%lf和%f & cin/cout取消绑定加速 & cin/cout保
    POJ3737UmBasketella推导之后发现,体积先增后减,所以我们三分。#include<stdio.h>#include<algorithm>#include<cmath>#include<iostream>#include<string>#defineFor(i,j,n)for(inti=j;i<=n;++i)constdoublepi=acos(-1.0);......
  • lua插件之----【luaString 字符串类】
    API列表 接口原型说明luaString.left(str,num)获取字符串左侧指定数量的字符luaString.right(str,num)获取字符串右侧指定数量的字符luaString.mid(str,pos,num)获取字符串指定起始位置后的几个字符luaString.lTrim(str,filterStr)去掉字符串左侧指定......
  • 【rCore OS 开源操作系统】Rust 智能指针
    前置知识点何为“智能”在Rust中,“智能指针”是指那些实现了特定智能行为的指针类型。这些智能行为通常包括内存管理、生命周期跟踪以及所有权转移等。常见智能指针BoxBox<T>是Rust中最简单的智能指针类型之一,它用于堆分配的内存。Box<T>允许你在堆上分配类型T......
  • rust操作mysql增删改查
    toml[dependencies]mysql="25.0.0"[[bin]]name="mysql"path="src/mysql.rs"mysql.rsusemysql::*;usemysql::prelude::*;fnmain(){leturl="mysql://root:root@localhost:3306/fiber";letpool=Po......
  • 毫末智行IPO之路荆棘密布,深度绑定长城汽车或成绊脚石
    在自动驾驶的浪潮中,毫末智行曾被视为一颗冉冉升起的新星。然而,随着其IPO计划的波折和与长城汽车关系的复杂性逐渐浮出水面,这家企业的未来开始蒙上一层阴影。近日,关于长城汽车董事长魏建军暂时叫停毫末智行港股IPO的消息虽被毫末智行董事长张凯否认,但这一事件无疑加剧了市场对......
  • Rust学习笔记
    首先下载RUST的安装程序:https://www.rust-lang.org/tools/installWindows系统直接下载rustup-init.exe进行安装。这个只是一个安装器,安装的过程中还需要再下载安装文件。下载的速度可能会有点慢。可以尝试设置下面两个系统环境变量(设置在当前用户里)RUSTUP_DIST_SERVER="https:......
  • DevExpress WinForms中文教程:Data Grid - 如何为网格绑定ADO. NET数据
    在本教程中,您将学习如何做到以下几点:在一个WinForms项目中创建并配置ADO.NET数据源将DevExpressWinForms数据网格绑定到数据源。将更改发布到数据库。P.S:DevExpressWinForms拥有180+组件和UI库,能为WindowsForms平台创建具有影响力的业务解决方案。DevExpressWinForms能......
  • Rust宏之derive的设计及实战
    Rust宏可以极大的简化编写的难度,学习好宏可以更好的减少冗余代码。宏的基本概念Rust中的宏可以分为两大类:声明宏(DeclarativeMacros)和过程宏(ProceduralMacros)。声明宏:也称为macro_rules!宏,使用macro_rules!关键字定义。它是一种基于模式匹配的文本替换宏,类似于C语言中的宏定......
  • nginx+lua实现文件上传功能
    需要用到nginx实现文件上传,刚好手里面的版本支持lua,下面是完整实现:首先是nginx的配置如下:注意$home_path设置的是上传文件的保存目录location/uploadFile{set$home_path"/root/up2";content_by_lua_fileconf/upfile.lua;}接着在web根目录放入Html文件,命......