bevy_descendant_collector_derive/
lib.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{Attribute, DeriveInput, Meta, parse_macro_input};
4
5fn read_attribute(attrs: Vec<Attribute>, attribute_name: &str) -> Option<TokenStream> {
6	let name_path_attr = attrs
7		.iter()
8		.find(|attr| attr.path().is_ident(attribute_name));
9
10	if let Some(attr) = name_path_attr {
11		if let Meta::List(list) = &attr.meta {
12			Some(list.tokens.clone())
13		} else {
14			panic!("{attribute_name} has the wrong type");
15		}
16	} else {
17		None
18	}
19}
20
21/// Inserts this component to freshly spawned scenes marked with a generic component
22#[proc_macro_derive(EntityCollectorTarget, attributes(name_path))]
23pub fn entity_collector_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
24	let derive_input = parse_macro_input!(input as DeriveInput);
25	let struct_name = derive_input.ident;
26	let generics = derive_input.generics;
27	let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
28
29	let fields = match derive_input.data {
30		syn::Data::Struct(ref data) => match &data.fields {
31			syn::Fields::Named(fields) => &fields.named,
32			_ => panic!("Only named fields are supported"),
33		},
34		_ => panic!("Only structs are supported"),
35	};
36
37	let armature_entry_name = read_attribute(derive_input.attrs, "name_path").expect("no root name_path is defined, define the name of the root element (The first element in blender in the exported collection)");
38
39	let assignments = fields
40		.iter()
41		.map(|field| {
42			let armature_field_entry_name = read_attribute(field.attrs.clone(), "name_path").expect("Field's that are not name_path's are not permitted");
43			let ident = field.ident.as_ref().expect("The variable must have a name!");
44			(ident, armature_field_entry_name)
45		})
46		.map(|(ident, path)| {
47			let error_msg = if path.is_empty() {
48				format!("Root element not found for {struct_name}. Actual name paths are:\n")
49			} else {
50				format!("Named element not found for {struct_name} at {path}. Actual name paths are:\n")
51			};
52			quote! {
53				#ident: bevy_descendant_collector::find_named_entity(entity_source_root, &named_query, &[#path]).unwrap_or_else(|| {
54					let named_entity_paths = bevy_descendant_collector::collect_named_entity_paths(entity_source_root, &named_query);
55					panic!("{} {:#?}", #error_msg, named_entity_paths);
56				}),
57			}
58		})
59		.collect::<Vec<_>>();
60
61	proc_macro::TokenStream::from(quote! {
62		impl #impl_generics bevy_descendant_collector::DescendantLoader for #struct_name #ty_generics #where_clause {
63
64			fn get_root_entity_name() -> &'static str {
65				#armature_entry_name
66			}
67
68			fn collect_descendants(
69				commands: &mut Commands,
70				entity_source_root: Entity,
71				entity_map_target: Entity,
72				named_query: &Query<(Entity, Option<&Name>, Option<&Children>)>) {
73				let armature = Self {
74					#(#assignments)*
75				};
76				commands.entity(entity_map_target).insert(armature);
77			}
78		}
79	})
80}