Skip to main content

fourthage_mud_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Meta};
4
5/// Derive macro for `ComponentStorage` that generates standard HashMap-based implementations.
6/// 
7/// Requires a `#[component(field = "field_name")]` attribute specifying the HashMap field
8/// in `EntityRegistryInternal`.
9/// 
10/// # Example
11/// ```ignore
12/// #[derive(ComponentStorage)]
13/// #[component(field = "names")]
14/// pub struct Name(String);
15/// ```
16#[proc_macro_derive(ComponentStorage, attributes(component))]
17pub fn derive_component_storage(input: TokenStream) -> TokenStream {
18    let input = parse_macro_input!(input as DeriveInput);
19
20    let name = &input.ident;
21    let field_name = match extract_field_name(&input.attrs) {
22        Ok(s) => s,
23        Err(_) => {
24            return syn::Error::new_spanned(
25                name,
26                "ComponentStorage derive requires #[component(field = \"field_name\")] attribute",
27            )
28            .to_compile_error().into();
29        }
30    };
31
32    let field_ident = syn::Ident::new(&field_name, name.span());
33
34    let expanded = quote! {
35        impl ComponentStorage for #name {
36            fn get<'a>(entities: &'a EntityRegistryInternal, entity: &EntityId) -> Option<&'a Self>
37            where
38                Self: Sized,
39            {
40                entities.#field_ident.get(entity)
41            }
42
43            fn update(entities: &mut EntityRegistryInternal, entity: &EntityId, component: Self)
44            where
45                Self: Sized,
46            {
47                entities.#field_ident.insert(entity.clone(), component);
48            }
49
50            fn remove(entities: &mut EntityRegistryInternal, entity: &EntityId)
51            where
52                Self: Sized,
53            {
54                entities.#field_ident.remove(entity);
55            }
56
57            fn storage(entities: &EntityRegistryInternal) -> &HashMap<EntityId, Self>
58            where
59                Self: Sized,
60            {
61                &entities.#field_ident
62            }
63        }
64    };
65
66    TokenStream::from(expanded)
67}
68
69fn extract_field_name(attrs: &[syn::Attribute]) -> Result<String, ()> {
70    for attr in attrs {
71        if attr.path().is_ident("component") {
72            // Parse #[component(field = "name")]
73            if let Meta::List(meta_list) = &attr.meta {
74                // Parse the content of the list as a MetaNameValue
75                let content: syn::MetaNameValue = syn::parse2(meta_list.tokens.clone()).map_err(|_| ())?;
76                if content.path.is_ident("field") {
77                    if let syn::Expr::Lit(syn::ExprLit {
78                        lit: syn::Lit::Str(lit_str),
79                        ..
80                    }) = &content.value
81                    {
82                        return Ok(lit_str.value());
83                    }
84                }
85            }
86        }
87    }
88    Err(())
89}