1use inflector::Inflector;
2use proc_macro2::{Delimiter, Span, TokenStream, TokenTree};
3use quote::quote;
4use syn::{parse_macro_input, Attribute, Data, DeriveInput, Fields, Ident, Type};
5
6mod create_type;
7mod load_asset;
8
9#[derive(Copy, Clone)]
10enum TheTruthType {
11 Float,
12 Double,
13 U32,
14 U64,
15 Bool,
16}
17
18impl TheTruthType {
19 fn new(ty: &Type) -> Self {
20 match ty {
21 Type::Path(path) => {
22 if let Some(ident) = path.path.get_ident() {
23 match ident.to_string().as_str() {
24 "f32" => TheTruthType::Float,
25 "f64" => TheTruthType::Double,
26 "u32" => TheTruthType::U32,
27 "u64" => TheTruthType::U64,
28 "bool" => TheTruthType::Bool,
29 _ => panic!("Unsupported property type"),
30 }
31 } else {
32 panic!("Unsupported property type");
33 }
34 }
35 _ => {
36 panic!("Unsupported property type");
37 }
38 }
39 }
40
41 fn get_enum_ident(self, span: Span) -> Ident {
42 match self {
43 TheTruthType::Float => Ident::new(
44 "tm_the_truth_property_type_TM_THE_TRUTH_PROPERTY_TYPE_FLOAT",
45 span,
46 ),
47 TheTruthType::Double => Ident::new(
48 "tm_the_truth_property_type_TM_THE_TRUTH_PROPERTY_TYPE_DOUBLE",
49 span,
50 ),
51 TheTruthType::U32 => Ident::new(
52 "tm_the_truth_property_type_TM_THE_TRUTH_PROPERTY_TYPE_UINT32_T",
53 span,
54 ),
55 TheTruthType::U64 => Ident::new(
56 "tm_the_truth_property_type_TM_THE_TRUTH_PROPERTY_TYPE_UINT64_T",
57 span,
58 ),
59 TheTruthType::Bool => Ident::new(
60 "tm_the_truth_property_type_TM_THE_TRUTH_PROPERTY_TYPE_BOOL",
61 span,
62 ),
63 }
64 }
65
66 fn get_tt_getter_ident(self, span: Span) -> Ident {
67 match self {
68 TheTruthType::Float => Ident::new("get_f32", span),
69 TheTruthType::Double => Ident::new("get_f64", span),
70 TheTruthType::U32 => Ident::new("get_u32", span),
71 TheTruthType::U64 => Ident::new("get_u64", span),
72 TheTruthType::Bool => Ident::new("get_bool", span),
73 }
74 }
75
76 fn get_tt_variadic_convertor(self) -> TokenStream {
77 match self {
78 TheTruthType::Float => quote! { as ::std::os::raw::c_double },
79 TheTruthType::Double => quote! { as ::std::os::raw::c_double },
80 TheTruthType::U32 => quote! { as u32 },
81 TheTruthType::U64 => quote! { as u64 },
82 TheTruthType::Bool => quote! { as bool },
83 }
84 }
85}
86
87fn get_default_value(attribute: &Attribute) -> TokenStream {
88 if let Some(tree) = attribute.tokens.clone().into_iter().next() {
89 if let TokenTree::Group(group) = tree {
90 if group.delimiter() != Delimiter::Parenthesis {
91 return quote! { ::std::default::Default::default() };
92 }
93
94 let tokens = group.stream().into_iter().collect::<Vec<_>>();
95
96 if tokens.len() != 3 {
97 return quote! { ::std::default::Default::default() };
98 }
99
100 if let (TokenTree::Ident(ident), TokenTree::Punct(punct), TokenTree::Literal(lit)) =
101 (&tokens[0], &tokens[1], &tokens[2])
102 {
103 if ident == "default" && punct.as_char() == '=' {
104 return TokenTree::from(lit.clone()).into();
105 } else {
106 panic!("Only (default = ..) supported")
107 }
108 }
109 }
110 }
111
112 quote! { ::std::default::Default::default() }
113}
114
115struct Property<'a> {
116 ident: &'a Ident,
117 ttt: TheTruthType,
118 default_value: TokenStream,
119}
120
121#[proc_macro_derive(Component, attributes(property))]
122pub fn derive_component(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
123 let input = parse_macro_input!(input as DeriveInput);
124
125 let name = input.ident.to_string();
126 let snake_case_name = name.to_snake_case();
127
128 let mut properties = Vec::new();
129
130 if let Data::Struct(data) = &input.data {
131 if let Fields::Named(fields) = &data.fields {
132 for field in &fields.named {
133 if let Some(attribute) = field.attrs.iter().find(|a| {
134 if let Some(ident) = a.path.get_ident() {
135 if ident == "property" {
136 return true;
137 }
138 }
139 false
140 }) {
141 if let Some(ident) = &field.ident {
142 properties.push(Property {
143 ident,
144 ttt: TheTruthType::new(&field.ty),
145 default_value: get_default_value(&attribute),
146 });
147 }
148 }
149 }
150 } else {
151 panic!("Only supports Structs with named fields");
152 }
153 } else {
154 panic!("Only supports Structs");
155 }
156
157 let struct_ident = Ident::new(&name, input.ident.span());
158 let internal_mod_ident = Ident::new(
159 &format!("__{}_internal", snake_case_name),
160 input.ident.span(),
161 );
162 let create_component_ident =
163 Ident::new(&format!("{}_create", snake_case_name), input.ident.span());
164 let create_type_ident = Ident::new(
165 &format!("{}_create_types", snake_case_name),
166 input.ident.span(),
167 );
168 let load_asset_ident = Ident::new(
169 &format!("{}_load_asset", snake_case_name),
170 input.ident.span(),
171 );
172
173 let create_type_fn = create_type::expand_fn(&snake_case_name, &create_type_ident, &properties);
174 let create_types_fn_option = if properties.is_empty() {
175 quote! { None }
176 } else {
177 quote! { Some(#create_type_ident) }
178 };
179
180 let load_asset_fn = load_asset::expand_fn(&struct_ident, &load_asset_ident, &properties);
181 let load_asset_option = load_asset::expand_option(&load_asset_ident, &properties);
182
183 let expanded = quote! {
184
185 impl ::tm_rs::component::Component for #struct_ident {
186 const NAME: &'static [u8] = ::std::concat!(#snake_case_name, "\0").as_bytes();
187 type CType = #struct_ident;
188 }
189
190 mod #internal_mod_ident {
191
192 #create_type_fn
193
194 #load_asset_fn
195
196 unsafe extern "C" fn #create_component_ident(
197 ctx: *mut ::tm_rs::ffi::tm_entity_context_o
198 ) {
199 let mut entity_api = ::tm_rs::api::with_ctx_mut::<::tm_rs::entity::EntityApi>(ctx);
200
201 let component = ::tm_rs::ffi::tm_component_i {
202 name: ::std::concat!(#snake_case_name, "\0").as_bytes().as_ptr() as *const ::std::os::raw::c_char,
203 bytes: ::std::mem::size_of::<super::#struct_ident>() as u32,
204 _padding_103: [0u8 as ::std::os::raw::c_char; 4usize],
205 default_data: ::std::ptr::null(),
206 manager: ::std::ptr::null_mut(),
207 components_created: None,
208 load_asset: #load_asset_option,
209 asset_loaded: None,
210 asset_loaded_sort_order: 0.0f64,
211 asset_reloaded: None,
212 add: None,
213 remove: None,
214 destroy: None,
215 debug_draw: None,
216 debug_draw_settings: ::tm_rs::ffi::tm_tt_id_t {
217 __bindgen_anon_1: ::tm_rs::ffi::tm_tt_id_t__bindgen_ty_1 {
218 u64_: 0u64,
219 },
220 },
221 };
222
223 entity_api.register_component(&component);
224 }
225
226 impl ::tm_rs::component::DerivedComponent for super::#struct_ident {
227 const CREATE_TYPES: Option<unsafe extern "C" fn(*mut ::tm_rs::ffi::tm_the_truth_o)> = #create_types_fn_option;
228 const CREATE_COMPONENT: unsafe extern "C" fn(*mut ::tm_rs::ffi::tm_entity_context_o) = #create_component_ident;
229 }
230
231 fn assert_send<T: Send>() {}
232 fn assert_sync<T: Sync>() {}
233 fn assert_copy<T: Copy>() {}
234 fn assert_default<T: Default>() {}
235
236 fn assertions() {
237 assert_send::<super::#struct_ident>();
238 assert_sync::<super::#struct_ident>();
239 assert_copy::<super::#struct_ident>();
240 assert_default::<super::#struct_ident>();
241 }
242 }
243 };
244
245 proc_macro::TokenStream::from(expanded)
248}