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 HcTestMacro
As an example:
-
type constructionIn the lua call
local val = ()
constructible -
type disjunctionIn the lua call
(val)
Dialyzable, onlylight use**rdata
-
Mapping of fields, assuming that there are fields
hc
We need to be able to quickly assign values to fields.
-
retrieve a value:
or
val:get_hc()
All values can be taken -
assign a value to something:
= "hclua"
orval:set_hc("hclua")
All values can be taken
-
type method, registered class methods, such as extra methods
call1
Then 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
})).
- 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 theHcTest { new = function(), del = function() }
Register for life via attrib:#[hclua_cfg(light)]
, indicating that the type islight userdata
That is, the lifecycle is controlled by Rust and defaults to theuserdata
That is, the lifecycle is controlled by Lua through the__gc
Recycling 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.
- 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);
- 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();
- 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.
- 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:
- Find out if there is a value of hc in val, if so return it directly
- Find the corresponding metatable in object
lua_getmetatable
If meta - locate
__index
or null if it doesn't exist. - call (programming)
__index
function, when the call to the number of the first parameter for theval
The second parameter ishc
- There are two possibilities at this point, either accessing a function to jump to 6, or accessing a variable to jump to 7.
- 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 follows
val:hc()
assume (office)(val)
Realize the call and end the process - 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 the
lua_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"
:
- Find out if there is a value for hc in val, and if so, set it directly.
- Find the corresponding metatable in object
lua_getmetatable
If meta - locate
__newindex
or null if it doesn't exist. - call (programming)
__newindex
function, when the call to the number of the first parameter for theval
The second parameter ishc
The third parameter is a string"hclua"
- If you determine that the second parameter is not a field at this point, you will directly return the lua error content
- At this point we will add the key value of the second parameter followed by a
__set
be considered to behc__set
We look for meta["hc__set"] if it is null then it fails, if it is a function then go to 7 - We'll call the function with the first parameter
val
The third parameterhclua
and 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.