From bcc695192737c37d5dd33f175d512f427859037e Mon Sep 17 00:00:00 2001 From: Archina Void Date: Fri, 20 Dec 2019 21:13:50 +0100 Subject: [PATCH] squashed impl properties macro add default value add builder_helper through block fix formating drop unnecessary clone Revert "formating on bindings_generator" This reverts commit 8de9b7183a231b309cb249d823a0b90405b5d4d6. rename builder to register_with, use bareFnArgs to extract function, call function use AttributeArgs instead of BareFnArgs improve checks against attribute ident paths rename builder to register_callback, use path instead of ident simplify register_callback assignment map fields in hashMap with map of potential literals forward ident to hashMap, evaluate optional default rename symbol use propertyConfig struct, use filter_map fix formating use suggested changes reapply fmt remove missed comment remove now obsolete function --- examples/spinning_cube/src/lib.rs | 79 +++++++------------ gdnative-derive/src/derive_macro.rs | 62 +++++++++++++-- .../src/derive_macro/property_args.rs | 39 +++++++++ gdnative-derive/src/lib.rs | 28 ++++++- 4 files changed, 149 insertions(+), 59 deletions(-) create mode 100644 gdnative-derive/src/derive_macro/property_args.rs diff --git a/examples/spinning_cube/src/lib.rs b/examples/spinning_cube/src/lib.rs index f39ef90fe..5d8436e3a 100644 --- a/examples/spinning_cube/src/lib.rs +++ b/examples/spinning_cube/src/lib.rs @@ -1,68 +1,43 @@ #[macro_use] extern crate gdnative as godot; -use godot::init::{Property, PropertyHint, PropertyUsage}; -use godot::GodotString; - +#[derive(godot::NativeClass)] +#[inherit(godot::MeshInstance)] +#[user_data(godot::user_data::MutexData)] +#[register_with(my_register_function)] struct RustTest { start: godot::Vector3, time: f32, + #[property(default = 0.05)] rotate_speed: f64, } -impl godot::NativeClass for RustTest { - type Base = godot::MeshInstance; - type UserData = godot::user_data::MutexData; - - fn class_name() -> &'static str { - "RustTest" - } - - fn init(_owner: Self::Base) -> Self { - Self::_init() - } - - fn register_properties(builder: &godot::init::ClassBuilder) { - builder.add_property(Property { - name: "base/rotate_speed", - default: 0.05, - hint: PropertyHint::Range { - range: 0.05..1.0, - step: 0.01, - slider: true, - }, - getter: |this: &RustTest| this.rotate_speed, - setter: |this: &mut RustTest, v| this.rotate_speed = v, - usage: PropertyUsage::DEFAULT, - }); - - builder.add_property(Property { - name: "test/test_enum", - default: GodotString::from_str("Hello"), - hint: PropertyHint::Enum { - values: &["Hello", "World", "Testing"], - }, - getter: |_: &RustTest| GodotString::from_str("Hello"), - setter: (), - usage: PropertyUsage::DEFAULT, - }); - - builder.add_property(Property { - name: "test/test_flags", - default: 0, - hint: PropertyHint::Flags { - values: &["A", "B", "C", "D"], - }, - getter: |_: &RustTest| 0, - setter: (), - usage: PropertyUsage::DEFAULT, - }); - } +fn my_register_function(builder: &godot::init::ClassBuilder) { + builder.add_property(godot::init::Property { + name: "test/test_enum", + default: godot::GodotString::from_str("Hello"), + hint: godot::init::PropertyHint::Enum { + values: &["Hello", "World", "Testing"], + }, + getter: |_: &RustTest| godot::GodotString::from_str("Hello"), + setter: (), + usage: godot::init::PropertyUsage::DEFAULT, + }); + builder.add_property(godot::init::Property { + name: "test/test_flags", + default: 0, + hint: godot::init::PropertyHint::Flags { + values: &["A", "B", "C", "D"], + }, + getter: |_: &RustTest| 0, + setter: (), + usage: godot::init::PropertyUsage::DEFAULT, + }); } #[godot::methods] impl RustTest { - fn _init() -> Self { + fn _init(_owner: godot::MeshInstance) -> Self { RustTest { start: godot::Vector3::new(0.0, 0.0, 0.0), time: 0.0, diff --git a/gdnative-derive/src/derive_macro.rs b/gdnative-derive/src/derive_macro.rs index 349f389fd..94004e806 100644 --- a/gdnative-derive/src/derive_macro.rs +++ b/gdnative-derive/src/derive_macro.rs @@ -1,10 +1,16 @@ use proc_macro::TokenStream; -use syn::{Data, DeriveInput, Fields, Ident, Type}; +use std::collections::HashMap; +use syn::{Data, DeriveInput, Fields, Ident, Meta, MetaList, NestedMeta, Path, Type}; + +mod property_args; +use property_args::{PropertyAttrArgs, PropertyAttrArgsBuilder}; pub(crate) struct DeriveData { pub(crate) name: Ident, pub(crate) base: Type, + pub(crate) register_callback: Option, pub(crate) user_data: Type, + pub(crate) properties: HashMap, } pub(crate) fn parse_derive_input(input: TokenStream) -> DeriveData { @@ -20,17 +26,26 @@ pub(crate) fn parse_derive_input(input: TokenStream) -> DeriveData { let inherit_attr = input .attrs .iter() - .find(|a| a.path.segments[0].ident == "inherit") + .find(|a| a.path.is_ident("inherit")) .expect("No \"inherit\" attribute found"); // read base class let base = syn::parse::(inherit_attr.tokens.clone().into()) .expect("`inherits` attribute requires the base type as an argument."); + let register_callback = input + .attrs + .iter() + .find(|a| a.path.is_ident("register_with")) + .map(|attr| { + attr.parse_args::() + .expect("`register_with` attributes requires a function as an argument.") + }); + let user_data = input .attrs .iter() - .find(|a| a.path.segments[0].ident == "user_data") + .find(|a| a.path.is_ident("user_data")) .map(|attr| { syn::parse::(attr.tokens.clone().into()) .expect("`userdata` attribute requires a type as an argument.") @@ -48,13 +63,48 @@ pub(crate) fn parse_derive_input(input: TokenStream) -> DeriveData { }; // read exported properties - if let Fields::Named(_names) = struct_data.fields { - // TODO - } + let properties = if let Fields::Named(names) = &struct_data.fields { + names + .named + .iter() + .filter_map(|field| { + let mut property_args = None; + + for attr in field.attrs.iter() { + if !attr.path.is_ident("property") { + continue; + } + + let meta = attr + .parse_meta() + .expect("should be able to parse attribute arguments"); + if let Meta::List(MetaList { nested, .. }) = meta { + property_args + .get_or_insert_with(PropertyAttrArgsBuilder::default) + .extend(nested.iter().map(|arg| match arg { + NestedMeta::Meta(Meta::NameValue(ref pair)) => pair, + _ => panic!("unexpected argument: {:?}", arg), + })); + } else { + panic!("unexpected meta variant: {:?}", meta); + } + } + + property_args.map(|builder| { + let ident = field.ident.clone().expect("fields should be named"); + (ident, builder.done()) + }) + }) + .collect::>() + } else { + HashMap::new() + }; DeriveData { name: ident, base, + register_callback, user_data, + properties, } } diff --git a/gdnative-derive/src/derive_macro/property_args.rs b/gdnative-derive/src/derive_macro/property_args.rs new file mode 100644 index 000000000..263bf2075 --- /dev/null +++ b/gdnative-derive/src/derive_macro/property_args.rs @@ -0,0 +1,39 @@ +pub struct PropertyAttrArgs { + pub default: syn::Lit, +} + +#[derive(Default)] +pub struct PropertyAttrArgsBuilder { + default: Option, +} + +impl<'a> Extend<&'a syn::MetaNameValue> for PropertyAttrArgsBuilder { + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + for pair in iter.into_iter() { + let name = pair + .path + .get_ident() + .expect("should be single identifier") + .to_string(); + match name.as_str() { + "default" => { + if let Some(old) = self.default.replace(pair.lit.clone()) { + panic!("there is already a default value set: {:?}", old); + } + } + _ => panic!("unexpected argument: {}", &name), + } + } + } +} + +impl PropertyAttrArgsBuilder { + pub fn done(self) -> PropertyAttrArgs { + PropertyAttrArgs { + default: self.default.expect("`default` value is required"), + } + } +} diff --git a/gdnative-derive/src/lib.rs b/gdnative-derive/src/lib.rs index 0a9b0d98b..84a348131 100644 --- a/gdnative-derive/src/lib.rs +++ b/gdnative-derive/src/lib.rs @@ -57,7 +57,10 @@ pub fn methods(meta: TokenStream, input: TokenStream) -> TokenStream { TokenStream::from(output) } -#[proc_macro_derive(NativeClass, attributes(inherit, export, user_data))] +#[proc_macro_derive( + NativeClass, + attributes(inherit, export, user_data, property, register_with) +)] pub fn derive_native_class(input: TokenStream) -> TokenStream { let data = derive_macro::parse_derive_input(input.clone()); @@ -66,6 +69,24 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { let name = data.name; let base = data.base; let user_data = data.user_data; + let register_callback = data + .register_callback + .map(|function_path| quote!(#function_path(builder);)) + .unwrap_or(quote!({})); + let properties = data.properties.iter().map(|(ident, config)| { + let default_value = &config.default; + let label = format!("base/{}", ident); + quote!({ + builder.add_property(gdnative::init::Property{ + name: #label, + getter: |this: &#name| this.#ident, + setter: |this: &mut #name, v| this.#ident = v, + default: #default_value, + usage: gdnative::init::PropertyUsage::DEFAULT, + hint: gdnative::init::PropertyHint::None + }); + }) + }); // string variant needed for the `class_name` function. let name_str = quote!(#name).to_string(); @@ -82,6 +103,11 @@ pub fn derive_native_class(input: TokenStream) -> TokenStream { fn init(owner: Self::Base) -> Self { Self::_init(owner) } + + fn register_properties(builder: &gdnative::init::ClassBuilder) { + #(#properties)*; + #register_callback + } } ) };