diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 8d8f19ca9a61e..45a948cc8c75d 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -12,11 +12,11 @@ mod world_query; use crate::{query_data::derive_query_data_impl, query_filter::derive_query_filter_impl}; use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest}; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{format_ident, quote}; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, - ConstParam, DeriveInput, GenericParam, Index, TypeParam, + ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, Token, TypeParam, }; enum BundleFieldKind { @@ -288,7 +288,7 @@ pub fn derive_visit_entities(input: TokenStream) -> TokenStream { pub fn derive_system_param(input: TokenStream) -> TokenStream { let token_stream = input.clone(); let ast = parse_macro_input!(input as DeriveInput); - let syn::Data::Struct(syn::DataStruct { + let Data::Struct(DataStruct { fields: field_definitions, .. }) = ast.data @@ -611,3 +611,46 @@ pub fn derive_states(input: TokenStream) -> TokenStream { pub fn derive_substates(input: TokenStream) -> TokenStream { states::derive_substates(input) } + +#[proc_macro_derive(FromWorld)] +pub fn derive_from_world(input: TokenStream) -> TokenStream { + let bevy_ecs_path = bevy_ecs_path(); + let ast = parse_macro_input!(input as DeriveInput); + let struct_name = ast.ident; + let (impl_generics, ty_generics, where_clauses) = ast.generics.split_for_impl(); + + let Data::Struct(DataStruct { fields, .. }) = &ast.data else { + return syn::Error::new( + Span::call_site(), + "#[derive(FromWorld)]` only supports structs", + ) + .into_compile_error() + .into(); + }; + + let field_init_expr = quote!(#bevy_ecs_path::world::FromWorld::from_world(world)); + + let field_initializers: Punctuated = match fields { + syn::Fields::Named(fields_named) => fields_named + .named + .iter() + .map(|field| { + let ident = field.ident.clone().unwrap(); + quote!(#ident: #field_init_expr) + }) + .collect(), + syn::Fields::Unnamed(fields_unnamed) => (0..fields_unnamed.unnamed.len()) + .map(|index| quote!(#index: #field_init_expr)) + .collect(), + syn::Fields::Unit => Punctuated::new(), + }; + + TokenStream::from(quote! { + impl #impl_generics #bevy_ecs_path::world::FromWorld for #struct_name #ty_generics #where_clauses { + fn from_world(world: &mut #bevy_ecs_path::world::World) -> Self { + #[allow(clippy::init_numbered_fields)] + Self { #field_initializers } + } + } + }) +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d2100758f34d6..0f943ef03da0c 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -18,6 +18,7 @@ pub use crate::{ change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, world::command_queue::CommandQueue, }; +pub use bevy_ecs_macros::FromWorld; pub use component_constants::*; pub use deferred_world::DeferredWorld; pub use entity_fetch::WorldEntityFetch; @@ -3657,7 +3658,26 @@ unsafe impl Sync for World {} /// /// This can be helpful for complex initialization or context-aware defaults. /// -/// [`FromWorld`] is automatically implemented for any type implementing [`Default`]. +/// [`FromWorld`] is automatically implemented for any type implementing [`Default`], +/// and may also be derived for any struct whose fields all implement `FromWorld`: +/// ```rs +/// #[derive(Default)] +/// struct A; +/// +/// #[derive(Default)] +/// struct B(Option) +/// +/// struct C; +/// +/// impl FromWorld for C { +/// fn from_world(_world: &mut World) -> Self { +/// Self +/// } +/// } +/// +/// #[derive(FromWorld)] +/// struct D(A, B, C); +/// ``` pub trait FromWorld { /// Creates `Self` using data from the given [`World`]. fn from_world(world: &mut World) -> Self;