unrust_proc_macro/
macro.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use std::fmt::Write;
4
5#[proc_macro_attribute]
6pub fn unity_authoring(_: TokenStream, item: TokenStream) -> TokenStream {
7    let input = proc_macro2::TokenStream::from(item);
8    quote! {
9        #[derive(bevy::prelude::Component, Clone, Copy, Debug)]
10        #[repr(C)]
11        #input
12    }
13    .into()
14}
15
16#[proc_macro_attribute]
17pub fn bevy_state(_: TokenStream, item: TokenStream) -> TokenStream {
18    let input = proc_macro2::TokenStream::from(item);
19    quote! {
20        #[repr(u8)]
21        #[derive(Clone, Eq, PartialEq, Debug, Hash, Default, unrust::bevy::prelude::States)]
22        #input
23    }
24    .into()
25}
26
27#[proc_macro]
28pub fn generate_inbuilt(item: TokenStream) -> TokenStream {
29    let config = syn::parse2::<syn::ExprTuple>(item.into()).expect("expecting tuples");
30
31    let enum_types = config.elems.iter().filter_map(|p| {
32        let syn::Expr::Path(p) = p else {
33            return None;
34        };
35        let p = p.path.get_ident()?;
36        Some(p)
37    });
38
39    let custom_types = enum_types.clone().enumerate().map(|(index, ident)| {
40        let index = index as u8;
41        quote! {
42            #ident= #index
43        }
44    });
45
46    let component_types = enum_types.clone().map(|ident| {
47        quote! {
48            pub #ident: #ident
49        }
50    });
51
52    let ingest_types = enum_types.clone().map(|ident| {
53        let fn_name = format_ident!("{}_ingest_component", ident);
54
55        quote! {
56            InbuiltTypes::#ident => #fn_name(entity, &ele.value.#ident)
57        }
58    });
59
60    let unity_enums =
61        enum_types
62            .clone()
63            .enumerate()
64            .fold(String::default(), |mut acc, (index, ident)| {
65                write!(acc, "{} = {},\n", ident, index).unwrap();
66
67                acc
68            });
69
70    let union_types = enum_types
71        .clone()
72        .fold(String::default(), |mut acc, ident| {
73            write!(
74                acc,
75                r#"
76              [FieldOffset(0)]
77              public {} {};
78"#,
79                ident, ident
80            )
81            .unwrap();
82
83            acc
84        });
85
86    let count = enum_types.clone().count() as i32;
87
88    let csharp_fns = enum_types.clone().map(|ident| {
89        let fn_name = format_ident!("{}_CSHARP_TOKEN", ident);
90
91        quote! {
92            #fn_name()
93        }
94    });
95
96    quote! {
97        #[repr(u8)]
98        pub enum InbuiltTypes {
99            #(#custom_types,)*
100        }
101
102        #[allow(non_snake_case)]
103        pub union InbuiltComponents {
104            #(#component_types,)*
105        }
106
107        #[repr(C)]
108        pub struct InbuiltData {
109            pub ty: InbuiltTypes,
110            pub value: InbuiltComponents,
111        }
112
113        #[repr(C)]
114        pub struct InbuiltEntityData {
115            pub entity: UnityEntity,
116            pub data: *const InbuiltData,
117            pub len: usize,
118        }
119
120        pub unsafe fn ingest_component(entity: &mut EntityMut, components: &[InbuiltData]) {
121            for ele in components {
122                match ele.ty {
123                    #(#ingest_types,)*
124                }
125            }
126        }
127
128        const UNITY_TYPES: &str = #unity_enums;
129        const UNITY_UNION: &str = #union_types;
130        const UNITY_COUNT: i32 = #count;
131
132        fn get_inbuilt_csharp_tokens() -> Vec<csharp::Tokens> {
133            vec![#(#csharp_fns,)*]
134        }
135    }
136    .into()
137}
138
139#[proc_macro_attribute]
140pub fn unity_prefab(_: TokenStream, item: TokenStream) -> TokenStream {
141    let input = proc_macro2::TokenStream::from(item.clone());
142
143    let parsed_enum = syn::parse_macro_input!(item as syn::ItemEnum);
144
145    let enum_name = parsed_enum.ident;
146
147    let res_name = format_ident!("{}Resource", enum_name);
148
149    let variants = parsed_enum.variants.iter().enumerate().map(|(index, v)| {
150        let id = &v.ident;
151        quote! {
152            self.vals.insert(#enum_name::#id, unrust::InstantiateEntity {
153                entity: vals[#index].clone()
154            });
155        }
156    });
157
158    quote! {
159        #[derive(PartialEq,Eq, Hash)]
160        #input
161
162        #[derive(unrust::bevy::prelude::Resource, Default)]
163        pub struct #res_name {
164            pub vals: std::collections::HashMap<#enum_name,unrust::InstantiateEntity>
165        }
166
167        impl #res_name {
168            pub fn get_unity_prefab(&self, val: &#enum_name) -> Option<&unrust::InstantiateEntity> {
169                self.vals.get(val)
170            }
171
172            pub fn insert_prefabs(&mut self, vals: &[unrust::UnityEntity]) {
173                #(#variants)*
174            }
175        }
176    }
177    .into()
178}
179
180#[proc_macro_attribute]
181pub fn unrust_setup(attr: TokenStream, item: TokenStream) -> TokenStream {
182    let input = proc_macro2::TokenStream::from(item.clone());
183    let parsed = syn::parse_macro_input!(item as syn::ItemFn);
184    let ident = parsed.sig.ident;
185
186    let custom_incoming = handle_custom_components(attr.clone());
187    let state_incoming = handle_custom_states(attr.clone());
188
189    let states = custom_states(attr.clone());
190    let prefabs = prefab_resources(attr.clone());
191    let register = register_prefabs(attr.clone());
192
193    quote! {
194        #input
195
196        #[derive(Default,Copy,Clone)]
197        #[repr(C)]
198        pub struct Game;
199
200        impl GamePlugin for Game {
201            fn initialize(&self, app: &mut App) {
202                #states
203                #prefabs
204                #ident(app);
205            }
206
207            fn register(&self, world: &mut World, prefabs: unrust::PrefabData) {
208                #register
209            }
210
211            #[allow(clippy::missing_safety_doc)]
212            unsafe fn spawn_custom(
213                &self,
214                entity: &mut unrust::bevy::ecs::world::EntityMut,
215                custom: *const u8,
216                custom_len: usize,
217                custom_state: *const u8,
218                custom_state_len: usize,
219            ) {
220                unsafe { handle_custom_components(entity, custom, custom_len) };
221                unsafe { handle_custom_states(entity, custom_state, custom_state_len); }
222            }
223        }
224
225        #custom_incoming
226
227        #state_incoming
228
229        #[no_mangle]
230        pub extern "C" fn create_game() {
231            let game = Box::new(Game);
232
233            unsafe { unrust::setup_game(game) };
234        }
235    }
236    .into()
237}
238
239fn get_nth_tuple(item: TokenStream, n: usize) -> Option<syn::ExprTuple> {
240    let tokens = proc_macro2::TokenStream::from(item);
241    let config = syn::parse2::<syn::ExprTuple>(tokens).expect("expecting a tuple of tuples");
242
243    let Some(config) = config.elems.iter().nth(n) else {
244        panic!("expected at least n tuples")
245    };
246
247    let syn::Expr::Tuple(types) = config else {
248        panic!("expected tuple for custom incoming types!");
249    };
250
251    Some(types.clone())
252}
253
254fn handle_custom_components(item: TokenStream) -> proc_macro2::TokenStream {
255    let Some(types) = get_nth_tuple(item, 0) else {
256        return quote! {
257            fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
258        };
259    };
260
261    let filtered = types.elems.iter().filter_map(|expr| {
262        let syn::Expr::Path(expr) = expr else {
263            return None;
264        };
265
266        let last = expr.path.segments.last()?;
267        Some((last, &expr.path))
268    });
269
270    let custom_types = filtered.clone().enumerate().map(|(index, (exp, _))| {
271        let index = index as u8;
272        quote! {
273            #exp = #index
274        }
275    });
276
277    let component_types = filtered.clone().map(|(exp, rest)| {
278        quote! {
279            pub #exp: #rest
280        }
281    });
282
283    let match_types = filtered.clone().map(|(exp, _)| {
284        quote! {
285            CustomTypes::#exp => entity.insert(ele.value.#exp)
286        }
287    });
288
289    if filtered.count() > 0 {
290        quote! {
291            #[repr(u8)]
292            pub enum CustomTypes {
293                #(#custom_types,)*
294            }
295
296            #[allow(non_snake_case)]
297            union CustomComponents {
298                #(#component_types,)*
299            }
300
301            #[repr(C)]
302            struct CustomData {
303                pub ty: CustomTypes,
304                pub value: CustomComponents,
305            }
306
307            unsafe fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {
308                let components = unsafe { std::slice::from_raw_parts(custom as *const CustomData, len) };
309                for ele in components {
310                    match ele.ty {
311                        #(#match_types,)*
312                    };
313                };
314            }
315        }
316    } else {
317        quote! {
318            fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
319        }
320    }
321}
322
323fn handle_custom_states(item: TokenStream) -> proc_macro2::TokenStream {
324    let Some(types) = get_nth_tuple(item, 1) else {
325        return quote! {
326            fn handle_custom_components(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
327        };
328    };
329
330    let filtered = types.elems.iter().filter_map(|expr| {
331        let syn::Expr::Path(expr) = expr else {
332            return None;
333        };
334
335        let last = expr.path.segments.last()?;
336        Some((last, &expr.path))
337    });
338
339    let custom_types = filtered.clone().enumerate().map(|(index, (exp, _))| {
340        let index = index as u8;
341        quote! {
342            #exp = #index
343        }
344    });
345
346    let match_types = filtered.clone().map(|(exp, _)| {
347        let ident = &exp.ident;
348        let comp_name = format_ident!("Custom{ident}");
349        quote! {
350            CustomStates::#exp => {
351                entity.insert(#comp_name { val: ele.value });
352            }
353        }
354    });
355
356    let custom_components = filtered.clone().map(|(exp, p)| {
357        let ident = &exp.ident;
358        let comp_name = format_ident!("Custom{ident}");
359        let fn_name = format_ident!("update_{ident}");
360        quote! {
361            #[derive(unrust::bevy::prelude::Component,Debug, Clone, PartialEq)]
362            pub struct #comp_name {
363                pub val: u8,
364            }
365
366            #[allow(non_snake_case)]
367            fn #fn_name(
368                mut next_state: ResMut<NextState<#p>>,
369                entities: Query<&#comp_name, Changed<#comp_name>>,
370            ) {
371                let Ok(state) = entities.get_single() else {
372                    return;
373                };
374
375                unrust::tracing::trace!("switching to {}", state.val);
376
377                unsafe {
378                    next_state.set(std::mem::transmute(state.val));
379                }
380            }
381        }
382    });
383
384    if filtered.count() > 0 {
385        quote! {
386            #(#custom_components)*
387
388            #[repr(u8)]
389            #[derive(Clone, Debug, PartialEq)]
390            pub enum CustomStates {
391                #(#custom_types,)*
392            }
393
394            #[repr(C)]
395            struct CustomStateData {
396                pub ty: CustomStates,
397                pub value: u8,
398            }
399
400            unsafe fn handle_custom_states(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {
401                let components = unsafe { std::slice::from_raw_parts(custom as *const CustomStateData, len) };
402                for ele in components {
403                    match ele.ty {
404                        #(#match_types,)*
405                    };
406                };
407            }
408        }
409    } else {
410        quote! {
411            unsafe fn handle_custom_states(entity: &mut unrust::bevy::ecs::world::EntityMut, custom: *const u8, len: usize) {}
412        }
413    }
414}
415
416fn custom_states(item: TokenStream) -> proc_macro2::TokenStream {
417    let Some(states) = get_nth_tuple(item, 1) else {
418        return quote! {};
419    };
420
421    let filtered = states.elems.iter().filter_map(|expr| {
422        let syn::Expr::Path(expr) = expr else {
423            return None;
424        };
425
426        let last = expr.path.segments.last()?;
427        let comp_name = &last.ident;
428        let fn_name = format_ident!("update_{comp_name}");
429
430        Some(quote! {
431            app.add_state::<#expr>();
432            app.add_systems(PostUpdate, #fn_name);
433        })
434    });
435
436    quote! {
437        #(#filtered)*
438    }
439}
440
441fn prefab_resources(item: TokenStream) -> proc_macro2::TokenStream {
442    let Some(states) = get_nth_tuple(item, 2) else {
443        return quote! {};
444    };
445
446    let filtered = states.elems.iter().filter_map(|expr| {
447        let syn::Expr::Path(expr) = expr else {
448            return None;
449        };
450
451        let last = expr.path.segments.last()?;
452        let comp_name = &last.ident;
453        let res_name = format_ident!("{comp_name}Resource");
454
455        Some(quote! {
456            app.insert_resource(#res_name::default());
457        })
458    });
459
460    quote! {
461        #(#filtered)*
462    }
463}
464
465fn register_prefabs(item: TokenStream) -> proc_macro2::TokenStream {
466    let Some(states) = get_nth_tuple(item, 2) else {
467        return quote! {};
468    };
469
470    let filtered = states
471        .elems
472        .iter()
473        .filter_map(|expr| {
474            let syn::Expr::Path(expr) = expr else {
475                return None;
476            };
477
478            Some(expr)
479        })
480        .enumerate()
481        .map(|(index, ident)| {
482            let count = index as i32;
483            let ident = quote! { #ident };
484            let ident: syn::Expr =
485                syn::parse_str(&format!("{}Resource", ident.to_string().replace(' ', ""))).unwrap();
486
487            Some(quote! {
488                #count => {
489                    let Some(mut res) = world.get_resource_mut::<#ident>() else {
490                        return;
491                    };
492
493                    res.insert_prefabs(guids);
494                }
495            })
496        });
497
498    quote! {
499        let guids = unsafe { std::slice::from_raw_parts(prefabs.guids, prefabs.len) };
500        match prefabs.ref_id {
501            #(#filtered)*
502            _ => {}
503        }
504    }
505}