Location>code7788 >text

Implementing Rust Object Binding in Lua

Popularity:132 ℃/2024-10-21 09:45:32

realize a goal: The ability to quickly map Rust objects for use in lua makes it as easy as possible to use.

functional objective

in order tostruct HcTestMacroAs an example:

  1. type constructionIn the lua calllocal val = ()constructible
  2. type disjunctionIn the lua call(val)Dialyzable, onlylight use**rdata
  3. Mapping of fields, assuming that there are fieldshcWe need to be able to quickly assign values to fields.
  • retrieve a valueorval:get_hc()All values can be taken
  • assign a value to something = "hclua"orval:set_hc("hclua")All values can be taken
  1. type method, registered class methods, such as extra methodscall1Then we can register to the lua VM by registering to the lua VM, which is not good for direct registration via macros since the lua VM may not be globally unique
// Direct registration of function registration
HcTestMacro::object_def(&mut lua, "ok", hclua::function1(HcTestMacro::ok));
// Closures register single parameters
HcTestMacro::object_def(&mut lua, "call1", hclua::function1(|obj: &HcTestMacro| -> u32 {

})).
// Closures register double parameters
HcTestMacro::object_def(&mut lua, "call2", hclua::function2(|obj: &mut HcTestMacro, val: u32| -> u32 {
     + val
})).
  1. static methodSome static class methods, i.e., those that do not actualize an object for registration, can be equivalent to modules.
HcTestMacro::object_static_def(&mut lua, "sta_run", hclua::function0(|| -> String {
    "test".to_string()
}));

Full column code

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);
    // Direct Registration Function Registration
    HcTestMacro::object_def(&mut lua, "ok", hclua::function1(HcTestMacro::ok));
    // The closure registration form parameter
    HcTestMacro::object_def(&mut lua, "call1", hclua::function1(|obj: &HcTestMacro| -> u32 {
        
    }));
    // Closures register double parameters
    HcTestMacro::object_def(&mut lua, "call2", hclua::function2(|obj: &mut HcTestMacro, val: u32| -> u32 {
         + val
    }));
    HcTestMacro::object_static_def(&mut lua, "sta_run", hclua::function0(|| -> String {
        "test".to_string()
    }));
    ();
    
    let val = "
        print(aaa);
        print(\"cccxxxxxxxxxxxxxxx\");
        print(type(HcTest));
        local v = ();
        print(\"call ok\", v:ok())
        print(\"call1\", v:call1())
        print(\"call2\", v:call2(2))
        print(\"kkkk\", )
         = \"dddsss\";
        print(\"kkkk ok get_hc\", v:get_hc())
         = \"aa\";
        print(\"new kkkkk\", )
        v:set_hc(\"dddddd\");
        print(\"new kkkkk1\", )
        print(\"attemp\", v.hc1)
        print(\"vvvvv\", v:call1())
        print(\"static run\", HcTest.sta_run())
        (v);
    ";
    let _: Option<()> = lua.exec_string(val);
}

source code address

hclua Lua binding in Rust.

Functional Implementation Stripping

Function registration via the derive macro.#[derive(ObjectMacro, Default)]
Named via the attrib statement:#[hclua_cfg(name = HcTest)]The class is configured for use in the lua
The name in theHcTest, which essentially registers the global table in lua by registering the
HcTest { new = function(), del = function() }
Register for life via attrib:#[hclua_cfg(light)], indicating that the type islight userdataThat is, the lifecycle is controlled by Rust and defaults to theuserdataThat is, the lifecycle is controlled by Lua through the__gcRecycling is carried out.
Declare fields via attrib:#[hclua_field]Put it in front of the field, i.e., you can register the field for use, and determine whether the field is available at the time of derive generation for field mapping.

derive macro implementation

The main source code is in thehclua-macro implementation, the full code is available for reference.

  1. Declare and parse 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. Parsing Config, i.e., determining the class name and whether or not light
let config = config::Config::parse_from_attributes(ident.to_string(), &attrs[..]).unwrap();
  1. Parses the fields and generates the corresponding functions
let functions: Vec<_> = fields
    .iter()
    .map(|field| {
        let field_ident = ().unwrap();
        if ().any(|attr| ().is_ident("hclua_field")) {
            let get_name = format_ident!("get_{}", field_ident);
            let set_name = format_ident!("set_{}", field_ident);
            let ty = ();
            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<_> = ().map(|field| {
    let field_ident = ().unwrap();
    if ().any(|attr| ().is_ident("hclua_field")) {
        let ty = ();
        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();

By generating an array of TokenStreams, the source code is expanded in the final#(#functions)*That is, you can get the effect of our TokenStream splice.

  1. Generate the final code
let name = ;
let is_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((), &#name)
            } else {
                hclua::LuaObject::<#ident>::new((), &#name)
            };
            ();

            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)*
    }
    // ...
};
()

This way we achieve our quick implementation of the solution through macros.

Field mapping implementation

In the Lua object mapping, thetype(val)is an object variable, and any accesses on this basis will trigger meta table operationsmetatable

Field acquisition

We access any object such as

  1. Find out if there is a value of hc in val, if so return it directly
  2. Find the corresponding metatable in objectlua_getmetatableIf meta
  3. locate__indexor null if it doesn't exist.
  4. call (programming)__indexfunction, when the call to the number of the first parameter for thevalThe second parameter ishc
  5. There are two possibilities at this point, either accessing a function to jump to 6, or accessing a variable to jump to 7.
  6. will directly take out the meta["hc"] returned to the lua, if it is a value that is the value, for the function is returned to the lua's subsequent calls, the usual form of expression is as followsval:hc()assume (office)(val)Realize the call and end the process
  7. Since the variable is a dynamic value and we do not have it in the metatable, we need to make an additional call to take out the correct value, and we will manually continue the function we took out in the call to thelua_call(lua, 1, 1);That is, you can implement the field return

Note: In the variable whether the value is a field processing will have a relative difference, and the need to efficiently carry out the validation, here the use of global static variables to store whether the type of field value.

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

Full Source:

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
    }
}

At this point the field fetching is complete.

Field settings

At this point we need to set the object = "hclua"

  1. Find out if there is a value for hc in val, and if so, set it directly.
  2. Find the corresponding metatable in objectlua_getmetatableIf meta
  3. locate__newindexor null if it doesn't exist.
  4. call (programming)__newindexfunction, when the call to the number of the first parameter for thevalThe second parameter ishcThe third parameter is a string"hclua"
  5. If you determine that the second parameter is not a field at this point, you will directly return the lua error content
  6. At this point we will add the key value of the second parameter followed by a__setbe considered to behc__setWe look for meta["hc__set"] if it is null then it fails, if it is a function then go to 7
  7. We'll call the function with the first parametervalThe third parameterhcluaand make function calls
lua_pushvalue(lua, 1);
lua_pushvalue(lua, 3);
lua_call(lua, 2, 1);

At this point the fields are set up.

wrap-up

Lua processing speed is slow, in order to high performance, there are usually many functions will be put into the Rust layer or the bottom of the processing, at this time there is a fast mapping can be convenient for the rapid use of the code reuse, and through the derive macro, we can quickly build the desired function.