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))]
71pub 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))]
133pub 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]
184pub 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}