Path: blob/main/crates/bevy_ecs/macros/src/query_data.rs
9367 views
use bevy_macro_utils::{ensure_no_collision, get_struct_fields};1use proc_macro::TokenStream;2use proc_macro2::{Ident, Span};3use quote::{format_ident, quote};4use syn::{5parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Attribute, DeriveInput,6Fields, ImplGenerics, Member, Meta, Type, TypeGenerics, Visibility, WhereClause,7};89use crate::{10bevy_ecs_path,11world_query::{item_struct, world_query_impl},12};1314#[derive(Default)]15struct QueryDataAttributes {16pub is_mutable: bool,1718pub is_contiguous_mutable: bool,19pub is_contiguous_immutable: bool,2021pub derive_args: Punctuated<Meta, Comma>,22}2324static MUTABLE_ATTRIBUTE_NAME: &str = "mutable";25static DERIVE_ATTRIBUTE_NAME: &str = "derive";26static CONTIGUOUS_ATTRIBUTE_NAME: &str = "contiguous";2728mod field_attr_keywords {29syn::custom_keyword!(ignore);30}3132pub static QUERY_DATA_ATTRIBUTE_NAME: &str = "query_data";3334fn contiguous_item_struct(35path: &syn::Path,36fields: &Fields,37derive_macro_call: &proc_macro2::TokenStream,38struct_name: &Ident,39visibility: &Visibility,40item_struct_name: &Ident,41field_types: &Vec<Type>,42user_impl_generics_with_world_and_state: &ImplGenerics,43field_attrs: &Vec<Vec<Attribute>>,44field_visibilities: &Vec<Visibility>,45field_members: &Vec<Member>,46user_ty_generics: &TypeGenerics,47user_ty_generics_with_world_and_state: &TypeGenerics,48user_where_clauses_with_world_and_state: Option<&WhereClause>,49) -> proc_macro2::TokenStream {50let item_attrs = quote! {51#[doc = concat!(52"Automatically generated [`ContiguousQueryData`](",53stringify!(#path),54"::fetch::ContiguousQueryData) item type for [`",55stringify!(#struct_name),56"`], returned when iterating over contiguous query results",57)]58#[automatically_derived]59};6061match fields {62Fields::Named(_) => quote! {63#derive_macro_call64#item_attrs65#visibility struct #item_struct_name #user_impl_generics_with_world_and_state #user_where_clauses_with_world_and_state {66#(#(#field_attrs)* #field_visibilities #field_members: <#field_types as #path::query::ContiguousQueryData>::Contiguous<'__w, '__s>,)*67}68},69Fields::Unnamed(_) => quote! {70#derive_macro_call71#item_attrs72#visibility struct #item_struct_name #user_impl_generics_with_world_and_state #user_where_clauses_with_world_and_state (73#( #field_visibilities <#field_types as #path::query::ContiguousQueryData>::Contiguous<'__w, '__s>, )*74)75},76Fields::Unit => quote! {77#item_attrs78#visibility type #item_struct_name #user_ty_generics_with_world_and_state = #struct_name #user_ty_generics;79},80}81}8283fn contiguous_query_data_impl(84path: &syn::Path,85struct_name: &Ident,86contiguous_item_struct_name: &Ident,87field_types: &Vec<Type>,88user_impl_generics: &ImplGenerics,89user_ty_generics: &TypeGenerics,90user_ty_generics_with_world_and_state: &TypeGenerics,91field_members: &Vec<Member>,92field_aliases: &Vec<Ident>,93user_where_clauses: Option<&WhereClause>,94) -> proc_macro2::TokenStream {95quote! {96impl #user_impl_generics #path::query::ContiguousQueryData for #struct_name #user_ty_generics #user_where_clauses {97type Contiguous<'__w, '__s> = #contiguous_item_struct_name #user_ty_generics_with_world_and_state;9899unsafe fn fetch_contiguous<'__w, '__s>(100_state: &'__s <Self as #path::query::WorldQuery>::State,101_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,102_entities: &'__w [#path::entity::Entity],103) -> Self::Contiguous<'__w, '__s> {104#contiguous_item_struct_name {105#(106#field_members:107<#field_types>::fetch_contiguous(108&_state.#field_aliases,109&mut _fetch.#field_aliases,110_entities,111),112)*113}114}115}116}117}118119pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {120let tokens = input.clone();121122let ast = parse_macro_input!(input as DeriveInput);123let visibility = ast.vis;124125let mut attributes = QueryDataAttributes::default();126for attr in &ast.attrs {127if !attr.path().is_ident(QUERY_DATA_ATTRIBUTE_NAME) {128continue;129}130131let result = attr.parse_nested_meta(|meta| {132if meta.path.is_ident(MUTABLE_ATTRIBUTE_NAME) {133attributes.is_mutable = true;134Ok(())135} else if meta.path.is_ident(DERIVE_ATTRIBUTE_NAME) {136meta.parse_nested_meta(|meta| {137attributes.derive_args.push(Meta::Path(meta.path));138Ok(())139})140} else if meta.path.is_ident(CONTIGUOUS_ATTRIBUTE_NAME) {141meta.parse_nested_meta(|meta| {142if meta.path.is_ident("all") {143attributes.is_contiguous_mutable = true;144attributes.is_contiguous_immutable = true;145Ok(())146} else if meta.path.is_ident("mutable") {147attributes.is_contiguous_mutable = true;148Ok(())149} else if meta.path.is_ident("immutable") {150attributes.is_contiguous_immutable = true;151Ok(())152} else {153Err(meta.error("invalid target, expected `all`, `mutable` or `immutable`"))154}155})156} else {157Err(meta.error(format_args!("invalid attribute, expected `{MUTABLE_ATTRIBUTE_NAME}`, `{DERIVE_ATTRIBUTE_NAME}` or `{CONTIGUOUS_ATTRIBUTE_NAME}`")))158}159});160161if let Err(err) = result {162return err.to_compile_error().into();163}164}165166let path = bevy_ecs_path();167168let user_generics = ast.generics.clone();169let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl();170let user_generics_with_world = {171let mut generics = ast.generics.clone();172generics.params.insert(0, parse_quote!('__w));173generics174};175let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) =176user_generics_with_world.split_for_impl();177let user_generics_with_world_and_state = {178let mut generics = ast.generics;179generics.params.insert(0, parse_quote!('__w));180generics.params.insert(1, parse_quote!('__s));181generics182};183let (184user_impl_generics_with_world_and_state,185user_ty_generics_with_world_and_state,186user_where_clauses_with_world_and_state,187) = user_generics_with_world_and_state.split_for_impl();188189let struct_name = ast.ident;190let read_only_struct_name = if attributes.is_mutable {191Ident::new(&format!("{struct_name}ReadOnly"), Span::call_site())192} else {193struct_name.clone()194};195196let item_struct_name = Ident::new(&format!("{struct_name}Item"), Span::call_site());197let read_only_item_struct_name = if attributes.is_mutable {198Ident::new(&format!("{struct_name}ReadOnlyItem"), Span::call_site())199} else {200item_struct_name.clone()201};202let contiguous_item_struct_name = if attributes.is_contiguous_mutable {203Ident::new(&format!("{struct_name}ContiguousItem"), Span::call_site())204} else {205item_struct_name.clone()206};207let read_only_contiguous_item_struct_name = if attributes.is_contiguous_immutable {208Ident::new(209&format!("{struct_name}ReadOnlyContiguousItem"),210Span::call_site(),211)212} else {213item_struct_name.clone()214};215216let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site());217let fetch_struct_name = ensure_no_collision(fetch_struct_name, tokens.clone());218let read_only_fetch_struct_name = if attributes.is_mutable {219let new_ident = Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site());220ensure_no_collision(new_ident, tokens.clone())221} else {222fetch_struct_name.clone()223};224225let marker_name =226ensure_no_collision(format_ident!("_world_query_derive_marker"), tokens.clone());227228// Generate a name for the state struct that doesn't conflict229// with the struct definition.230let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site());231let state_struct_name = ensure_no_collision(state_struct_name, tokens);232233let fields = match get_struct_fields(&ast.data, "derive(QueryData)") {234Ok(fields) => fields,235Err(e) => return e.into_compile_error().into(),236};237238let field_attrs = fields.iter().map(|f| f.attrs.clone()).collect();239let field_visibilities = fields.iter().map(|f| f.vis.clone()).collect();240let field_members = fields.members().collect();241let field_aliases = fields242.members()243.map(|m| format_ident!("field{}", m))244.collect();245let field_types: Vec<Type> = fields.iter().map(|f| f.ty.clone()).collect();246let read_only_field_types = field_types247.iter()248.map(|ty| parse_quote!(<#ty as #path::query::QueryData>::ReadOnly))249.collect();250251let derive_args = &attributes.derive_args;252// `#[derive()]` is valid syntax253let derive_macro_call = quote! { #[derive(#derive_args)] };254255let mutable_item_struct = item_struct(256&path,257fields,258&derive_macro_call,259&struct_name,260&visibility,261&item_struct_name,262&field_types,263&user_impl_generics_with_world_and_state,264&field_attrs,265&field_visibilities,266&field_members,267&user_ty_generics,268&user_ty_generics_with_world_and_state,269user_where_clauses_with_world_and_state,270);271let mutable_world_query_impl = world_query_impl(272&path,273&struct_name,274&visibility,275&fetch_struct_name,276&field_types,277&user_impl_generics,278&user_impl_generics_with_world,279&user_ty_generics,280&user_ty_generics_with_world,281&field_aliases,282&marker_name,283&state_struct_name,284user_where_clauses,285user_where_clauses_with_world,286);287288let (mutable_contiguous_item_struct, mutable_contiguous_impl) =289if attributes.is_contiguous_mutable {290let contiguous_item_struct = contiguous_item_struct(291&path,292fields,293&derive_macro_call,294&struct_name,295&visibility,296&contiguous_item_struct_name,297&field_types,298&user_impl_generics_with_world_and_state,299&field_attrs,300&field_visibilities,301&field_members,302&user_ty_generics,303&user_ty_generics_with_world_and_state,304user_where_clauses_with_world_and_state,305);306307let contiguous_impl = contiguous_query_data_impl(308&path,309&struct_name,310&contiguous_item_struct_name,311&field_types,312&user_impl_generics,313&user_ty_generics,314&user_ty_generics_with_world_and_state,315&field_members,316&field_aliases,317user_where_clauses,318);319320(contiguous_item_struct, contiguous_impl)321} else {322(quote! {}, quote! {})323};324325let (read_only_struct, read_only_impl) = if attributes.is_mutable {326// If the query is mutable, we need to generate a separate readonly version of some things327let readonly_item_struct = item_struct(328&path,329fields,330&derive_macro_call,331&read_only_struct_name,332&visibility,333&read_only_item_struct_name,334&read_only_field_types,335&user_impl_generics_with_world_and_state,336&field_attrs,337&field_visibilities,338&field_members,339&user_ty_generics,340&user_ty_generics_with_world_and_state,341user_where_clauses_with_world_and_state,342);343let readonly_world_query_impl = world_query_impl(344&path,345&read_only_struct_name,346&visibility,347&read_only_fetch_struct_name,348&read_only_field_types,349&user_impl_generics,350&user_impl_generics_with_world,351&user_ty_generics,352&user_ty_generics_with_world,353&field_aliases,354&marker_name,355&state_struct_name,356user_where_clauses,357user_where_clauses_with_world,358);359let read_only_structs = quote! {360#[doc = concat!(361"Automatically generated [`WorldQuery`](",362stringify!(#path),363"::query::WorldQuery) type for a read-only variant of [`",364stringify!(#struct_name),365"`]."366)]367#[automatically_derived]368#visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses {369#(370#[doc = "Automatically generated read-only field for accessing `"]371#[doc = stringify!(#field_types)]372#[doc = "`."]373#field_visibilities #field_members: #read_only_field_types,374)*375}376377#readonly_item_struct378};379(read_only_structs, readonly_world_query_impl)380} else {381(quote! {}, quote! {})382};383384let (read_only_contiguous_item_struct, read_only_contiguous_impl) =385if attributes.is_mutable && attributes.is_contiguous_immutable {386let contiguous_item_struct = contiguous_item_struct(387&path,388fields,389&derive_macro_call,390&read_only_struct_name,391&visibility,392&read_only_contiguous_item_struct_name,393&read_only_field_types,394&user_impl_generics_with_world_and_state,395&field_attrs,396&field_visibilities,397&field_members,398&user_ty_generics,399&user_ty_generics_with_world_and_state,400user_where_clauses_with_world_and_state,401);402403let contiguous_impl = contiguous_query_data_impl(404&path,405&read_only_struct_name,406&read_only_contiguous_item_struct_name,407&read_only_field_types,408&user_impl_generics,409&user_ty_generics,410&user_ty_generics_with_world_and_state,411&field_members,412&field_aliases,413user_where_clauses,414);415416(contiguous_item_struct, contiguous_impl)417} else {418(quote! {}, quote! {})419};420421let data_impl = {422let read_only_data_impl = if attributes.is_mutable {423quote! {424// SAFETY: we assert fields are readonly below425unsafe impl #user_impl_generics #path::query::QueryData426for #read_only_struct_name #user_ty_generics #user_where_clauses {427const IS_READ_ONLY: bool = true;428const IS_ARCHETYPAL: bool = true #(&& <#read_only_field_types as #path::query::QueryData>::IS_ARCHETYPAL)*;429type ReadOnly = #read_only_struct_name #user_ty_generics;430type Item<'__w, '__s> = #read_only_item_struct_name #user_ty_generics_with_world_and_state;431432fn shrink<'__wlong: '__wshort, '__wshort, '__s>(433item: Self::Item<'__wlong, '__s>434) -> Self::Item<'__wshort, '__s> {435#read_only_item_struct_name {436#(437#field_members: <#read_only_field_types>::shrink(item.#field_members),438)*439}440}441442fn provide_extra_access(443state: &mut Self::State,444access: &mut #path::query::Access,445available_access: &#path::query::Access,446) {447#(<#field_types>::provide_extra_access(&mut state.#field_aliases, access, available_access);)*448}449450/// SAFETY: we call `fetch` for each member that implements `Fetch`.451#[inline(always)]452unsafe fn fetch<'__w, '__s>(453_state: &'__s Self::State,454_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,455_entity: #path::entity::Entity,456_table_row: #path::storage::TableRow,457) -> Option<Self::Item<'__w, '__s>> {458Some(Self::Item {459#(#field_members: <#read_only_field_types>::fetch(&_state.#field_aliases, &mut _fetch.#field_aliases, _entity, _table_row)?,)*460})461}462463fn iter_access(464_state: &Self::State,465) -> impl ::core::iter::Iterator<Item = #path::query::EcsAccessType<'_>> {466::core::iter::empty() #(.chain(<#field_types>::iter_access(&_state.#field_aliases)))*467}468}469470impl #user_impl_generics #path::query::ReleaseStateQueryData471for #read_only_struct_name #user_ty_generics #user_where_clauses472// Make these HRTBs with an unused lifetime parameter to allow trivial constraints473// See https://github.com/rust-lang/rust/issues/48214474where #(for<'__a> #field_types: #path::query::QueryData<ReadOnly: #path::query::ReleaseStateQueryData>,)* {475fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> {476Self::Item {477#(#field_members: <#read_only_field_types>::release_state(_item.#field_members),)*478}479}480}481482impl #user_impl_generics #path::query::ArchetypeQueryData483for #read_only_struct_name #user_ty_generics #user_where_clauses484// Make these HRTBs with an unused lifetime parameter to allow trivial constraints485// See https://github.com/rust-lang/rust/issues/48214486where #(for<'__a> #field_types: #path::query::ArchetypeQueryData,)* {}487}488} else {489quote! {}490};491492let is_read_only = !attributes.is_mutable;493494quote! {495// SAFETY: we assert fields are readonly below496unsafe impl #user_impl_generics #path::query::QueryData497for #struct_name #user_ty_generics #user_where_clauses {498const IS_READ_ONLY: bool = #is_read_only;499const IS_ARCHETYPAL: bool = true #(&& <#field_types as #path::query::QueryData>::IS_ARCHETYPAL)*;500type ReadOnly = #read_only_struct_name #user_ty_generics;501type Item<'__w, '__s> = #item_struct_name #user_ty_generics_with_world_and_state;502503fn shrink<'__wlong: '__wshort, '__wshort, '__s>(504item: Self::Item<'__wlong, '__s>505) -> Self::Item<'__wshort, '__s> {506#item_struct_name {507#(508#field_members: <#field_types>::shrink(item.#field_members),509)*510}511}512513fn provide_extra_access(514state: &mut Self::State,515access: &mut #path::query::Access,516available_access: &#path::query::Access,517) {518#(<#field_types>::provide_extra_access(&mut state.#field_aliases, access, available_access);)*519}520521/// SAFETY: we call `fetch` for each member that implements `Fetch`.522#[inline(always)]523unsafe fn fetch<'__w, '__s>(524_state: &'__s Self::State,525_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,526_entity: #path::entity::Entity,527_table_row: #path::storage::TableRow,528) -> Option<Self::Item<'__w, '__s>> {529Some(Self::Item {530#(#field_members: <#field_types>::fetch(&_state.#field_aliases, &mut _fetch.#field_aliases, _entity, _table_row)?,)*531})532}533534fn iter_access(535_state: &Self::State,536) -> impl ::core::iter::Iterator<Item = #path::query::EcsAccessType<'_>> {537::core::iter::empty() #(.chain(<#field_types>::iter_access(&_state.#field_aliases)))*538}539}540541impl #user_impl_generics #path::query::ReleaseStateQueryData542for #struct_name #user_ty_generics #user_where_clauses543// Make these HRTBs with an unused lifetime parameter to allow trivial constraints544// See https://github.com/rust-lang/rust/issues/48214545where #(for<'__a> #field_types: #path::query::ReleaseStateQueryData,)* {546fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> {547Self::Item {548#(#field_members: <#field_types>::release_state(_item.#field_members),)*549}550}551}552553impl #user_impl_generics #path::query::ArchetypeQueryData554for #struct_name #user_ty_generics #user_where_clauses555// Make these HRTBs with an unused lifetime parameter to allow trivial constraints556// See https://github.com/rust-lang/rust/issues/48214557where #(for<'__a> #field_types: #path::query::ArchetypeQueryData,)* {}558559#read_only_data_impl560}561};562563let read_only_data_impl = quote! {564// SAFETY: we assert fields are readonly below565unsafe impl #user_impl_generics #path::query::ReadOnlyQueryData566for #read_only_struct_name #user_ty_generics #user_where_clauses {}567};568569let read_only_asserts = if attributes.is_mutable {570quote! {571// Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only.572// This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyQueryData`573// but to protect against future mistakes we assert the assoc type implements `ReadOnlyQueryData` anyway574#( assert_readonly::<#read_only_field_types>(); )*575}576} else {577quote! {578// Statically checks that the safety guarantee of `ReadOnlyQueryData` for `$fetch_struct_name` actually holds true.579// We need this to make sure that we don't compile `ReadOnlyQueryData` if our struct contains nested `QueryData`580// members that don't implement it. I.e.:581// ```582// #[derive(QueryData)]583// pub struct Foo { a: &'static mut MyComponent }584// ```585#( assert_readonly::<#field_types>(); )*586}587};588589let data_asserts = quote! {590#( assert_data::<#field_types>(); )*591};592593TokenStream::from(quote! {594#mutable_item_struct595596#read_only_struct597598#mutable_contiguous_item_struct599600#read_only_contiguous_item_struct601602const _: () = {603#[doc(hidden)]604#[doc = concat!(605"Automatically generated internal [`WorldQuery`](",606stringify!(#path),607"::query::WorldQuery) state type for [`",608stringify!(#struct_name),609"`], used for caching."610)]611#[automatically_derived]612#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {613#(#field_aliases: <#field_types as #path::query::WorldQuery>::State,)*614}615616#mutable_world_query_impl617618#read_only_impl619620#data_impl621622#read_only_data_impl623624#mutable_contiguous_impl625626#read_only_contiguous_impl627};628629#[allow(dead_code)]630const _: () = {631fn assert_readonly<T>()632where633T: #path::query::ReadOnlyQueryData,634{635}636637fn assert_data<T>()638where639T: #path::query::QueryData,640{641}642643// We generate a readonly assertion for every struct member.644fn assert_all #user_impl_generics_with_world () #user_where_clauses_with_world {645#read_only_asserts646#data_asserts647}648};649650// The original struct will most likely be left unused. As we don't want our users having651// to specify `#[allow(dead_code)]` for their custom queries, we are using this cursed652// workaround.653#[allow(dead_code)]654const _: () = {655fn dead_code_workaround #user_impl_generics (656q: #struct_name #user_ty_generics,657q2: #read_only_struct_name #user_ty_generics658) #user_where_clauses {659#(q.#field_members;)*660#(q2.#field_members;)*661}662};663})664}665666667