bevy_2dviewangle_macro/
lib.rs

1// Copyright 2024 Trung Do <dothanhtrung@pm.me>
2
3use proc_macro::TokenStream;
4use std::collections::HashMap;
5
6use quote::{format_ident, quote};
7use syn::punctuated::Punctuated;
8use syn::{Data, Expr, ExprLit, Fields, Lit, Meta, Token};
9
10macro_rules! enumize {
11    ($k: expr, $e: expr, $m: expr, $c: expr, $v: expr) => {{
12        let num;
13        let key_str = capitalize_first_letter(&$k.value());
14        if $m.contains_key(&key_str) {
15            num = *$m.get(&key_str).unwrap();
16        } else {
17            $c += 1;
18            let variant_name = syn::Ident::new(&key_str, $k.span());
19            $e.push(quote! {#variant_name});
20            $m.insert(key_str, $c);
21            num = $c;
22        }
23        $v = quote! {Some(#num)};
24    }}
25}
26
27fn capitalize_first_letter(s: &str) -> String {
28    let mut c = s.chars();
29    match c.next() {
30        None => String::new(),
31        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
32    }
33}
34
35const TEXTUREVIEW_ATTRIBUTE: &str = "textureview";
36#[proc_macro_derive(View2dCollection, attributes(textureview))]
37pub fn actors_textures_derive(input: TokenStream) -> TokenStream {
38    let ast: syn::DeriveInput = syn::parse(input).unwrap();
39    impl_actors_textures(ast).unwrap_or_default().into()
40}
41
42fn impl_actors_textures(ast: syn::DeriveInput) -> Result<proc_macro2::TokenStream, Vec<syn::Error>> {
43    let struct_name = &ast.ident;
44
45    if let Data::Struct(data_struct) = &ast.data {
46        if let Fields::Named(fields) = &data_struct.fields {
47            let mut fields_info = Vec::new();
48            let mut actor_enum = Vec::new();
49            let mut actor_map = HashMap::new();
50            let mut actor_count: u64 = 0;
51            let mut action_enum = Vec::new();
52            let mut action_map = HashMap::new();
53            let mut action_count: u16 = 0;
54
55            for field in fields.named.iter() {
56                let field_name = field.ident.as_ref().unwrap();
57                let mut actor_value = quote! {None};
58                let mut action_value = quote! {None};
59                let mut angle_value = quote! {None};
60                let mut image_value = quote! {None};
61                let mut atlas_layout_value = quote! {None};
62
63                for attr in field
64                    .attrs
65                    .iter()
66                    .filter(|attribute| attribute.path().is_ident(TEXTUREVIEW_ATTRIBUTE))
67                {
68                    let view_meta_list = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated);
69
70                    for attribute in view_meta_list.unwrap() {
71                        match attribute {
72                            Meta::NameValue(named_value) if named_value.path.is_ident("actor") => {
73                                if let Expr::Lit(ExprLit { lit: Lit::Str(key), .. }) = &named_value.value {
74                                    enumize!(key, actor_enum, actor_map, actor_count, actor_value);
75                                }
76                            }
77                            Meta::NameValue(named_value) if named_value.path.is_ident("action") => {
78                                if let Expr::Lit(ExprLit { lit: Lit::Str(key), .. }) = &named_value.value {
79                                    enumize!(key, action_enum, action_map, action_count, action_value);
80                                }
81                            }
82                            Meta::NameValue(named_value) if named_value.path.is_ident("angle") => {
83                                if let Expr::Lit(ExprLit { lit: Lit::Str(key), .. }) = &named_value.value {
84                                    let key_str = capitalize_first_letter(&key.value());
85                                    let variant_name = syn::Ident::new(&key_str, key.span());
86                                    angle_value = quote! {Some(Angle::#variant_name)};
87                                }
88                            }
89                            _ => {}
90                        }
91                    }
92                }
93
94                match &field.ty {
95                    ty if quote!(#ty).to_string() == "Handle < Image >" => {
96                        image_value = quote! {Some(&self.#field_name)}
97                    }
98                    ty if quote!(#ty).to_string() == "Handle < TextureAtlasLayout >" => {
99                        atlas_layout_value = quote! {Some(&self.#field_name)}
100                    }
101                    _ => {}
102                }
103
104                let field_info = quote! {
105                    (
106                        #actor_value,
107                        #action_value,
108                        #angle_value,
109                        #image_value,
110                        #atlas_layout_value,
111                    )
112                };
113
114                fields_info.push(field_info);
115            }
116
117            let actor_enum_name = format_ident!("Actor{}", struct_name);
118            let action_enum_name = format_ident!("Action{}", struct_name);
119            let expanded = quote! {
120                use bevy_2dviewangle::*;
121
122                #[derive(Default, Eq, PartialEq)]
123                #[repr(u64)]
124                pub enum #actor_enum_name {
125                    #[default]
126                    Any,
127                    #( #actor_enum ),*
128                }
129
130                impl From<#actor_enum_name> for u64 {
131                    fn from(actor: #actor_enum_name) -> Self {
132                        actor as u64
133                    }
134                }
135
136                #[derive(Default, Eq, PartialEq)]
137                #[repr(u16)]
138                pub enum #action_enum_name {
139                    #[default]
140                    Any,
141                    #( #action_enum ),*
142                }
143
144                impl From<#action_enum_name> for u16 {
145                    fn from(action: #action_enum_name) -> Self {
146                        action as u16
147                    }
148                }
149
150                #[automatically_derived]
151                impl View2dCollection for #struct_name {
152                    fn get_all(&self) -> Vec<(
153                        Option<u64>,
154                        Option<u16>,
155                        Option<Angle>,
156                        Option<&Handle<Image>>,
157                        Option<&Handle<TextureAtlasLayout>>,
158                    )> {
159                        vec![#( #fields_info ),*]
160                    }
161                }
162            };
163            return Ok(expanded);
164        }
165    }
166
167    Err(vec![])
168}