solstice_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote};
5use syn::{self, parse_macro_input, Data, DeriveInput, Field, Fields, Meta, NestedMeta};
6
7fn has_attr(field: &Field, i: &str) -> bool {
8    field.attrs.iter().any(|attr| {
9        if let Some(attr_ident) = attr.path.get_ident() {
10            attr_ident == i
11        } else {
12            false
13        }
14    })
15}
16
17#[proc_macro_derive(Shader, attributes(uniform))]
18pub fn derive_shader(item: TokenStream) -> TokenStream {
19    let input = parse_macro_input!(item as DeriveInput);
20
21    let ident = input.ident;
22
23    let fields = match input.data {
24        Data::Struct(s) => match s.fields {
25            Fields::Named(fields) => fields
26                .named
27                .iter()
28                .filter(|field| has_attr(field, "uniform"))
29                .map(|field| {
30                    let field_ident = field.ident.as_ref().unwrap();
31                    let field_ty = &field.ty;
32
33                    quote! {
34                        impl ::solstice::shader::UniformGetter<#field_ty> for #ident {
35                            fn get_uniform(&self) -> &#field_ty {
36                                &self.#field_ident
37                            }
38                        }
39
40                        impl ::solstice::shader::UniformGetterMut<#field_ty> for #ident {
41                            fn get_uniform_mut(&mut self) -> &mut #field_ty {
42                                &mut self.#field_ident
43                            }
44                        }
45                    }
46                })
47                .collect::<Vec<_>>(),
48            _ => panic!("only named fields are supported"),
49        },
50        _ => panic!("only structs are supported"),
51    };
52
53    TokenStream::from(quote! {
54        #(#fields)*
55        impl ::solstice::shader::BasicUniformSetter for #ident {}
56    })
57}
58
59#[proc_macro_derive(Uniform, attributes(location))]
60pub fn derive_uniform(item: TokenStream) -> TokenStream {
61    let input = parse_macro_input!(item as DeriveInput);
62
63    let ident = input.ident;
64
65    let fields = match input.data {
66        Data::Struct(s) => match s.fields {
67            Fields::Named(fields) => fields
68                .named
69                .iter()
70                .filter(|field| has_attr(field, "location"))
71                .map(|field| {
72                    let field_ident = field.ident.as_ref().unwrap();
73                    quote! {
74                        impl ::solstice::shader::UniformTrait for #ident {
75                            type Value = [f32; 16];
76                            const NAME: &'static str = "#field_ident";
77
78                            fn get_location(&self) -> Option<&::solstice::shader::UniformLocation> {
79                                self.#field_ident.as_ref()
80                            }
81                        }
82                    }
83                })
84                .collect::<Vec<_>>(),
85            _ => panic!("only named fields are supported"),
86        },
87        _ => panic!("only structs are supported"),
88    };
89
90    TokenStream::from(quote! {
91        #(#fields)*
92    })
93}
94
95#[proc_macro_derive(Vertex)]
96pub fn derive_vertex(item: TokenStream) -> TokenStream {
97    let input = parse_macro_input!(item as DeriveInput);
98
99    assert!(
100        input.attrs.iter().any(|attr| {
101            if let Some(ident) = attr.path.get_ident() {
102                if ident == "repr" {
103                    if let Ok(syn::Meta::List(ref meta_list)) = attr.parse_meta() {
104                        let reprs = meta_list
105                            .nested
106                            .iter()
107                            .filter_map(|nested| match nested {
108                                NestedMeta::Meta(Meta::Path(path)) => path.get_ident(),
109                                _ => None,
110                            })
111                            .collect::<Vec<_>>();
112                        return ["C"]
113                            .iter()
114                            .all(|repr| reprs.iter().find(|&r| r == repr).is_some());
115                    }
116                }
117            }
118            false
119        }),
120        "Vertex structs must be `#[repr(C)]`"
121    );
122
123    match input.data {
124        Data::Struct(s) => match s.fields {
125            Fields::Named(fields) => {
126                let field_types = fields.named.iter().scan(vec![], |state, field| {
127                    state.push(&field.ty);
128                    Some(state.clone())
129                });
130
131                let vertex_formats = field_types.zip(fields.named.iter()).map(|(mut types, ident)| {
132                        let this_type = types.pop().unwrap();
133                        let name = format!("{:}", ident.ident.as_ref().unwrap());
134                        let offset = if types.is_empty() {
135                            quote! {
136                                0usize
137                            }
138                        } else {
139                            quote! {
140                                #(::std::mem::size_of::<#types>())+*
141                            }
142                        };
143
144                        quote! {
145                            ::solstice::vertex::VertexFormat {
146                                name: #name,
147                                offset: #offset,
148                                atype: <#this_type as ::solstice::vertex::VertexAttributeType>::A_TYPE,
149                                normalize: false,
150                            }
151                        }
152                    });
153                let ident = format_ident!("{}", input.ident);
154                TokenStream::from(quote! {
155                    impl ::solstice::vertex::Vertex for #ident {
156                        fn build_bindings() -> &'static [::solstice::vertex::VertexFormat] {
157                            &[
158                                #(#vertex_formats),*
159                            ]
160                        }
161                    }
162                })
163            }
164            Fields::Unnamed(_) | Fields::Unit => panic!("only named fields are supported"),
165        },
166        Data::Enum(_) | Data::Union(_) => panic!("only structs are supported"),
167    }
168}