Skip to content

Commit

Permalink
Merge pull request godot-rust#256 from Archina/implementPropertyMacro
Browse files Browse the repository at this point in the history
implement property macro
  • Loading branch information
toasteater committed Dec 22, 2019
2 parents 3f8371c + bcc6951 commit ba88f6f
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 59 deletions.
79 changes: 27 additions & 52 deletions examples/spinning_cube/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<RustTest>)]
#[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<RustTest>;

fn class_name() -> &'static str {
"RustTest"
}

fn init(_owner: Self::Base) -> Self {
Self::_init()
}

fn register_properties(builder: &godot::init::ClassBuilder<Self>) {
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<RustTest>) {
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,
Expand Down
62 changes: 56 additions & 6 deletions gdnative-derive/src/derive_macro.rs
Original file line number Diff line number Diff line change
@@ -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<Path>,
pub(crate) user_data: Type,
pub(crate) properties: HashMap<Ident, PropertyAttrArgs>,
}

pub(crate) fn parse_derive_input(input: TokenStream) -> DeriveData {
Expand All @@ -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::<Type>(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::<Path>()
.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::<Type>(attr.tokens.clone().into())
.expect("`userdata` attribute requires a type as an argument.")
Expand All @@ -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::<HashMap<_, _>>()
} else {
HashMap::new()
};

DeriveData {
name: ident,
base,
register_callback,
user_data,
properties,
}
}
39 changes: 39 additions & 0 deletions gdnative-derive/src/derive_macro/property_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
pub struct PropertyAttrArgs {
pub default: syn::Lit,
}

#[derive(Default)]
pub struct PropertyAttrArgsBuilder {
default: Option<syn::Lit>,
}

impl<'a> Extend<&'a syn::MetaNameValue> for PropertyAttrArgsBuilder {
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = &'a syn::MetaNameValue>,
{
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"),
}
}
}
28 changes: 27 additions & 1 deletion gdnative-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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();
Expand All @@ -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<Self>) {
#(#properties)*;
#register_callback
}
}
)
};
Expand Down

0 comments on commit ba88f6f

Please sign in to comment.