1#![cfg_attr(docsrs, feature(doc_cfg))]
4
5extern crate proc_macro;
6
7mod component;
8mod event;
9mod message;
10mod query_data;
11mod query_filter;
12mod world_query;
13
14use crate::{
15 component::map_entities, query_data::derive_query_data_impl,
16 query_filter::derive_query_filter_impl,
17};
18use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
19use proc_macro::TokenStream;
20use proc_macro2::{Ident, Span};
21use quote::{format_ident, quote, ToTokens};
22use syn::{
23 parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
24 ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, TypeParam,
25};
26
27enum BundleFieldKind {
28 Component,
29 Ignore,
30}
31
32const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
33const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
34const BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS: &str = "ignore_from_components";
35
36#[derive(Debug)]
37struct BundleAttributes {
38 impl_from_components: bool,
39}
40
41impl Default for BundleAttributes {
42 fn default() -> Self {
43 Self {
44 impl_from_components: true,
45 }
46 }
47}
48
49#[proc_macro_derive(Bundle, attributes(bundle))]
51pub fn derive_bundle(input: TokenStream) -> TokenStream {
52 let ast = parse_macro_input!(input as DeriveInput);
53 let ecs_path = bevy_ecs_path();
54
55 let mut errors = vec![];
56
57 let mut attributes = BundleAttributes::default();
58
59 for attr in &ast.attrs {
60 if attr.path().is_ident(BUNDLE_ATTRIBUTE_NAME) {
61 let parsing = attr.parse_nested_meta(|meta| {
62 if meta.path.is_ident(BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS) {
63 attributes.impl_from_components = false;
64 return Ok(());
65 }
66
67 Err(meta.error(format!("Invalid bundle container attribute. Allowed attributes: `{BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS}`")))
68 });
69
70 if let Err(error) = parsing {
71 errors.push(error.into_compile_error());
72 }
73 }
74 }
75
76 let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") {
77 Ok(fields) => fields,
78 Err(e) => return e.into_compile_error().into(),
79 };
80
81 let mut field_kind = Vec::with_capacity(named_fields.len());
82
83 for field in named_fields {
84 let mut kind = BundleFieldKind::Component;
85
86 for attr in field
87 .attrs
88 .iter()
89 .filter(|a| a.path().is_ident(BUNDLE_ATTRIBUTE_NAME))
90 {
91 if let Err(error) = attr.parse_nested_meta(|meta| {
92 if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
93 kind = BundleFieldKind::Ignore;
94 Ok(())
95 } else {
96 Err(meta.error(format!(
97 "Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
98 )))
99 }
100 }) {
101 return error.into_compile_error().into();
102 }
103 }
104
105 field_kind.push(kind);
106 }
107
108 let field = named_fields
109 .iter()
110 .map(|field| field.ident.as_ref())
111 .collect::<Vec<_>>();
112
113 let field_type = named_fields
114 .iter()
115 .map(|field| &field.ty)
116 .collect::<Vec<_>>();
117
118 let mut active_field_types = Vec::new();
119 let mut active_field_tokens = Vec::new();
120 let mut active_field_alias: Vec<proc_macro2::TokenStream> = Vec::new();
121 let mut inactive_field_tokens = Vec::new();
122 for (((i, field_type), field_kind), field) in field_type
123 .iter()
124 .enumerate()
125 .zip(field_kind.iter())
126 .zip(field.iter())
127 {
128 let field_alias = format_ident!("field_{}", i).to_token_stream();
129 let field_tokens = match field {
130 Some(field) => field.to_token_stream(),
131 None => Index::from(i).to_token_stream(),
132 };
133 match field_kind {
134 BundleFieldKind::Component => {
135 active_field_types.push(field_type);
136 active_field_alias.push(field_alias);
137 active_field_tokens.push(field_tokens);
138 }
139
140 BundleFieldKind::Ignore => inactive_field_tokens.push(field_tokens),
141 }
142 }
143 let generics = ast.generics;
144 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
145 let struct_name = &ast.ident;
146
147 let bundle_impl = quote! {
148 #[allow(deprecated)]
153 unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause {
154 fn component_ids(
155 components: &mut #ecs_path::component::ComponentsRegistrator,
156 ids: &mut impl FnMut(#ecs_path::component::ComponentId)
157 ) {
158 #(<#active_field_types as #ecs_path::bundle::Bundle>::component_ids(components, ids);)*
159 }
160
161 fn get_component_ids(
162 components: &#ecs_path::component::Components,
163 ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
164 ) {
165 #(<#active_field_types as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);)*
166 }
167 }
168 };
169
170 let dynamic_bundle_impl = quote! {
171 impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
172 type Effect = ();
173 #[allow(unused_variables)]
174 #[inline]
175 unsafe fn get_components(
176 ptr: #ecs_path::ptr::MovingPtr<'_, Self>,
177 func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>)
178 ) {
179 use #ecs_path::__macro_exports::DebugCheckedUnwrap;
180
181 #ecs_path::ptr::deconstruct_moving_ptr!({
182 let #struct_name { #(#active_field_tokens: #active_field_alias,)* #(#inactive_field_tokens: _,)* } = ptr;
183 });
184 #(
185 <#active_field_types as #ecs_path::bundle::DynamicBundle>::get_components(
186 #active_field_alias,
187 func
188 );
189 )*
190 }
191
192 #[allow(unused_variables)]
193 #[inline]
194 unsafe fn apply_effect(
195 ptr: #ecs_path::ptr::MovingPtr<'_, core::mem::MaybeUninit<Self>>,
196 func: &mut #ecs_path::world::EntityWorldMut<'_>,
197 ) {
198 }
199 }
200 };
201
202 let from_components_impl = attributes.impl_from_components.then(|| quote! {
203 #[allow(deprecated)]
206 unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
207 #[allow(unused_variables, non_snake_case)]
208 unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
209 where
210 __F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
211 {
212 Self {
213 #(#active_field_tokens: <#active_field_types as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),)*
214 #(#inactive_field_tokens: ::core::default::Default::default(),)*
215 }
216 }
217 }
218 });
219
220 let attribute_errors = &errors;
221
222 TokenStream::from(quote! {
223 #(#attribute_errors)*
224 #bundle_impl
225 #from_components_impl
226 #dynamic_bundle_impl
227 })
228}
229
230#[proc_macro_derive(MapEntities, attributes(entities))]
232pub fn derive_map_entities(input: TokenStream) -> TokenStream {
233 let ast = parse_macro_input!(input as DeriveInput);
234 let ecs_path = bevy_ecs_path();
235
236 let map_entities_impl = map_entities(
237 &ast.data,
238 &ecs_path,
239 Ident::new("self", Span::call_site()),
240 false,
241 false,
242 None,
243 );
244
245 let struct_name = &ast.ident;
246 let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
247 TokenStream::from(quote! {
248 impl #impl_generics #ecs_path::entity::MapEntities for #struct_name #type_generics #where_clause {
249 fn map_entities<M: #ecs_path::entity::EntityMapper>(&mut self, mapper: &mut M) {
250 #map_entities_impl
251 }
252 }
253 })
254}
255
256#[proc_macro_derive(SystemParam, attributes(system_param))]
258pub fn derive_system_param(input: TokenStream) -> TokenStream {
259 let token_stream = input.clone();
260 let ast = parse_macro_input!(input as DeriveInput);
261 let Data::Struct(DataStruct {
262 fields: field_definitions,
263 ..
264 }) = ast.data
265 else {
266 return syn::Error::new(
267 ast.span(),
268 "Invalid `SystemParam` type: expected a `struct`",
269 )
270 .into_compile_error()
271 .into();
272 };
273 let path = bevy_ecs_path();
274
275 let mut field_locals = Vec::new();
276 let mut field_names = Vec::new();
277 let mut fields = Vec::new();
278 let mut field_types = Vec::new();
279 let mut field_messages = Vec::new();
280 for (i, field) in field_definitions.iter().enumerate() {
281 field_locals.push(format_ident!("f{i}"));
282 let i = Index::from(i);
283 let field_value = field
284 .ident
285 .as_ref()
286 .map(|f| quote! { #f })
287 .unwrap_or_else(|| quote! { #i });
288 field_names.push(format!("::{field_value}"));
289 fields.push(field_value);
290 field_types.push(&field.ty);
291 let mut field_message = None;
292 for meta in field
293 .attrs
294 .iter()
295 .filter(|a| a.path().is_ident("system_param"))
296 {
297 if let Err(e) = meta.parse_nested_meta(|nested| {
298 if nested.path.is_ident("validation_message") {
299 field_message = Some(nested.value()?.parse()?);
300 Ok(())
301 } else {
302 Err(nested.error("Unsupported attribute"))
303 }
304 }) {
305 return e.into_compile_error().into();
306 }
307 }
308 field_messages.push(field_message.unwrap_or_else(|| quote! { err.message }));
309 }
310
311 let generics = ast.generics;
312
313 for lt in generics.lifetimes() {
315 let ident = <.lifetime.ident;
316 let w = format_ident!("w");
317 let s = format_ident!("s");
318 if ident != &w && ident != &s {
319 return syn::Error::new_spanned(
320 lt,
321 r#"invalid lifetime name: expected `'w` or `'s`
322 'w -- refers to data stored in the World.
323 's -- refers to data stored in the SystemParam's state.'"#,
324 )
325 .into_compile_error()
326 .into();
327 }
328 }
329
330 let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl();
331
332 let lifetimeless_generics: Vec<_> = generics
333 .params
334 .iter()
335 .filter(|g| !matches!(g, GenericParam::Lifetime(_)))
336 .collect();
337
338 let shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|_| quote!('_)).collect();
339
340 let mut punctuated_generics = Punctuated::<_, Comma>::new();
341 punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
342 GenericParam::Type(g) => GenericParam::Type(TypeParam {
343 default: None,
344 ..g.clone()
345 }),
346 GenericParam::Const(g) => GenericParam::Const(ConstParam {
347 default: None,
348 ..g.clone()
349 }),
350 _ => unreachable!(),
351 }));
352
353 let mut punctuated_generic_idents = Punctuated::<_, Comma>::new();
354 punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g {
355 GenericParam::Type(g) => &g.ident,
356 GenericParam::Const(g) => &g.ident,
357 _ => unreachable!(),
358 }));
359
360 let punctuated_generics_no_bounds: Punctuated<_, Comma> = lifetimeless_generics
361 .iter()
362 .map(|&g| match g.clone() {
363 GenericParam::Type(mut g) => {
364 g.bounds.clear();
365 GenericParam::Type(g)
366 }
367 g => g,
368 })
369 .collect();
370
371 let mut tuple_types: Vec<_> = field_types.iter().map(|x| quote! { #x }).collect();
372 let mut tuple_patterns: Vec<_> = field_locals.iter().map(|x| quote! { #x }).collect();
373
374 const LIMIT: usize = 16;
377 while tuple_types.len() > LIMIT {
378 let end = Vec::from_iter(tuple_types.drain(..LIMIT));
379 tuple_types.push(parse_quote!( (#(#end,)*) ));
380
381 let end = Vec::from_iter(tuple_patterns.drain(..LIMIT));
382 tuple_patterns.push(parse_quote!( (#(#end,)*) ));
383 }
384
385 let mut read_only_generics = generics.clone();
388 let read_only_where_clause = read_only_generics.make_where_clause();
389 for field_type in &field_types {
390 read_only_where_clause
391 .predicates
392 .push(syn::parse_quote!(#field_type: #path::system::ReadOnlySystemParam));
393 }
394
395 let fields_alias =
396 ensure_no_collision(format_ident!("__StructFieldsAlias"), token_stream.clone());
397
398 let struct_name = &ast.ident;
399 let state_struct_visibility = &ast.vis;
400 let state_struct_name = ensure_no_collision(format_ident!("FetchState"), token_stream);
401
402 let mut builder_name = None;
403 for meta in ast
404 .attrs
405 .iter()
406 .filter(|a| a.path().is_ident("system_param"))
407 {
408 if let Err(e) = meta.parse_nested_meta(|nested| {
409 if nested.path.is_ident("builder") {
410 builder_name = Some(format_ident!("{struct_name}Builder"));
411 Ok(())
412 } else {
413 Err(nested.error("Unsupported attribute"))
414 }
415 }) {
416 return e.into_compile_error().into();
417 }
418 }
419
420 let builder = builder_name.map(|builder_name| {
421 let builder_type_parameters: Vec<_> = (0..fields.len()).map(|i| format_ident!("B{i}")).collect();
422 let builder_doc_comment = format!("A [`SystemParamBuilder`] for a [`{struct_name}`].");
423 let builder_struct = quote! {
424 #[doc = #builder_doc_comment]
425 struct #builder_name<#(#builder_type_parameters,)*> {
426 #(#fields: #builder_type_parameters,)*
427 }
428 };
429 let lifetimes: Vec<_> = generics.lifetimes().collect();
430 let generic_struct = quote!{ #struct_name <#(#lifetimes,)* #punctuated_generic_idents> };
431 let builder_impl = quote!{
432 unsafe impl<
434 #(#lifetimes,)*
435 #(#builder_type_parameters: #path::system::SystemParamBuilder<#field_types>,)*
436 #punctuated_generics
437 > #path::system::SystemParamBuilder<#generic_struct> for #builder_name<#(#builder_type_parameters,)*>
438 #where_clause
439 {
440 fn build(self, world: &mut #path::world::World) -> <#generic_struct as #path::system::SystemParam>::State {
441 let #builder_name { #(#fields: #field_locals,)* } = self;
442 #state_struct_name {
443 state: #path::system::SystemParamBuilder::build((#(#tuple_patterns,)*), world)
444 }
445 }
446 }
447 };
448 (builder_struct, builder_impl)
449 });
450 let (builder_struct, builder_impl) = builder.unzip();
451
452 TokenStream::from(quote! {
453 const _: () = {
457 type #fields_alias <'w, 's, #punctuated_generics_no_bounds> = (#(#tuple_types,)*);
459
460 #[doc(hidden)]
461 #state_struct_visibility struct #state_struct_name <#(#lifetimeless_generics,)*>
462 #where_clause {
463 state: <#fields_alias::<'static, 'static, #punctuated_generic_idents> as #path::system::SystemParam>::State,
464 }
465
466 unsafe impl<#punctuated_generics> #path::system::SystemParam for
467 #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents> #where_clause
468 {
469 type State = #state_struct_name<#punctuated_generic_idents>;
470 type Item<'w, 's> = #struct_name #ty_generics;
471
472 fn init_state(world: &mut #path::world::World) -> Self::State {
473 #state_struct_name {
474 state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world),
475 }
476 }
477
478 fn init_access(state: &Self::State, system_meta: &mut #path::system::SystemMeta, component_access_set: &mut #path::query::FilteredAccessSet, world: &mut #path::world::World) {
479 <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_access(&state.state, system_meta, component_access_set, world);
480 }
481
482 fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
483 <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
484 }
485
486 fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: #path::world::DeferredWorld) {
487 <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::queue(&mut state.state, system_meta, world);
488 }
489
490 #[inline]
491 unsafe fn validate_param<'w, 's>(
492 state: &'s mut Self::State,
493 _system_meta: &#path::system::SystemMeta,
494 _world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w>,
495 ) -> Result<(), #path::system::SystemParamValidationError> {
496 let #state_struct_name { state: (#(#tuple_patterns,)*) } = state;
497 #(
498 <#field_types as #path::system::SystemParam>::validate_param(#field_locals, _system_meta, _world)
499 .map_err(|err| #path::system::SystemParamValidationError::new::<Self>(err.skipped, #field_messages, #field_names))?;
500 )*
501 Result::Ok(())
502 }
503
504 #[inline]
505 unsafe fn get_param<'w, 's>(
506 state: &'s mut Self::State,
507 system_meta: &#path::system::SystemMeta,
508 world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w>,
509 change_tick: #path::component::Tick,
510 ) -> Self::Item<'w, 's> {
511 let (#(#tuple_patterns,)*) = <
512 (#(#tuple_types,)*) as #path::system::SystemParam
513 >::get_param(&mut state.state, system_meta, world, change_tick);
514 #struct_name {
515 #(#fields: #field_locals,)*
516 }
517 }
518 }
519
520 unsafe impl<'w, 's, #punctuated_generics> #path::system::ReadOnlySystemParam for #struct_name #ty_generics #read_only_where_clause {}
522
523 #builder_impl
524 };
525
526 #builder_struct
527 })
528}
529
530#[proc_macro_derive(QueryData, attributes(query_data))]
532pub fn derive_query_data(input: TokenStream) -> TokenStream {
533 derive_query_data_impl(input)
534}
535
536#[proc_macro_derive(QueryFilter, attributes(query_filter))]
538pub fn derive_query_filter(input: TokenStream) -> TokenStream {
539 derive_query_filter_impl(input)
540}
541
542#[proc_macro_derive(ScheduleLabel)]
546pub fn derive_schedule_label(input: TokenStream) -> TokenStream {
547 let input = parse_macro_input!(input as DeriveInput);
548 let mut trait_path = bevy_ecs_path();
549 trait_path.segments.push(format_ident!("schedule").into());
550 trait_path
551 .segments
552 .push(format_ident!("ScheduleLabel").into());
553 derive_label(input, "ScheduleLabel", &trait_path)
554}
555
556#[proc_macro_derive(SystemSet)]
560pub fn derive_system_set(input: TokenStream) -> TokenStream {
561 let input = parse_macro_input!(input as DeriveInput);
562 let mut trait_path = bevy_ecs_path();
563 trait_path.segments.push(format_ident!("schedule").into());
564 trait_path.segments.push(format_ident!("SystemSet").into());
565 derive_label(input, "SystemSet", &trait_path)
566}
567
568pub(crate) fn bevy_ecs_path() -> syn::Path {
569 BevyManifest::shared().get_path("bevy_ecs")
570}
571
572#[proc_macro_derive(Event, attributes(event))]
574pub fn derive_event(input: TokenStream) -> TokenStream {
575 event::derive_event(input)
576}
577
578#[proc_macro_derive(EntityEvent, attributes(entity_event, event_target))]
592pub fn derive_entity_event(input: TokenStream) -> TokenStream {
593 event::derive_entity_event(input)
594}
595
596#[proc_macro_derive(Message)]
598pub fn derive_message(input: TokenStream) -> TokenStream {
599 message::derive_message(input)
600}
601
602#[proc_macro_derive(Resource)]
604pub fn derive_resource(input: TokenStream) -> TokenStream {
605 component::derive_resource(input)
606}
607
608#[proc_macro_derive(
692 Component,
693 attributes(component, require, relationship, relationship_target, entities)
694)]
695pub fn derive_component(input: TokenStream) -> TokenStream {
696 component::derive_component(input)
697}
698
699#[proc_macro_derive(FromWorld, attributes(from_world))]
701pub fn derive_from_world(input: TokenStream) -> TokenStream {
702 let bevy_ecs_path = bevy_ecs_path();
703 let ast = parse_macro_input!(input as DeriveInput);
704 let name = ast.ident;
705 let (impl_generics, ty_generics, where_clauses) = ast.generics.split_for_impl();
706
707 let (fields, variant_ident) = match &ast.data {
708 Data::Struct(data) => (&data.fields, None),
709 Data::Enum(data) => {
710 match data.variants.iter().find(|variant| {
711 variant
712 .attrs
713 .iter()
714 .any(|attr| attr.path().is_ident("from_world"))
715 }) {
716 Some(variant) => (&variant.fields, Some(&variant.ident)),
717 None => {
718 return syn::Error::new(
719 Span::call_site(),
720 "No variant found with the `#[from_world]` attribute",
721 )
722 .into_compile_error()
723 .into();
724 }
725 }
726 }
727 Data::Union(_) => {
728 return syn::Error::new(
729 Span::call_site(),
730 "#[derive(FromWorld)]` does not support unions",
731 )
732 .into_compile_error()
733 .into();
734 }
735 };
736
737 let field_init_expr = quote!(#bevy_ecs_path::world::FromWorld::from_world(world));
738 let members = fields.members();
739
740 let field_initializers = match variant_ident {
741 Some(variant_ident) => quote!( Self::#variant_ident {
742 #(#members: #field_init_expr),*
743 }),
744 None => quote!( Self {
745 #(#members: #field_init_expr),*
746 }),
747 };
748
749 TokenStream::from(quote! {
750 impl #impl_generics #bevy_ecs_path::world::FromWorld for #name #ty_generics #where_clauses {
751 fn from_world(world: &mut #bevy_ecs_path::world::World) -> Self {
752 #field_initializers
753 }
754 }
755 })
756}