Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/macros/src/query_data.rs
6600 views
1
use bevy_macro_utils::ensure_no_collision;
2
use proc_macro::TokenStream;
3
use proc_macro2::{Ident, Span};
4
use quote::{format_ident, quote};
5
use syn::{
6
parse_macro_input, parse_quote, punctuated::Punctuated, token, token::Comma, Attribute, Data,
7
DataStruct, DeriveInput, Field, Index, Meta,
8
};
9
10
use crate::{
11
bevy_ecs_path,
12
world_query::{item_struct, world_query_impl},
13
};
14
15
#[derive(Default)]
16
struct QueryDataAttributes {
17
pub is_mutable: bool,
18
19
pub derive_args: Punctuated<Meta, Comma>,
20
}
21
22
static MUTABLE_ATTRIBUTE_NAME: &str = "mutable";
23
static DERIVE_ATTRIBUTE_NAME: &str = "derive";
24
25
mod field_attr_keywords {
26
syn::custom_keyword!(ignore);
27
}
28
29
pub static QUERY_DATA_ATTRIBUTE_NAME: &str = "query_data";
30
31
pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
32
let tokens = input.clone();
33
34
let ast = parse_macro_input!(input as DeriveInput);
35
let visibility = ast.vis;
36
37
let mut attributes = QueryDataAttributes::default();
38
for attr in &ast.attrs {
39
if attr
40
.path()
41
.get_ident()
42
.is_none_or(|ident| ident != QUERY_DATA_ATTRIBUTE_NAME)
43
{
44
continue;
45
}
46
47
let result = attr.parse_nested_meta(|meta| {
48
if meta.path.is_ident(MUTABLE_ATTRIBUTE_NAME) {
49
attributes.is_mutable = true;
50
if meta.input.peek(token::Paren) {
51
Err(meta.error(format_args!("`{MUTABLE_ATTRIBUTE_NAME}` does not take any arguments")))
52
} else {
53
Ok(())
54
}
55
} else if meta.path.is_ident(DERIVE_ATTRIBUTE_NAME) {
56
meta.parse_nested_meta(|meta| {
57
attributes.derive_args.push(Meta::Path(meta.path));
58
Ok(())
59
}).map_err(|_| {
60
meta.error(format_args!("`{DERIVE_ATTRIBUTE_NAME}` requires at least one argument"))
61
})
62
} else {
63
Err(meta.error(format_args!("invalid attribute, expected `{MUTABLE_ATTRIBUTE_NAME}` or `{DERIVE_ATTRIBUTE_NAME}`")))
64
}
65
});
66
67
if let Err(err) = result {
68
return err.to_compile_error().into();
69
}
70
}
71
72
let path = bevy_ecs_path();
73
74
let user_generics = ast.generics.clone();
75
let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl();
76
let user_generics_with_world = {
77
let mut generics = ast.generics.clone();
78
generics.params.insert(0, parse_quote!('__w));
79
generics
80
};
81
let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) =
82
user_generics_with_world.split_for_impl();
83
let user_generics_with_world_and_state = {
84
let mut generics = ast.generics;
85
generics.params.insert(0, parse_quote!('__w));
86
generics.params.insert(1, parse_quote!('__s));
87
generics
88
};
89
let (
90
user_impl_generics_with_world_and_state,
91
user_ty_generics_with_world_and_state,
92
user_where_clauses_with_world_and_state,
93
) = user_generics_with_world_and_state.split_for_impl();
94
95
let struct_name = ast.ident;
96
let read_only_struct_name = if attributes.is_mutable {
97
Ident::new(&format!("{struct_name}ReadOnly"), Span::call_site())
98
} else {
99
struct_name.clone()
100
};
101
102
let item_struct_name = Ident::new(&format!("{struct_name}Item"), Span::call_site());
103
let read_only_item_struct_name = if attributes.is_mutable {
104
Ident::new(&format!("{struct_name}ReadOnlyItem"), Span::call_site())
105
} else {
106
item_struct_name.clone()
107
};
108
109
let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site());
110
let fetch_struct_name = ensure_no_collision(fetch_struct_name, tokens.clone());
111
let read_only_fetch_struct_name = if attributes.is_mutable {
112
let new_ident = Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site());
113
ensure_no_collision(new_ident, tokens.clone())
114
} else {
115
fetch_struct_name.clone()
116
};
117
118
let marker_name =
119
ensure_no_collision(format_ident!("_world_query_derive_marker"), tokens.clone());
120
121
// Generate a name for the state struct that doesn't conflict
122
// with the struct definition.
123
let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site());
124
let state_struct_name = ensure_no_collision(state_struct_name, tokens);
125
126
let Data::Struct(DataStruct { fields, .. }) = &ast.data else {
127
return syn::Error::new(
128
Span::call_site(),
129
"#[derive(QueryData)]` only supports structs",
130
)
131
.into_compile_error()
132
.into();
133
};
134
135
let mut field_attrs = Vec::new();
136
let mut field_visibilities = Vec::new();
137
let mut field_idents = Vec::new();
138
let mut named_field_idents = Vec::new();
139
let mut field_types = Vec::new();
140
let mut read_only_field_types = Vec::new();
141
for (i, field) in fields.iter().enumerate() {
142
let attrs = match read_world_query_field_info(field) {
143
Ok(QueryDataFieldInfo { attrs }) => attrs,
144
Err(e) => return e.into_compile_error().into(),
145
};
146
147
let named_field_ident = field
148
.ident
149
.as_ref()
150
.cloned()
151
.unwrap_or_else(|| format_ident!("f{i}"));
152
let i = Index::from(i);
153
let field_ident = field
154
.ident
155
.as_ref()
156
.map_or(quote! { #i }, |i| quote! { #i });
157
field_idents.push(field_ident);
158
named_field_idents.push(named_field_ident);
159
field_attrs.push(attrs);
160
field_visibilities.push(field.vis.clone());
161
let field_ty = field.ty.clone();
162
field_types.push(quote!(#field_ty));
163
read_only_field_types.push(quote!(<#field_ty as #path::query::QueryData>::ReadOnly));
164
}
165
166
let derive_args = &attributes.derive_args;
167
// `#[derive()]` is valid syntax
168
let derive_macro_call = quote! { #[derive(#derive_args)] };
169
170
let mutable_item_struct = item_struct(
171
&path,
172
fields,
173
&derive_macro_call,
174
&struct_name,
175
&visibility,
176
&item_struct_name,
177
&field_types,
178
&user_impl_generics_with_world_and_state,
179
&field_attrs,
180
&field_visibilities,
181
&field_idents,
182
&user_ty_generics,
183
&user_ty_generics_with_world_and_state,
184
user_where_clauses_with_world_and_state,
185
);
186
let mutable_world_query_impl = world_query_impl(
187
&path,
188
&struct_name,
189
&visibility,
190
&fetch_struct_name,
191
&field_types,
192
&user_impl_generics,
193
&user_impl_generics_with_world,
194
&user_ty_generics,
195
&user_ty_generics_with_world,
196
&named_field_idents,
197
&marker_name,
198
&state_struct_name,
199
user_where_clauses,
200
user_where_clauses_with_world,
201
);
202
203
let (read_only_struct, read_only_impl) = if attributes.is_mutable {
204
// If the query is mutable, we need to generate a separate readonly version of some things
205
let readonly_item_struct = item_struct(
206
&path,
207
fields,
208
&derive_macro_call,
209
&read_only_struct_name,
210
&visibility,
211
&read_only_item_struct_name,
212
&read_only_field_types,
213
&user_impl_generics_with_world_and_state,
214
&field_attrs,
215
&field_visibilities,
216
&field_idents,
217
&user_ty_generics,
218
&user_ty_generics_with_world_and_state,
219
user_where_clauses_with_world_and_state,
220
);
221
let readonly_world_query_impl = world_query_impl(
222
&path,
223
&read_only_struct_name,
224
&visibility,
225
&read_only_fetch_struct_name,
226
&read_only_field_types,
227
&user_impl_generics,
228
&user_impl_generics_with_world,
229
&user_ty_generics,
230
&user_ty_generics_with_world,
231
&named_field_idents,
232
&marker_name,
233
&state_struct_name,
234
user_where_clauses,
235
user_where_clauses_with_world,
236
);
237
let read_only_structs = quote! {
238
#[doc = concat!(
239
"Automatically generated [`WorldQuery`](",
240
stringify!(#path),
241
"::query::WorldQuery) type for a read-only variant of [`",
242
stringify!(#struct_name),
243
"`]."
244
)]
245
#[automatically_derived]
246
#visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses {
247
#(
248
#[doc = "Automatically generated read-only field for accessing `"]
249
#[doc = stringify!(#field_types)]
250
#[doc = "`."]
251
#field_visibilities #named_field_idents: #read_only_field_types,
252
)*
253
}
254
255
#readonly_item_struct
256
};
257
(read_only_structs, readonly_world_query_impl)
258
} else {
259
(quote! {}, quote! {})
260
};
261
262
let data_impl = {
263
let read_only_data_impl = if attributes.is_mutable {
264
quote! {
265
/// SAFETY: we assert fields are readonly below
266
unsafe impl #user_impl_generics #path::query::QueryData
267
for #read_only_struct_name #user_ty_generics #user_where_clauses {
268
const IS_READ_ONLY: bool = true;
269
type ReadOnly = #read_only_struct_name #user_ty_generics;
270
type Item<'__w, '__s> = #read_only_item_struct_name #user_ty_generics_with_world_and_state;
271
272
fn shrink<'__wlong: '__wshort, '__wshort, '__s>(
273
item: Self::Item<'__wlong, '__s>
274
) -> Self::Item<'__wshort, '__s> {
275
#read_only_item_struct_name {
276
#(
277
#field_idents: <#read_only_field_types>::shrink(item.#field_idents),
278
)*
279
}
280
}
281
282
fn provide_extra_access(
283
state: &mut Self::State,
284
access: &mut #path::query::Access,
285
available_access: &#path::query::Access,
286
) {
287
#(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)*
288
}
289
290
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
291
#[inline(always)]
292
unsafe fn fetch<'__w, '__s>(
293
_state: &'__s Self::State,
294
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
295
_entity: #path::entity::Entity,
296
_table_row: #path::storage::TableRow,
297
) -> Self::Item<'__w, '__s> {
298
Self::Item {
299
#(#field_idents: <#read_only_field_types>::fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row),)*
300
}
301
}
302
}
303
304
impl #user_impl_generics #path::query::ReleaseStateQueryData
305
for #read_only_struct_name #user_ty_generics #user_where_clauses
306
// Make these HRTBs with an unused lifetime parameter to allow trivial constraints
307
// See https://github.com/rust-lang/rust/issues/48214
308
where #(for<'__a> #field_types: #path::query::QueryData<ReadOnly: #path::query::ReleaseStateQueryData>,)* {
309
fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> {
310
Self::Item {
311
#(#field_idents: <#read_only_field_types>::release_state(_item.#field_idents),)*
312
}
313
}
314
}
315
}
316
} else {
317
quote! {}
318
};
319
320
let is_read_only = !attributes.is_mutable;
321
322
quote! {
323
/// SAFETY: we assert fields are readonly below
324
unsafe impl #user_impl_generics #path::query::QueryData
325
for #struct_name #user_ty_generics #user_where_clauses {
326
const IS_READ_ONLY: bool = #is_read_only;
327
type ReadOnly = #read_only_struct_name #user_ty_generics;
328
type Item<'__w, '__s> = #item_struct_name #user_ty_generics_with_world_and_state;
329
330
fn shrink<'__wlong: '__wshort, '__wshort, '__s>(
331
item: Self::Item<'__wlong, '__s>
332
) -> Self::Item<'__wshort, '__s> {
333
#item_struct_name {
334
#(
335
#field_idents: <#field_types>::shrink(item.#field_idents),
336
)*
337
}
338
}
339
340
fn provide_extra_access(
341
state: &mut Self::State,
342
access: &mut #path::query::Access,
343
available_access: &#path::query::Access,
344
) {
345
#(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)*
346
}
347
348
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
349
#[inline(always)]
350
unsafe fn fetch<'__w, '__s>(
351
_state: &'__s Self::State,
352
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
353
_entity: #path::entity::Entity,
354
_table_row: #path::storage::TableRow,
355
) -> Self::Item<'__w, '__s> {
356
Self::Item {
357
#(#field_idents: <#field_types>::fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row),)*
358
}
359
}
360
}
361
362
impl #user_impl_generics #path::query::ReleaseStateQueryData
363
for #struct_name #user_ty_generics #user_where_clauses
364
// Make these HRTBs with an unused lifetime parameter to allow trivial constraints
365
// See https://github.com/rust-lang/rust/issues/48214
366
where #(for<'__a> #field_types: #path::query::ReleaseStateQueryData,)* {
367
fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> {
368
Self::Item {
369
#(#field_idents: <#field_types>::release_state(_item.#field_idents),)*
370
}
371
}
372
}
373
374
#read_only_data_impl
375
}
376
};
377
378
let read_only_data_impl = quote! {
379
/// SAFETY: we assert fields are readonly below
380
unsafe impl #user_impl_generics #path::query::ReadOnlyQueryData
381
for #read_only_struct_name #user_ty_generics #user_where_clauses {}
382
};
383
384
let read_only_asserts = if attributes.is_mutable {
385
quote! {
386
// Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only.
387
// This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyQueryData`
388
// but to protect against future mistakes we assert the assoc type implements `ReadOnlyQueryData` anyway
389
#( assert_readonly::<#read_only_field_types>(); )*
390
}
391
} else {
392
quote! {
393
// Statically checks that the safety guarantee of `ReadOnlyQueryData` for `$fetch_struct_name` actually holds true.
394
// We need this to make sure that we don't compile `ReadOnlyQueryData` if our struct contains nested `QueryData`
395
// members that don't implement it. I.e.:
396
// ```
397
// #[derive(QueryData)]
398
// pub struct Foo { a: &'static mut MyComponent }
399
// ```
400
#( assert_readonly::<#field_types>(); )*
401
}
402
};
403
404
let data_asserts = quote! {
405
#( assert_data::<#field_types>(); )*
406
};
407
408
TokenStream::from(quote! {
409
#mutable_item_struct
410
411
#read_only_struct
412
413
const _: () = {
414
#[doc(hidden)]
415
#[doc = concat!(
416
"Automatically generated internal [`WorldQuery`](",
417
stringify!(#path),
418
"::query::WorldQuery) state type for [`",
419
stringify!(#struct_name),
420
"`], used for caching."
421
)]
422
#[automatically_derived]
423
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
424
#(#named_field_idents: <#field_types as #path::query::WorldQuery>::State,)*
425
}
426
427
#mutable_world_query_impl
428
429
#read_only_impl
430
431
#data_impl
432
433
#read_only_data_impl
434
};
435
436
#[allow(dead_code)]
437
const _: () = {
438
fn assert_readonly<T>()
439
where
440
T: #path::query::ReadOnlyQueryData,
441
{
442
}
443
444
fn assert_data<T>()
445
where
446
T: #path::query::QueryData,
447
{
448
}
449
450
// We generate a readonly assertion for every struct member.
451
fn assert_all #user_impl_generics_with_world () #user_where_clauses_with_world {
452
#read_only_asserts
453
#data_asserts
454
}
455
};
456
457
// The original struct will most likely be left unused. As we don't want our users having
458
// to specify `#[allow(dead_code)]` for their custom queries, we are using this cursed
459
// workaround.
460
#[allow(dead_code)]
461
const _: () = {
462
fn dead_code_workaround #user_impl_generics (
463
q: #struct_name #user_ty_generics,
464
q2: #read_only_struct_name #user_ty_generics
465
) #user_where_clauses {
466
#(q.#field_idents;)*
467
#(q2.#field_idents;)*
468
}
469
};
470
})
471
}
472
473
struct QueryDataFieldInfo {
474
/// All field attributes except for `query_data` ones.
475
attrs: Vec<Attribute>,
476
}
477
478
fn read_world_query_field_info(field: &Field) -> syn::Result<QueryDataFieldInfo> {
479
let mut attrs = Vec::new();
480
for attr in &field.attrs {
481
if attr
482
.path()
483
.get_ident()
484
.is_some_and(|ident| ident == QUERY_DATA_ATTRIBUTE_NAME)
485
{
486
return Err(syn::Error::new_spanned(
487
attr,
488
"#[derive(QueryData)] does not support field attributes.",
489
));
490
}
491
attrs.push(attr.clone());
492
}
493
494
Ok(QueryDataFieldInfo { attrs })
495
}
496
497