shura_proc/
lib.rs

1use lazy_static::lazy_static;
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, TokenStream as TokenStream2};
4use quote::{quote, ToTokens};
5use std::sync::Mutex;
6use std::{collections::HashSet, str::FromStr};
7use syn::{parse_macro_input, parse_quote, Data, DataStruct, DeriveInput, Fields, Type, TypePath};
8
9fn field_names(data_struct: &DataStruct) -> Vec<String> {
10    let mut result = vec![];
11    match data_struct.fields {
12        Fields::Named(ref fields_named) => {
13            for field in fields_named.named.iter() {
14                let field_name = field
15                    .ident
16                    .as_ref()
17                    .and_then(|f| Some(format!("\"{0}\"", f.to_string())))
18                    .unwrap();
19                result.push(field_name);
20            }
21        }
22        _ => (),
23    }
24    return result;
25}
26
27fn position_field(data_struct: &DataStruct, attr_name: &str) -> Option<(Ident, TypePath)> {
28    match &data_struct.fields {
29        Fields::Named(fields_named) => {
30            for field in fields_named.named.iter() {
31                for attr in &field.attrs {
32                    let name = attr.path();
33                    if name.to_token_stream().to_string() == attr_name {
34                        match &field.ty {
35                            Type::Path(type_name) => {
36                                let field_name = field.ident.as_ref().unwrap();
37                                return Some((field_name.clone(), type_name.clone()));
38                            }
39                            _ => panic!("Cannot extract the type of the component."),
40                        };
41                    }
42                }
43            }
44        }
45        _ => (),
46    }
47    None
48}
49
50fn name_field(ast: &DeriveInput, attr_name: &str) -> Option<syn::Expr> {
51    for attr in &ast.attrs {
52        let name = match &attr.meta {
53            syn::Meta::NameValue(p) => p,
54            _ => continue,
55        };
56
57        let name_str = attr.path().to_token_stream().to_string();
58        if name_str == attr_name {
59            return Some(name.value.clone());
60        }
61    }
62    return None;
63}
64
65lazy_static! {
66    static ref USED_COMPONENT_HASHES: Mutex<HashSet<u32>> = Mutex::new(HashSet::new());
67    static ref USED_STATE_HASHES: Mutex<HashSet<u32>> = Mutex::new(HashSet::new());
68}
69
70#[proc_macro_derive(Component, attributes(base, name, collider, rigid_body))]
71/// All components need to derive from a BaseComponent. This macro is used to make this more
72/// easily
73/// 
74/// 
75/// # Example:
76///
77/// ```
78/// #[derive(Component)]
79/// struct Bunny {
80///     #[base] base: BaseComponent,
81///     linvel: Vector<f32>,    
82/// }
83/// ```
84pub fn derive_component(input: TokenStream) -> TokenStream {
85    let ast = parse_macro_input!(input as DeriveInput);
86    let data_struct = match ast.data {
87        Data::Struct(ref data_struct) => data_struct,
88        _ => panic!("Must be a struct!"),
89    };
90
91    let struct_name = ast.ident.clone();
92    let struct_name_str = struct_name.to_string();
93    let struct_identifier = name_field(&ast, "name").unwrap_or(parse_quote!(#struct_name_str));
94    let struct_identifier_str = struct_identifier.to_token_stream().to_string();
95    let fields =
96        TokenStream2::from_str(&format!("&[{}]", field_names(data_struct).join(", "))).unwrap();
97
98    let (field_name, _) = position_field(data_struct, "base")
99        .expect("The helper attribute #[component] has not been found!");
100
101    let mut hashes = USED_COMPONENT_HASHES.lock().unwrap();
102    let hash = const_fnv1a_hash::fnv1a_hash_str_32(&struct_identifier_str);
103    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
104    if hashes.contains(&hash) {
105        panic!("A component with the identifier '{struct_identifier_str}' already exists!");
106    }
107    hashes.insert(hash);
108
109    quote!(
110        impl #impl_generics shura::FieldNames for #struct_name #ty_generics #where_clause {
111            const FIELDS: &'static [&'static str] = #fields;
112        }
113
114        impl #impl_generics shura::ComponentIdentifier for #struct_name #ty_generics #where_clause {
115            const TYPE_NAME: &'static str = #struct_identifier;
116            const IDENTIFIER: shura::ComponentTypeId = shura::ComponentTypeId::new(#hash);
117        }
118
119        impl #impl_generics shura::ComponentDerive for #struct_name #ty_generics #where_clause {
120            fn base(&self) -> &dyn shura::BaseComponent {
121                &self.#field_name
122            }
123
124            fn component_type_id(&self) -> shura::ComponentTypeId {
125                shura::ComponentTypeId::new(#hash)
126            }
127        }
128    )
129    .into()
130}
131
132#[proc_macro_derive(State, attributes(name, priority))]
133/// All scene- and globalstates must derive from this macro.
134/// 
135/// # Example:
136/// 
137/// ```
138/// #[derive(State)]
139/// struct MySceneStates {
140///     shared_model: Model
141/// }
142/// ```
143pub fn derive_state(input: TokenStream) -> TokenStream {
144    let ast = parse_macro_input!(input as DeriveInput);
145    let data_struct = &match ast.data {
146        Data::Struct(ref data_struct) => data_struct,
147        _ => panic!("Must be a struct!"),
148    };
149
150    let struct_name = ast.ident.clone();
151    let struct_name_str = struct_name.to_string();
152    let struct_identifier = name_field(&ast, "name").unwrap_or(parse_quote!(#struct_name_str));
153    let struct_identifier_str = struct_identifier.to_token_stream().to_string();
154    let mut hashes = USED_STATE_HASHES.lock().unwrap();
155    let hash = const_fnv1a_hash::fnv1a_hash_str_32(&struct_identifier_str);
156    if hashes.contains(&hash) {
157        panic!("A state with the identifier '{struct_identifier_str}' already exists!");
158    }
159    hashes.insert(hash);
160
161    let struct_name = ast.ident;
162    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
163    let fields =
164        TokenStream2::from_str(&format!("&[{}]", field_names(data_struct).join(", "))).unwrap();
165
166    quote!(
167        impl #impl_generics StateIdentifier for #struct_name #ty_generics #where_clause {
168            const TYPE_NAME: &'static str = #struct_identifier;
169            const IDENTIFIER: shura::StateTypeId = shura::StateTypeId::new(#hash);
170        }
171
172        impl #impl_generics shura::FieldNames for #struct_name #ty_generics #where_clause {
173            const FIELDS: &'static [&'static str] = #fields;
174        }
175
176        impl #impl_generics shura::StateDerive for #struct_name #ty_generics #where_clause {
177            
178        }
179    )
180    .into()
181}
182
183#[proc_macro_attribute]
184/// This macro helps setup a cross plattform main method 
185pub fn main(_args: TokenStream, item: TokenStream) -> TokenStream {
186    let item: TokenStream2 = item.into();
187    quote!(
188        #item
189
190        #[cfg(target_os = "android")]
191        #[no_mangle]
192        fn android_main(app: AndroidApp) {
193            shura_main(shura::ShuraConfig::default(app));
194        }
195
196        #[cfg(not(target_os = "android"))]
197        #[allow(dead_code)]
198        fn main() {
199            shura_main(shura::ShuraConfig::default());
200        }
201    )
202    .into()
203}