Skip to main content

tm_derive/
lib.rs

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    //println!("{}", expanded.to_string());
246
247    proc_macro::TokenStream::from(expanded)
248}