Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/macros/src/component.rs
9368 views
1
use proc_macro::TokenStream;
2
use proc_macro2::{Span, TokenStream as TokenStream2};
3
use quote::{format_ident, quote, ToTokens};
4
use std::collections::HashSet;
5
use syn::{
6
braced, parenthesized,
7
parse::Parse,
8
parse_macro_input, parse_quote,
9
punctuated::Punctuated,
10
spanned::Spanned,
11
token::{Brace, Comma, Paren},
12
Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprPath, Field, Fields, Ident,
13
LitStr, Member, Path, Result, Token, Type, Visibility,
14
};
15
16
pub fn derive_resource(input: TokenStream) -> TokenStream {
17
let mut ast = parse_macro_input!(input as DeriveInput);
18
let bevy_ecs_path: Path = crate::bevy_ecs_path();
19
20
// We want to raise a compile time error when the generic lifetimes
21
// are not bound to 'static lifetime
22
let non_static_lifetime_error = ast
23
.generics
24
.lifetimes()
25
.filter(|lifetime| !lifetime.bounds.iter().any(|bound| bound.ident == "static"))
26
.map(|param| syn::Error::new(param.span(), "Lifetimes must be 'static"))
27
.reduce(|mut err_acc, err| {
28
err_acc.combine(err);
29
err_acc
30
});
31
if let Some(err) = non_static_lifetime_error {
32
return err.into_compile_error().into();
33
}
34
35
// Implement the Component trait.
36
let map_entities = map_entities(
37
&ast.data,
38
&bevy_ecs_path,
39
Ident::new("this", Span::call_site()),
40
false,
41
false,
42
None
43
).map(|map_entities_impl| quote! {
44
fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {
45
use #bevy_ecs_path::entity::MapEntities;
46
#map_entities_impl
47
}
48
});
49
50
let storage = storage_path(&bevy_ecs_path, StorageTy::Table);
51
52
let on_add_path = None;
53
let on_remove_path = None;
54
let on_insert_path = None;
55
let on_replace_path = None;
56
let on_despawn_path = None;
57
58
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);
59
let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);
60
let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);
61
let on_replace =
62
hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);
63
let on_despawn =
64
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);
65
66
ast.generics
67
.make_where_clause()
68
.predicates
69
.push(parse_quote! { Self: Send + Sync + 'static });
70
71
let struct_name = &ast.ident;
72
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
73
74
let mut register_required = Vec::with_capacity(1);
75
// We add the component_id existence check here to avoid recursive init during required components initialization.
76
register_required.push(quote! {
77
let resource_component_id = if let Some(id) = required_components.components_registrator().component_id::<#struct_name #type_generics>() {
78
id
79
} else {
80
required_components.components_registrator().register_component::<#struct_name #type_generics>()
81
};
82
required_components.register_required::<#bevy_ecs_path::resource::IsResource>(move || #bevy_ecs_path::resource::IsResource::new(resource_component_id));
83
});
84
85
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
86
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
87
let component_derive_token_stream = TokenStream::from(quote! {
88
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
89
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
90
type Mutability = #bevy_ecs_path::component::Mutable;
91
fn register_required_components(
92
_requiree: #bevy_ecs_path::component::ComponentId,
93
required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,
94
) {
95
#(#register_required)*
96
}
97
98
#on_add
99
#on_insert
100
#on_replace
101
#on_remove
102
#on_despawn
103
104
fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {
105
#bevy_ecs_path::component::ComponentCloneBehavior::Default
106
}
107
108
#map_entities
109
110
fn relationship_accessor() -> Option<#bevy_ecs_path::relationship::ComponentRelationshipAccessor<Self>> {
111
None
112
}
113
}
114
});
115
116
// Implement the Resource trait.
117
let resource_impl_token_stream = TokenStream::from(quote! {
118
impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause {
119
}
120
});
121
122
resource_impl_token_stream
123
.into_iter()
124
.chain(component_derive_token_stream)
125
.collect()
126
}
127
128
/// Component derive syntax is documented on both the macro and the trait.
129
pub fn derive_component(input: TokenStream) -> TokenStream {
130
let mut ast = parse_macro_input!(input as DeriveInput);
131
let bevy_ecs_path: Path = crate::bevy_ecs_path();
132
133
let attrs = match parse_component_attr(&ast) {
134
Ok(attrs) => attrs,
135
Err(e) => return e.into_compile_error().into(),
136
};
137
138
let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) {
139
Ok(value) => value,
140
Err(err) => err.into_compile_error().into(),
141
};
142
let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) {
143
Ok(value) => value,
144
Err(err) => err.into_compile_error().into(),
145
};
146
147
let map_entities = map_entities(
148
&ast.data,
149
&bevy_ecs_path,
150
Ident::new("this", Span::call_site()),
151
relationship.is_some(),
152
relationship_target.is_some(),
153
attrs.map_entities
154
).map(|map_entities_impl| quote! {
155
fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {
156
use #bevy_ecs_path::entity::MapEntities;
157
#map_entities_impl
158
}
159
});
160
161
let storage = storage_path(&bevy_ecs_path, attrs.storage);
162
163
let on_add_path = attrs
164
.on_add
165
.map(|path| path.to_token_stream(&bevy_ecs_path));
166
let on_remove_path = attrs
167
.on_remove
168
.map(|path| path.to_token_stream(&bevy_ecs_path));
169
170
let on_insert_path = if relationship.is_some() {
171
if attrs.on_insert.is_some() {
172
return syn::Error::new(
173
ast.span(),
174
"Custom on_insert hooks are not supported as relationships already define an on_insert hook",
175
)
176
.into_compile_error()
177
.into();
178
}
179
180
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert))
181
} else {
182
attrs
183
.on_insert
184
.map(|path| path.to_token_stream(&bevy_ecs_path))
185
};
186
187
let on_replace_path = if relationship.is_some() {
188
if attrs.on_replace.is_some() {
189
return syn::Error::new(
190
ast.span(),
191
"Custom on_replace hooks are not supported as Relationships already define an on_replace hook",
192
)
193
.into_compile_error()
194
.into();
195
}
196
197
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_replace))
198
} else if attrs.relationship_target.is_some() {
199
if attrs.on_replace.is_some() {
200
return syn::Error::new(
201
ast.span(),
202
"Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook",
203
)
204
.into_compile_error()
205
.into();
206
}
207
208
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_replace))
209
} else {
210
attrs
211
.on_replace
212
.map(|path| path.to_token_stream(&bevy_ecs_path))
213
};
214
215
let on_despawn_path = if attrs
216
.relationship_target
217
.is_some_and(|target| target.linked_spawn)
218
{
219
if attrs.on_despawn.is_some() {
220
return syn::Error::new(
221
ast.span(),
222
"Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute",
223
)
224
.into_compile_error()
225
.into();
226
}
227
228
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn))
229
} else {
230
attrs
231
.on_despawn
232
.map(|path| path.to_token_stream(&bevy_ecs_path))
233
};
234
235
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);
236
let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);
237
let on_replace =
238
hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);
239
let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);
240
let on_despawn =
241
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);
242
243
ast.generics
244
.make_where_clause()
245
.predicates
246
.push(parse_quote! { Self: Send + Sync + 'static });
247
248
let requires = &attrs.requires;
249
let mut register_required = Vec::with_capacity(attrs.requires.iter().len());
250
if let Some(requires) = requires {
251
for require in requires {
252
let ident = &require.path;
253
let constructor = match &require.func {
254
Some(func) => quote! { || { let x: #ident = (#func)().into(); x } },
255
None => quote! { <#ident as Default>::default },
256
};
257
register_required.push(quote! {
258
required_components.register_required::<#ident>(#constructor);
259
});
260
}
261
}
262
let struct_name = &ast.ident;
263
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
264
265
let required_component_docs = attrs.requires.map(|r| {
266
let paths = r
267
.iter()
268
.map(|r| format!("[`{}`]", r.path.to_token_stream()))
269
.collect::<Vec<_>>()
270
.join(", ");
271
let doc = format!("**Required Components**: {paths}. \n\n A component's Required Components are inserted whenever it is inserted. Note that this will also insert the required components _of_ the required components, recursively, in depth-first order.");
272
quote! {
273
#[doc = #doc]
274
}
275
});
276
277
let mutable_type = (attrs.immutable || relationship.is_some())
278
.then_some(quote! { #bevy_ecs_path::component::Immutable })
279
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
280
281
let clone_behavior = if relationship_target.is_some() || relationship.is_some() {
282
quote!(
283
use #bevy_ecs_path::relationship::{
284
RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect,
285
RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy
286
};
287
(&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
288
)
289
} else if let Some(behavior) = attrs.clone_behavior {
290
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
291
} else {
292
quote!(
293
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
294
(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
295
)
296
};
297
298
let relationship_accessor = if (relationship.is_some() || relationship_target.is_some())
299
&& let Data::Struct(DataStruct {
300
fields,
301
struct_token,
302
..
303
}) = &ast.data
304
&& let Ok(field) = relationship_field(fields, "Relationship", struct_token.span())
305
{
306
let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);
307
if relationship.is_some() {
308
quote! {
309
Some(
310
// Safety: we pass valid offset of a field containing Entity (obtained via offset_off!)
311
unsafe {
312
#bevy_ecs_path::relationship::ComponentRelationshipAccessor::<Self>::relationship(
313
::core::mem::offset_of!(Self, #relationship_member)
314
)
315
}
316
)
317
}
318
} else {
319
quote! {
320
Some(#bevy_ecs_path::relationship::ComponentRelationshipAccessor::<Self>::relationship_target())
321
}
322
}
323
} else {
324
quote! {None}
325
};
326
327
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
328
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
329
TokenStream::from(quote! {
330
#required_component_docs
331
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
332
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
333
type Mutability = #mutable_type;
334
fn register_required_components(
335
_requiree: #bevy_ecs_path::component::ComponentId,
336
required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator,
337
) {
338
#(#register_required)*
339
}
340
341
#on_add
342
#on_insert
343
#on_replace
344
#on_remove
345
#on_despawn
346
347
fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {
348
#clone_behavior
349
}
350
351
#map_entities
352
353
fn relationship_accessor() -> Option<#bevy_ecs_path::relationship::ComponentRelationshipAccessor<Self>> {
354
#relationship_accessor
355
}
356
}
357
358
#relationship
359
360
#relationship_target
361
})
362
}
363
364
const ENTITIES: &str = "entities";
365
366
pub(crate) fn map_entities(
367
data: &Data,
368
bevy_ecs_path: &Path,
369
self_ident: Ident,
370
is_relationship: bool,
371
is_relationship_target: bool,
372
map_entities_attr: Option<MapEntitiesAttributeKind>,
373
) -> Option<TokenStream2> {
374
if let Some(map_entities_override) = map_entities_attr {
375
let map_entities_tokens = map_entities_override.to_token_stream(bevy_ecs_path);
376
return Some(quote!(
377
#map_entities_tokens(#self_ident, mapper)
378
));
379
}
380
381
match data {
382
Data::Struct(DataStruct { fields, .. }) => {
383
let mut map = Vec::with_capacity(fields.len());
384
385
let relationship = if is_relationship || is_relationship_target {
386
relationship_field(fields, "MapEntities", fields.span()).ok()
387
} else {
388
None
389
};
390
fields
391
.iter()
392
.enumerate()
393
.filter(|(_, field)| {
394
field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))
395
|| relationship.is_some_and(|relationship| relationship == *field)
396
})
397
.for_each(|(index, field)| {
398
let field_member = field
399
.ident
400
.clone()
401
.map_or(Member::from(index), Member::Named);
402
403
map.push(quote!(#self_ident.#field_member.map_entities(mapper);));
404
});
405
if map.is_empty() {
406
return None;
407
};
408
Some(quote!(
409
#(#map)*
410
))
411
}
412
Data::Enum(DataEnum { variants, .. }) => {
413
let mut map = Vec::with_capacity(variants.len());
414
415
for variant in variants.iter() {
416
let field_members = variant
417
.fields
418
.iter()
419
.enumerate()
420
.filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES)))
421
.map(|(index, field)| {
422
field
423
.ident
424
.clone()
425
.map_or(Member::from(index), Member::Named)
426
})
427
.collect::<Vec<_>>();
428
429
let ident = &variant.ident;
430
let field_idents = field_members
431
.iter()
432
.map(|member| format_ident!("__self{}", member))
433
.collect::<Vec<_>>();
434
435
map.push(
436
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
437
#(#field_idents.map_entities(mapper);)*
438
}),
439
);
440
}
441
442
if map.is_empty() {
443
return None;
444
};
445
446
Some(quote!(
447
match #self_ident {
448
#(#map,)*
449
_ => {}
450
}
451
))
452
}
453
Data::Union(_) => None,
454
}
455
}
456
457
pub const COMPONENT: &str = "component";
458
pub const STORAGE: &str = "storage";
459
pub const REQUIRE: &str = "require";
460
pub const RELATIONSHIP: &str = "relationship";
461
pub const RELATIONSHIP_TARGET: &str = "relationship_target";
462
463
pub const ON_ADD: &str = "on_add";
464
pub const ON_INSERT: &str = "on_insert";
465
pub const ON_REPLACE: &str = "on_replace";
466
pub const ON_REMOVE: &str = "on_remove";
467
pub const ON_DESPAWN: &str = "on_despawn";
468
pub const MAP_ENTITIES: &str = "map_entities";
469
470
pub const IMMUTABLE: &str = "immutable";
471
pub const CLONE_BEHAVIOR: &str = "clone_behavior";
472
473
/// All allowed attribute value expression kinds for component hooks.
474
/// This doesn't simply use general expressions because of conflicting needs:
475
/// - we want to be able to use `Self` & generic parameters in paths
476
/// - call expressions producing a closure need to be wrapped in a function
477
/// to turn them into function pointers, which prevents access to the outer generic params
478
#[derive(Debug)]
479
enum HookAttributeKind {
480
/// expressions like function or struct names
481
///
482
/// structs will throw compile errors on the code generation so this is safe
483
Path(ExprPath),
484
/// function call like expressions
485
Call(ExprCall),
486
}
487
488
impl HookAttributeKind {
489
fn parse(
490
input: syn::parse::ParseStream,
491
default_hook_path: impl FnOnce() -> ExprPath,
492
) -> Result<Self> {
493
if input.peek(Token![=]) {
494
input.parse::<Token![=]>()?;
495
input.parse::<Expr>().and_then(Self::from_expr)
496
} else {
497
Ok(Self::Path(default_hook_path()))
498
}
499
}
500
501
fn from_expr(value: Expr) -> Result<Self> {
502
match value {
503
Expr::Path(path) => Ok(HookAttributeKind::Path(path)),
504
Expr::Call(call) => Ok(HookAttributeKind::Call(call)),
505
// throw meaningful error on all other expressions
506
_ => Err(syn::Error::new(
507
value.span(),
508
[
509
"Not supported in this position, please use one of the following:",
510
"- path to function",
511
"- call to function yielding closure",
512
]
513
.join("\n"),
514
)),
515
}
516
}
517
518
fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {
519
match self {
520
HookAttributeKind::Path(path) => path.to_token_stream(),
521
HookAttributeKind::Call(call) => {
522
quote!({
523
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {
524
(#call)(world, ctx)
525
}
526
_internal_hook
527
})
528
}
529
}
530
}
531
}
532
533
#[derive(Debug)]
534
pub(super) enum MapEntitiesAttributeKind {
535
/// expressions like function or struct names
536
///
537
/// structs will throw compile errors on the code generation so this is safe
538
Path(ExprPath),
539
/// When no value is specified
540
Default,
541
}
542
543
impl MapEntitiesAttributeKind {
544
fn from_expr(value: Expr) -> Result<Self> {
545
match value {
546
Expr::Path(path) => Ok(Self::Path(path)),
547
// throw meaningful error on all other expressions
548
_ => Err(syn::Error::new(
549
value.span(),
550
[
551
"Not supported in this position, please use one of the following:",
552
"- path to function",
553
"- nothing to default to MapEntities implementation",
554
]
555
.join("\n"),
556
)),
557
}
558
}
559
560
fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {
561
match self {
562
MapEntitiesAttributeKind::Path(path) => path.to_token_stream(),
563
MapEntitiesAttributeKind::Default => {
564
quote!(
565
<Self as #bevy_ecs_path::entity::MapEntities>::map_entities
566
)
567
}
568
}
569
}
570
}
571
572
impl Parse for MapEntitiesAttributeKind {
573
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
574
if input.peek(Token![=]) {
575
input.parse::<Token![=]>()?;
576
input.parse::<Expr>().and_then(Self::from_expr)
577
} else {
578
Ok(Self::Default)
579
}
580
}
581
}
582
583
struct Attrs {
584
storage: StorageTy,
585
requires: Option<Punctuated<Require, Comma>>,
586
on_add: Option<HookAttributeKind>,
587
on_insert: Option<HookAttributeKind>,
588
on_replace: Option<HookAttributeKind>,
589
on_remove: Option<HookAttributeKind>,
590
on_despawn: Option<HookAttributeKind>,
591
relationship: Option<Relationship>,
592
relationship_target: Option<RelationshipTarget>,
593
immutable: bool,
594
clone_behavior: Option<Expr>,
595
map_entities: Option<MapEntitiesAttributeKind>,
596
}
597
598
#[derive(Clone, Copy)]
599
enum StorageTy {
600
Table,
601
SparseSet,
602
}
603
604
struct Require {
605
path: Path,
606
func: Option<TokenStream2>,
607
}
608
609
struct Relationship {
610
relationship_target: Type,
611
allow_self_referential: bool,
612
}
613
614
struct RelationshipTarget {
615
relationship: Type,
616
linked_spawn: bool,
617
}
618
619
// values for `storage` attribute
620
const TABLE: &str = "Table";
621
const SPARSE_SET: &str = "SparseSet";
622
623
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
624
let mut attrs = Attrs {
625
storage: StorageTy::Table,
626
on_add: None,
627
on_insert: None,
628
on_replace: None,
629
on_remove: None,
630
on_despawn: None,
631
requires: None,
632
relationship: None,
633
relationship_target: None,
634
immutable: false,
635
clone_behavior: None,
636
map_entities: None,
637
};
638
639
let mut require_paths = HashSet::new();
640
for attr in ast.attrs.iter() {
641
if attr.path().is_ident(COMPONENT) {
642
attr.parse_nested_meta(|nested| {
643
if nested.path.is_ident(STORAGE) {
644
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
645
s if s == TABLE => StorageTy::Table,
646
s if s == SPARSE_SET => StorageTy::SparseSet,
647
s => {
648
return Err(nested.error(format!(
649
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
650
)));
651
}
652
};
653
Ok(())
654
} else if nested.path.is_ident(ON_ADD) {
655
attrs.on_add = Some(HookAttributeKind::parse(nested.input, || {
656
parse_quote! { Self::on_add }
657
})?);
658
Ok(())
659
} else if nested.path.is_ident(ON_INSERT) {
660
attrs.on_insert = Some(HookAttributeKind::parse(nested.input, || {
661
parse_quote! { Self::on_insert }
662
})?);
663
Ok(())
664
} else if nested.path.is_ident(ON_REPLACE) {
665
attrs.on_replace = Some(HookAttributeKind::parse(nested.input, || {
666
parse_quote! { Self::on_replace }
667
})?);
668
Ok(())
669
} else if nested.path.is_ident(ON_REMOVE) {
670
attrs.on_remove = Some(HookAttributeKind::parse(nested.input, || {
671
parse_quote! { Self::on_remove }
672
})?);
673
Ok(())
674
} else if nested.path.is_ident(ON_DESPAWN) {
675
attrs.on_despawn = Some(HookAttributeKind::parse(nested.input, || {
676
parse_quote! { Self::on_despawn }
677
})?);
678
Ok(())
679
} else if nested.path.is_ident(IMMUTABLE) {
680
attrs.immutable = true;
681
Ok(())
682
} else if nested.path.is_ident(CLONE_BEHAVIOR) {
683
attrs.clone_behavior = Some(nested.value()?.parse()?);
684
Ok(())
685
} else if nested.path.is_ident(MAP_ENTITIES) {
686
attrs.map_entities = Some(nested.input.parse::<MapEntitiesAttributeKind>()?);
687
Ok(())
688
} else {
689
Err(nested.error("Unsupported attribute"))
690
}
691
})?;
692
} else if attr.path().is_ident(REQUIRE) {
693
let punctuated =
694
attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;
695
for require in punctuated.iter() {
696
if !require_paths.insert(require.path.to_token_stream().to_string()) {
697
return Err(syn::Error::new(
698
require.path.span(),
699
"Duplicate required components are not allowed.",
700
));
701
}
702
}
703
if let Some(current) = &mut attrs.requires {
704
current.extend(punctuated);
705
} else {
706
attrs.requires = Some(punctuated);
707
}
708
} else if attr.path().is_ident(RELATIONSHIP) {
709
let relationship = attr.parse_args::<Relationship>()?;
710
attrs.relationship = Some(relationship);
711
} else if attr.path().is_ident(RELATIONSHIP_TARGET) {
712
let relationship_target = attr.parse_args::<RelationshipTarget>()?;
713
attrs.relationship_target = Some(relationship_target);
714
}
715
}
716
717
if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {
718
return Err(syn::Error::new(
719
attrs.clone_behavior.span(),
720
"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",
721
));
722
}
723
724
Ok(attrs)
725
}
726
727
impl Parse for Require {
728
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
729
let mut path = input.parse::<Path>()?;
730
let mut last_segment_is_lower = false;
731
let mut is_constructor_call = false;
732
733
// Use the case of the type name to check if it's an enum
734
// This doesn't match everything that can be an enum according to the rust spec
735
// but it matches what clippy is OK with
736
let is_enum = {
737
let mut first_chars = path
738
.segments
739
.iter()
740
.rev()
741
.filter_map(|s| s.ident.to_string().chars().next());
742
if let Some(last) = first_chars.next() {
743
if last.is_uppercase() {
744
if let Some(last) = first_chars.next() {
745
last.is_uppercase()
746
} else {
747
false
748
}
749
} else {
750
last_segment_is_lower = true;
751
false
752
}
753
} else {
754
false
755
}
756
};
757
758
let func = if input.peek(Token![=]) {
759
// If there is an '=', then this is a "function style" require
760
input.parse::<Token![=]>()?;
761
let expr: Expr = input.parse()?;
762
Some(quote!(|| #expr ))
763
} else if input.peek(Brace) {
764
// This is a "value style" named-struct-like require
765
let content;
766
braced!(content in input);
767
let content = content.parse::<TokenStream2>()?;
768
Some(quote!(|| #path { #content }))
769
} else if input.peek(Paren) {
770
// This is a "value style" tuple-struct-like require
771
let content;
772
parenthesized!(content in input);
773
let content = content.parse::<TokenStream2>()?;
774
is_constructor_call = last_segment_is_lower;
775
Some(quote!(|| #path (#content)))
776
} else if is_enum {
777
// if this is an enum, then it is an inline enum component declaration
778
Some(quote!(|| #path))
779
} else {
780
// if this isn't any of the above, then it is a component ident, which will use Default
781
None
782
};
783
if is_enum || is_constructor_call {
784
path.segments.pop();
785
path.segments.pop_punct();
786
}
787
Ok(Require { path, func })
788
}
789
}
790
791
fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
792
let storage_type = match ty {
793
StorageTy::Table => Ident::new("Table", Span::call_site()),
794
StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),
795
};
796
797
quote! { #bevy_ecs_path::component::StorageType::#storage_type }
798
}
799
800
fn hook_register_function_call(
801
bevy_ecs_path: &Path,
802
hook: TokenStream2,
803
function: Option<TokenStream2>,
804
) -> Option<TokenStream2> {
805
function.map(|meta| {
806
quote! {
807
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
808
::core::option::Option::Some(#meta)
809
}
810
}
811
})
812
}
813
814
mod kw {
815
syn::custom_keyword!(relationship_target);
816
syn::custom_keyword!(relationship);
817
syn::custom_keyword!(linked_spawn);
818
syn::custom_keyword!(allow_self_referential);
819
}
820
821
impl Parse for Relationship {
822
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
823
let mut relationship_target: Option<Type> = None;
824
let mut allow_self_referential: bool = false;
825
826
while !input.is_empty() {
827
let lookahead = input.lookahead1();
828
if lookahead.peek(kw::allow_self_referential) {
829
input.parse::<kw::allow_self_referential>()?;
830
allow_self_referential = true;
831
} else if lookahead.peek(kw::relationship_target) {
832
input.parse::<kw::relationship_target>()?;
833
input.parse::<Token![=]>()?;
834
relationship_target = Some(input.parse()?);
835
} else {
836
return Err(lookahead.error());
837
}
838
if !input.is_empty() {
839
input.parse::<Token![,]>()?;
840
}
841
}
842
Ok(Relationship {
843
relationship_target: relationship_target.ok_or_else(|| {
844
syn::Error::new(input.span(), "Missing `relationship_target = X` attribute")
845
})?,
846
allow_self_referential,
847
})
848
}
849
}
850
851
impl Parse for RelationshipTarget {
852
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
853
let mut relationship: Option<Type> = None;
854
let mut linked_spawn: bool = false;
855
856
while !input.is_empty() {
857
let lookahead = input.lookahead1();
858
if lookahead.peek(kw::linked_spawn) {
859
input.parse::<kw::linked_spawn>()?;
860
linked_spawn = true;
861
} else if lookahead.peek(kw::relationship) {
862
input.parse::<kw::relationship>()?;
863
input.parse::<Token![=]>()?;
864
relationship = Some(input.parse()?);
865
} else {
866
return Err(lookahead.error());
867
}
868
if !input.is_empty() {
869
input.parse::<Token![,]>()?;
870
}
871
}
872
Ok(RelationshipTarget {
873
relationship: relationship.ok_or_else(|| {
874
syn::Error::new(input.span(), "Missing `relationship = X` attribute")
875
})?,
876
linked_spawn,
877
})
878
}
879
}
880
881
fn derive_relationship(
882
ast: &DeriveInput,
883
attrs: &Attrs,
884
bevy_ecs_path: &Path,
885
) -> Result<Option<TokenStream2>> {
886
let Some(relationship) = &attrs.relationship else {
887
return Ok(None);
888
};
889
let Data::Struct(DataStruct {
890
fields,
891
struct_token,
892
..
893
}) = &ast.data
894
else {
895
return Err(syn::Error::new(
896
ast.span(),
897
"Relationship can only be derived for structs.",
898
));
899
};
900
let field = relationship_field(fields, "Relationship", struct_token.span())?;
901
902
let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);
903
let members = fields
904
.members()
905
.filter(|member| member != &relationship_member);
906
907
let struct_name = &ast.ident;
908
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
909
910
let relationship_target = &relationship.relationship_target;
911
let allow_self_referential = relationship.allow_self_referential;
912
913
Ok(Some(quote! {
914
impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause {
915
type RelationshipTarget = #relationship_target;
916
const ALLOW_SELF_REFERENTIAL: bool = #allow_self_referential;
917
918
#[inline(always)]
919
fn get(&self) -> #bevy_ecs_path::entity::Entity {
920
self.#relationship_member
921
}
922
923
#[inline]
924
fn from(entity: #bevy_ecs_path::entity::Entity) -> Self {
925
Self {
926
#(#members: ::core::default::Default::default(),)*
927
#relationship_member: entity
928
}
929
}
930
931
#[inline]
932
fn set_risky(&mut self, entity: #bevy_ecs_path::entity::Entity) {
933
self.#relationship_member = entity;
934
}
935
}
936
}))
937
}
938
939
fn derive_relationship_target(
940
ast: &DeriveInput,
941
attrs: &Attrs,
942
bevy_ecs_path: &Path,
943
) -> Result<Option<TokenStream2>> {
944
let Some(relationship_target) = &attrs.relationship_target else {
945
return Ok(None);
946
};
947
948
let Data::Struct(DataStruct {
949
fields,
950
struct_token,
951
..
952
}) = &ast.data
953
else {
954
return Err(syn::Error::new(
955
ast.span(),
956
"RelationshipTarget can only be derived for structs.",
957
));
958
};
959
let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?;
960
961
if field.vis != Visibility::Inherited {
962
return Err(syn::Error::new(field.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships."));
963
}
964
let collection = &field.ty;
965
let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);
966
967
let members = fields
968
.members()
969
.filter(|member| member != &relationship_member);
970
971
let relationship = &relationship_target.relationship;
972
let struct_name = &ast.ident;
973
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
974
let linked_spawn = relationship_target.linked_spawn;
975
Ok(Some(quote! {
976
impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause {
977
const LINKED_SPAWN: bool = #linked_spawn;
978
type Relationship = #relationship;
979
type Collection = #collection;
980
981
#[inline]
982
fn collection(&self) -> &Self::Collection {
983
&self.#relationship_member
984
}
985
986
#[inline]
987
fn collection_mut_risky(&mut self) -> &mut Self::Collection {
988
&mut self.#relationship_member
989
}
990
991
#[inline]
992
fn from_collection_risky(collection: Self::Collection) -> Self {
993
Self {
994
#(#members: ::core::default::Default::default(),)*
995
#relationship_member: collection
996
}
997
}
998
}
999
}))
1000
}
1001
1002
/// Returns the field with the `#[relationship]` attribute, the only field if unnamed,
1003
/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`.
1004
fn relationship_field<'a>(
1005
fields: &'a Fields,
1006
derive: &'static str,
1007
span: Span,
1008
) -> Result<&'a Field> {
1009
match fields {
1010
Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()),
1011
Fields::Named(fields) => fields.named.iter().find(|field| {
1012
field
1013
.attrs
1014
.iter()
1015
.any(|attr| attr.path().is_ident(RELATIONSHIP))
1016
}).ok_or(syn::Error::new(
1017
span,
1018
format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].")
1019
)),
1020
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(fields.unnamed.first().unwrap()),
1021
Fields::Unnamed(fields) => fields.unnamed.iter().find(|field| {
1022
field
1023
.attrs
1024
.iter()
1025
.any(|attr| attr.path().is_ident(RELATIONSHIP))
1026
})
1027
.ok_or(syn::Error::new(
1028
span,
1029
format!("{derive} derive expected unnamed structs with one field or with a field annotated with #[relationship]."),
1030
)),
1031
Fields::Unit => Err(syn::Error::new(
1032
span,
1033
format!("{derive} derive expected named or unnamed struct, found unit struct."),
1034
)),
1035
}
1036
}
1037
1038