bevy_asset_macros/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Macros for deriving asset traits.
4
5use bevy_macro_utils::{as_member, BevyManifest};
6use proc_macro::{Span, TokenStream};
7use quote::{format_ident, quote};
8use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Path};
9
10pub(crate) fn bevy_asset_path() -> Path {
11    BevyManifest::shared(|manifest| manifest.get_path("bevy_asset"))
12}
13
14const DEPENDENCY_ATTRIBUTE: &str = "dependency";
15
16/// Implement the `Asset` trait.
17#[proc_macro_derive(Asset, attributes(dependency))]
18pub fn derive_asset(input: TokenStream) -> TokenStream {
19    let ast = parse_macro_input!(input as DeriveInput);
20    let bevy_asset_path: Path = bevy_asset_path();
21
22    let struct_name = &ast.ident;
23    let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
24    let dependency_visitor = match derive_dependency_visitor_internal(&ast, &bevy_asset_path) {
25        Ok(dependency_visitor) => dependency_visitor,
26        Err(err) => return err.into_compile_error().into(),
27    };
28
29    TokenStream::from(quote! {
30        impl #impl_generics #bevy_asset_path::Asset for #struct_name #type_generics #where_clause { }
31        #dependency_visitor
32    })
33}
34
35/// Implement the `VisitAssetDependencies` trait.
36#[proc_macro_derive(VisitAssetDependencies, attributes(dependency))]
37pub fn derive_asset_dependency_visitor(input: TokenStream) -> TokenStream {
38    let ast = parse_macro_input!(input as DeriveInput);
39    let bevy_asset_path: Path = bevy_asset_path();
40    match derive_dependency_visitor_internal(&ast, &bevy_asset_path) {
41        Ok(dependency_visitor) => TokenStream::from(dependency_visitor),
42        Err(err) => err.into_compile_error().into(),
43    }
44}
45
46fn derive_dependency_visitor_internal(
47    ast: &DeriveInput,
48    bevy_asset_path: &Path,
49) -> Result<proc_macro2::TokenStream, syn::Error> {
50    let struct_name = &ast.ident;
51    let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
52
53    let visit_dep = |to_read| quote!(#bevy_asset_path::VisitAssetDependencies::visit_dependencies(#to_read, visit););
54    let is_dep_attribute = |a: &syn::Attribute| a.path().is_ident(DEPENDENCY_ATTRIBUTE);
55    let field_has_dep = |f: &syn::Field| f.attrs.iter().any(is_dep_attribute);
56
57    let body = match &ast.data {
58        Data::Struct(DataStruct { fields, .. }) => {
59            let field_visitors = fields
60                .iter()
61                .enumerate()
62                .filter(|(_, f)| field_has_dep(f))
63                .map(|(i, field)| as_member(field.ident.as_ref(), i))
64                .map(|member| visit_dep(quote!(&self.#member)));
65            Some(quote!(#(#field_visitors)*))
66        }
67        Data::Enum(data_enum) => {
68            let variant_has_dep = |v: &syn::Variant| v.fields.iter().any(field_has_dep);
69            let any_case_required = data_enum.variants.iter().any(variant_has_dep);
70            let cases = data_enum.variants.iter().filter(|v| variant_has_dep(v));
71            let cases = cases.map(|variant| {
72                let ident = &variant.ident;
73                let field_members = variant
74                    .fields
75                    .iter()
76                    .enumerate()
77                    .filter(|(_, f)| field_has_dep(f))
78                    .map(|(i, field)| as_member(field.ident.as_ref(), i));
79                let field_locals = field_members.clone().map(|m| format_ident!("__self_{}", m));
80                let field_visitors = field_locals.clone().map(|i| visit_dep(quote!(#i)));
81                quote!(Self::#ident {#(#field_members: #field_locals,)* ..} => {
82                    #(#field_visitors)*
83                })
84            });
85
86            any_case_required.then(|| quote!(match self { #(#cases)*, _ => {} }))
87        }
88        Data::Union(_) => {
89            return Err(syn::Error::new(
90                Span::call_site().into(),
91                "Asset derive currently doesn't work on unions",
92            ));
93        }
94    };
95
96    // prevent unused variable warning in case there are no dependencies
97    let visit = if body.is_none() {
98        quote! { _visit }
99    } else {
100        quote! { visit }
101    };
102
103    Ok(quote! {
104        impl #impl_generics #bevy_asset_path::VisitAssetDependencies for #struct_name #type_generics #where_clause {
105            fn visit_dependencies(&self, #visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) {
106                #body
107            }
108        }
109    })
110}