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