bevy/crates/bevy_ecs/macros/src/component.rs
Joona Aalto 38c3423693
Event Split: Event, EntityEvent, and BufferedEvent (#19647)
# Objective

Closes #19564.

The current `Event` trait looks like this:

```rust
pub trait Event: Send + Sync + 'static {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
    
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

The `Event` trait is used by both buffered events
(`EventReader`/`EventWriter`) and observer events. If they are observer
events, they can optionally be targeted at specific `Entity`s or
`ComponentId`s, and can even be propagated to other entities.

However, there has long been a desire to split the trait semantically
for a variety of reasons, see #14843, #14272, and #16031 for discussion.
Some reasons include:

- It's very uncommon to use a single event type as both a buffered event
and targeted observer event. They are used differently and tend to have
distinct semantics.
- A common footgun is using buffered events with observers or event
readers with observer events, as there is no type-level error that
prevents this kind of misuse.
- #19440 made `Trigger::target` return an `Option<Entity>`. This
*seriously* hurts ergonomics for the general case of entity observers,
as you need to `.unwrap()` each time. If we could statically determine
whether the event is expected to have an entity target, this would be
unnecessary.

There's really two main ways that we can categorize events: push vs.
pull (i.e. "observer event" vs. "buffered event") and global vs.
targeted:

|              | Push            | Pull                        |
| ------------ | --------------- | --------------------------- |
| **Global**   | Global observer | `EventReader`/`EventWriter` |
| **Targeted** | Entity observer | -                           |

There are many ways to approach this, each with their tradeoffs.
Ultimately, we kind of want to split events both ways:

- A type-level distinction between observer events and buffered events,
to prevent people from using the wrong kind of event in APIs
- A statically designated entity target for observer events to avoid
accidentally using untargeted events for targeted APIs

This PR achieves these goals by splitting event traits into `Event`,
`EntityEvent`, and `BufferedEvent`, with `Event` being the shared trait
implemented by all events.

## `Event`, `EntityEvent`, and `BufferedEvent`

`Event` is now a very simple trait shared by all events.

```rust
pub trait Event: Send + Sync + 'static {
    // Required for observer APIs
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

You can call `trigger` for *any* event, and use a global observer for
listening to the event.

```rust
#[derive(Event)]
struct Speak {
    message: String,
}

// ...

app.add_observer(|trigger: On<Speak>| {
    println!("{}", trigger.message);
});

// ...

commands.trigger(Speak {
    message: "Y'all like these reworked events?".to_string(),
});
```

To allow an event to be targeted at entities and even propagated
further, you can additionally implement the `EntityEvent` trait:

```rust
pub trait EntityEvent: Event {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
}
```

This lets you call `trigger_targets`, and to use targeted observer APIs
like `EntityCommands::observe`:

```rust
#[derive(Event, EntityEvent)]
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
struct Damage {
    amount: f32,
}

// ...

let enemy = commands.spawn((Enemy, Health(100.0))).id();

// Spawn some armor as a child of the enemy entity.
// When the armor takes damage, it will bubble the event up to the enemy.
let armor_piece = commands
    .spawn((ArmorPiece, Health(25.0), ChildOf(enemy)))
    .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| {
        // Note: `On::target` only exists because this is an `EntityEvent`.
        let mut health = query.get(trigger.target()).unwrap();
        health.0 -= trigger.amount();
    });

commands.trigger_targets(Damage { amount: 10.0 }, armor_piece);
```

> [!NOTE]
> You *can* still also trigger an `EntityEvent` without targets using
`trigger`. We probably *could* make this an either-or thing, but I'm not
sure that's actually desirable.

To allow an event to be used with the buffered API, you can implement
`BufferedEvent`:

```rust
pub trait BufferedEvent: Event {}
```

The event can then be used with `EventReader`/`EventWriter`:

```rust
#[derive(Event, BufferedEvent)]
struct Message(String);

fn write_hello(mut writer: EventWriter<Message>) {
    writer.write(Message("I hope these examples are alright".to_string()));
}

fn read_messages(mut reader: EventReader<Message>) {
    // Process all buffered events of type `Message`.
    for Message(message) in reader.read() {
        println!("{message}");
    }
}
```

In summary:

- Need a basic event you can trigger and observe? Derive `Event`!
- Need the event to be targeted at an entity? Derive `EntityEvent`!
- Need the event to be buffered and support the
`EventReader`/`EventWriter` API? Derive `BufferedEvent`!

## Alternatives

I'll now cover some of the alternative approaches I have considered and
briefly explored. I made this section collapsible since it ended up
being quite long :P

<details>

<summary>Expand this to see alternatives</summary>

### 1. Unified `Event` Trait

One option is not to have *three* separate traits (`Event`,
`EntityEvent`, `BufferedEvent`), and to instead just use associated
constants on `Event` to determine whether an event supports targeting
and buffering or not:

```rust
pub trait Event: Send + Sync + 'static {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
    const TARGETED: bool = false;
    const BUFFERED: bool = false;
    
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

Methods can then use bounds like `where E: Event<TARGETED = true>` or
`where E: Event<BUFFERED = true>` to limit APIs to specific kinds of
events.

This would keep everything under one `Event` trait, but I don't think
it's necessarily a good idea. It makes APIs harder to read, and docs
can't easily refer to specific types of events. You can also create
weird invariants: what if you specify `TARGETED = false`, but have
`Traversal` and/or `AUTO_PROPAGATE` enabled?

### 2. `Event` and `Trigger`

Another option is to only split the traits between buffered events and
observer events, since that is the main thing people have been asking
for, and they have the largest API difference.

If we did this, I think we would need to make the terms *clearly*
separate. We can't really use `Event` and `BufferedEvent` as the names,
since it would be strange that `BufferedEvent` doesn't implement
`Event`. Something like `ObserverEvent` and `BufferedEvent` could work,
but it'd be more verbose.

For this approach, I would instead keep `Event` for the current
`EventReader`/`EventWriter` API, and call the observer event a
`Trigger`, since the "trigger" terminology is already used in the
observer context within Bevy (both as a noun and a verb). This is also
what a long [bikeshed on
Discord](https://discord.com/channels/691052431525675048/749335865876021248/1298057661878898791)
seemed to land on at the end of last year.

```rust
// For `EventReader`/`EventWriter`
pub trait Event: Send + Sync + 'static {}

// For observers
pub trait Trigger: Send + Sync + 'static {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
    const TARGETED: bool = false;
    
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

The problem is that "event" is just a really good term for something
that "happens". Observers are rapidly becoming the more prominent API,
so it'd be weird to give them the `Trigger` name and leave the good
`Event` name for the less common API.

So, even though a split like this seems neat on the surface, I think it
ultimately wouldn't really work. We want to keep the `Event` name for
observer events, and there is no good alternative for the buffered
variant. (`Message` was suggested, but saying stuff like "sends a
collision message" is weird.)

### 3. `GlobalEvent` + `TargetedEvent`

What if instead of focusing on the buffered vs. observed split, we
*only* make a distinction between global and targeted events?

```rust
// A shared event trait to allow global observers to work
pub trait Event: Send + Sync + 'static {
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}

// For buffered events and non-targeted observer events
pub trait GlobalEvent: Event {}

// For targeted observer events
pub trait TargetedEvent: Event {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
}
```

This is actually the first approach I implemented, and it has the neat
characteristic that you can only use non-targeted APIs like `trigger`
with a `GlobalEvent` and targeted APIs like `trigger_targets` with a
`TargetedEvent`. You have full control over whether the entity should or
should not have a target, as they are fully distinct at the type-level.

However, there's a few problems:

- There is no type-level indication of whether a `GlobalEvent` supports
buffered events or just non-targeted observer events
- An `Event` on its own does literally nothing, it's just a shared trait
required to make global observers accept both non-targeted and targeted
events
- If an event is both a `GlobalEvent` and `TargetedEvent`, global
observers again have ambiguity on whether an event has a target or not,
undermining some of the benefits
- The names are not ideal

### 4. `Event` and `EntityEvent`

We can fix some of the problems of Alternative 3 by accepting that
targeted events can also be used in non-targeted contexts, and simply
having the `Event` and `EntityEvent` traits:

```rust
// For buffered events and non-targeted observer events
pub trait Event: Send + Sync + 'static {
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}

// For targeted observer events
pub trait EntityEvent: Event {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
}
```

This is essentially identical to this PR, just without a dedicated
`BufferedEvent`. The remaining major "problem" is that there is still
zero type-level indication of whether an `Event` event *actually*
supports the buffered API. This leads us to the solution proposed in
this PR, using `Event`, `EntityEvent`, and `BufferedEvent`.

</details>

## Conclusion

The `Event` + `EntityEvent` + `BufferedEvent` split proposed in this PR
aims to solve all the common problems with Bevy's current event model
while keeping the "weirdness" factor minimal. It splits in terms of both
the push vs. pull *and* global vs. targeted aspects, while maintaining a
shared concept for an "event".

### Why I Like This

- The term "event" remains as a single concept for all the different
kinds of events in Bevy.
- Despite all event types being "events", they use fundamentally
different APIs. Instead of assuming that you can use an event type with
any pattern (when only one is typically supported), you explicitly opt
in to each one with dedicated traits.
- Using separate traits for each type of event helps with documentation
and clearer function signatures.
- I can safely make assumptions on expected usage.
- If I see that an event is an `EntityEvent`, I can assume that I can
use `observe` on it and get targeted events.
- If I see that an event is a `BufferedEvent`, I can assume that I can
use `EventReader` to read events.
- If I see both `EntityEvent` and `BufferedEvent`, I can assume that
both APIs are supported.

In summary: This allows for a unified concept for events, while limiting
the different ways to use them with opt-in traits. No more guess-work
involved when using APIs.

### Problems?

- Because `BufferedEvent` implements `Event` (for more consistent
semantics etc.), you can still use all buffered events for non-targeted
observers. I think this is fine/good. The important part is that if you
see that an event implements `BufferedEvent`, you know that the
`EventReader`/`EventWriter` API should be supported. Whether it *also*
supports other APIs is secondary.
- I currently only support `trigger_targets` for an `EntityEvent`.
However, you can technically target components too, without targeting
any entities. I consider that such a niche and advanced use case that
it's not a huge problem to only support it for `EntityEvent`s, but we
could also split `trigger_targets` into `trigger_entities` and
`trigger_components` if we wanted to (or implement components as
entities :P).
- You can still trigger an `EntityEvent` *without* targets. I consider
this correct, since `Event` implements the non-targeted behavior, and
it'd be weird if implementing another trait *removed* behavior. However,
it does mean that global observers for entity events can technically
return `Entity::PLACEHOLDER` again (since I got rid of the
`Option<Entity>` added in #19440 for ergonomics). I think that's enough
of an edge case that it's not a huge problem, but it is worth keeping in
mind.
- ~~Deriving both `EntityEvent` and `BufferedEvent` for the same type
currently duplicates the `Event` implementation, so you instead need to
manually implement one of them.~~ Changed to always requiring `Event` to
be derived.

## Related Work

There are plans to implement multi-event support for observers,
especially for UI contexts. [Cart's
example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508)
API looked like this:

```rust
// Truncated for brevity
trigger: Trigger<(
    OnAdd<Pressed>,
    OnRemove<Pressed>,
    OnAdd<InteractionDisabled>,
    OnRemove<InteractionDisabled>,
    OnInsert<Hovered>,
)>,
```

I believe this shouldn't be in conflict with this PR. If anything, this
PR might *help* achieve the multi-event pattern for entity observers
with fewer footguns: by statically enforcing that all of these events
are `EntityEvent`s in the context of `EntityCommands::observe`, we can
avoid misuse or weird cases where *some* events inside the trigger are
targeted while others are not.
2025-06-15 16:46:34 +00:00

896 lines
31 KiB
Rust

use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote, ToTokens};
use std::collections::HashSet;
use syn::{
braced, parenthesized,
parse::Parse,
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::{Brace, Comma, Paren},
Data, DataEnum, DataStruct, DeriveInput, Expr, ExprCall, ExprPath, Field, Fields, Ident,
LitStr, Member, Path, Result, Token, Type, Visibility,
};
pub const EVENT: &str = "entity_event";
pub const AUTO_PROPAGATE: &str = "auto_propagate";
pub const TRAVERSAL: &str = "traversal";
pub fn derive_event(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {}
})
}
pub fn derive_entity_event(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let mut auto_propagate = false;
let mut traversal: Type = parse_quote!(());
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
if let Some(attr) = ast.attrs.iter().find(|attr| attr.path().is_ident(EVENT)) {
if let Err(e) = attr.parse_nested_meta(|meta| match meta.path.get_ident() {
Some(ident) if ident == AUTO_PROPAGATE => {
auto_propagate = true;
Ok(())
}
Some(ident) if ident == TRAVERSAL => {
traversal = meta.value()?.parse()?;
Ok(())
}
Some(ident) => Err(meta.error(format!("unsupported attribute: {ident}"))),
None => Err(meta.error("expected identifier")),
}) {
return e.to_compile_error().into();
}
}
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause {
type Traversal = #traversal;
const AUTO_PROPAGATE: bool = #auto_propagate;
}
})
}
pub fn derive_buffered_event(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::BufferedEvent for #struct_name #type_generics #where_clause {}
})
}
pub fn derive_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause {
}
})
}
pub fn derive_component(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
let attrs = match parse_component_attr(&ast) {
Ok(attrs) => attrs,
Err(e) => return e.into_compile_error().into(),
};
let relationship = match derive_relationship(&ast, &attrs, &bevy_ecs_path) {
Ok(value) => value,
Err(err) => err.into_compile_error().into(),
};
let relationship_target = match derive_relationship_target(&ast, &attrs, &bevy_ecs_path) {
Ok(value) => value,
Err(err) => err.into_compile_error().into(),
};
let map_entities = map_entities(
&ast.data,
Ident::new("this", Span::call_site()),
relationship.is_some(),
relationship_target.is_some(),
).map(|map_entities_impl| quote! {
fn map_entities<M: #bevy_ecs_path::entity::EntityMapper>(this: &mut Self, mapper: &mut M) {
use #bevy_ecs_path::entity::MapEntities;
#map_entities_impl
}
});
let storage = storage_path(&bevy_ecs_path, attrs.storage);
let on_add_path = attrs
.on_add
.map(|path| path.to_token_stream(&bevy_ecs_path));
let on_remove_path = attrs
.on_remove
.map(|path| path.to_token_stream(&bevy_ecs_path));
let on_insert_path = if relationship.is_some() {
if attrs.on_insert.is_some() {
return syn::Error::new(
ast.span(),
"Custom on_insert hooks are not supported as relationships already define an on_insert hook",
)
.into_compile_error()
.into();
}
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert))
} else {
attrs
.on_insert
.map(|path| path.to_token_stream(&bevy_ecs_path))
};
let on_replace_path = if relationship.is_some() {
if attrs.on_replace.is_some() {
return syn::Error::new(
ast.span(),
"Custom on_replace hooks are not supported as Relationships already define an on_replace hook",
)
.into_compile_error()
.into();
}
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_replace))
} else if attrs.relationship_target.is_some() {
if attrs.on_replace.is_some() {
return syn::Error::new(
ast.span(),
"Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook",
)
.into_compile_error()
.into();
}
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_replace))
} else {
attrs
.on_replace
.map(|path| path.to_token_stream(&bevy_ecs_path))
};
let on_despawn_path = if attrs
.relationship_target
.is_some_and(|target| target.linked_spawn)
{
if attrs.on_despawn.is_some() {
return syn::Error::new(
ast.span(),
"Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute",
)
.into_compile_error()
.into();
}
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn))
} else {
attrs
.on_despawn
.map(|path| path.to_token_stream(&bevy_ecs_path))
};
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);
let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);
let on_replace =
hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);
let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);
let on_despawn =
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let requires = &attrs.requires;
let mut register_required = Vec::with_capacity(attrs.requires.iter().len());
let mut register_recursive_requires = Vec::with_capacity(attrs.requires.iter().len());
if let Some(requires) = requires {
for require in requires {
let ident = &require.path;
register_recursive_requires.push(quote! {
<#ident as #bevy_ecs_path::component::Component>::register_required_components(
requiree,
components,
required_components,
inheritance_depth + 1,
recursion_check_stack
);
});
match &require.func {
Some(func) => {
register_required.push(quote! {
components.register_required_components_manual::<Self, #ident>(
required_components,
|| { let x: #ident = (#func)().into(); x },
inheritance_depth,
recursion_check_stack
);
});
}
None => {
register_required.push(quote! {
components.register_required_components_manual::<Self, #ident>(
required_components,
<#ident as Default>::default,
inheritance_depth,
recursion_check_stack
);
});
}
}
}
}
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let required_component_docs = attrs.requires.map(|r| {
let paths = r
.iter()
.map(|r| format!("[`{}`]", r.path.to_token_stream()))
.collect::<Vec<_>>()
.join(", ");
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.");
quote! {
#[doc = #doc]
}
});
let mutable_type = (attrs.immutable || relationship.is_some())
.then_some(quote! { #bevy_ecs_path::component::Immutable })
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
let clone_behavior = if relationship_target.is_some() {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
} else if let Some(behavior) = attrs.clone_behavior {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
} else {
quote!(
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
(&&&#bevy_ecs_path::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
)
};
// This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top
// level components are initialized first, giving them precedence over recursively defined constructors for the same component type
TokenStream::from(quote! {
#required_component_docs
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
type Mutability = #mutable_type;
fn register_required_components(
requiree: #bevy_ecs_path::component::ComponentId,
components: &mut #bevy_ecs_path::component::ComponentsRegistrator,
required_components: &mut #bevy_ecs_path::component::RequiredComponents,
inheritance_depth: u16,
recursion_check_stack: &mut #bevy_ecs_path::__macro_exports::Vec<#bevy_ecs_path::component::ComponentId>
) {
#bevy_ecs_path::component::enforce_no_required_components_recursion(components, recursion_check_stack);
let self_id = components.register_component::<Self>();
recursion_check_stack.push(self_id);
#(#register_required)*
#(#register_recursive_requires)*
recursion_check_stack.pop();
}
#on_add
#on_insert
#on_replace
#on_remove
#on_despawn
fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {
#clone_behavior
}
#map_entities
}
#relationship
#relationship_target
})
}
const ENTITIES: &str = "entities";
pub(crate) fn map_entities(
data: &Data,
self_ident: Ident,
is_relationship: bool,
is_relationship_target: bool,
) -> Option<TokenStream2> {
match data {
Data::Struct(DataStruct { fields, .. }) => {
let mut map = Vec::with_capacity(fields.len());
let relationship = if is_relationship || is_relationship_target {
relationship_field(fields, "MapEntities", fields.span()).ok()
} else {
None
};
fields
.iter()
.enumerate()
.filter(|(_, field)| {
field.attrs.iter().any(|a| a.path().is_ident(ENTITIES))
|| relationship.is_some_and(|relationship| relationship == *field)
})
.for_each(|(index, field)| {
let field_member = field
.ident
.clone()
.map_or(Member::from(index), Member::Named);
map.push(quote!(#self_ident.#field_member.map_entities(mapper);));
});
if map.is_empty() {
return None;
};
Some(quote!(
#(#map)*
))
}
Data::Enum(DataEnum { variants, .. }) => {
let mut map = Vec::with_capacity(variants.len());
for variant in variants.iter() {
let field_members = variant
.fields
.iter()
.enumerate()
.filter(|(_, field)| field.attrs.iter().any(|a| a.path().is_ident(ENTITIES)))
.map(|(index, field)| {
field
.ident
.clone()
.map_or(Member::from(index), Member::Named)
})
.collect::<Vec<_>>();
let ident = &variant.ident;
let field_idents = field_members
.iter()
.map(|member| format_ident!("__self_{}", member))
.collect::<Vec<_>>();
map.push(
quote!(Self::#ident {#(#field_members: #field_idents,)* ..} => {
#(#field_idents.map_entities(mapper);)*
}),
);
}
if map.is_empty() {
return None;
};
Some(quote!(
match #self_ident {
#(#map,)*
_ => {}
}
))
}
Data::Union(_) => None,
}
}
pub const COMPONENT: &str = "component";
pub const STORAGE: &str = "storage";
pub const REQUIRE: &str = "require";
pub const RELATIONSHIP: &str = "relationship";
pub const RELATIONSHIP_TARGET: &str = "relationship_target";
pub const ON_ADD: &str = "on_add";
pub const ON_INSERT: &str = "on_insert";
pub const ON_REPLACE: &str = "on_replace";
pub const ON_REMOVE: &str = "on_remove";
pub const ON_DESPAWN: &str = "on_despawn";
pub const IMMUTABLE: &str = "immutable";
pub const CLONE_BEHAVIOR: &str = "clone_behavior";
/// All allowed attribute value expression kinds for component hooks
#[derive(Debug)]
enum HookAttributeKind {
/// expressions like function or struct names
///
/// structs will throw compile errors on the code generation so this is safe
Path(ExprPath),
/// function call like expressions
Call(ExprCall),
}
impl HookAttributeKind {
fn from_expr(value: Expr) -> Result<Self> {
match value {
Expr::Path(path) => Ok(HookAttributeKind::Path(path)),
Expr::Call(call) => Ok(HookAttributeKind::Call(call)),
// throw meaningful error on all other expressions
_ => Err(syn::Error::new(
value.span(),
[
"Not supported in this position, please use one of the following:",
"- path to function",
"- call to function yielding closure",
]
.join("\n"),
)),
}
}
fn to_token_stream(&self, bevy_ecs_path: &Path) -> TokenStream2 {
match self {
HookAttributeKind::Path(path) => path.to_token_stream(),
HookAttributeKind::Call(call) => {
quote!({
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {
(#call)(world, ctx)
}
_internal_hook
})
}
}
}
}
impl Parse for HookAttributeKind {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
input.parse::<Expr>().and_then(Self::from_expr)
}
}
struct Attrs {
storage: StorageTy,
requires: Option<Punctuated<Require, Comma>>,
on_add: Option<HookAttributeKind>,
on_insert: Option<HookAttributeKind>,
on_replace: Option<HookAttributeKind>,
on_remove: Option<HookAttributeKind>,
on_despawn: Option<HookAttributeKind>,
relationship: Option<Relationship>,
relationship_target: Option<RelationshipTarget>,
immutable: bool,
clone_behavior: Option<Expr>,
}
#[derive(Clone, Copy)]
enum StorageTy {
Table,
SparseSet,
}
struct Require {
path: Path,
func: Option<TokenStream2>,
}
struct Relationship {
relationship_target: Type,
}
struct RelationshipTarget {
relationship: Type,
linked_spawn: bool,
}
// values for `storage` attribute
const TABLE: &str = "Table";
const SPARSE_SET: &str = "SparseSet";
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
let mut attrs = Attrs {
storage: StorageTy::Table,
on_add: None,
on_insert: None,
on_replace: None,
on_remove: None,
on_despawn: None,
requires: None,
relationship: None,
relationship_target: None,
immutable: false,
clone_behavior: None,
};
let mut require_paths = HashSet::new();
for attr in ast.attrs.iter() {
if attr.path().is_ident(COMPONENT) {
attr.parse_nested_meta(|nested| {
if nested.path.is_ident(STORAGE) {
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
s if s == TABLE => StorageTy::Table,
s if s == SPARSE_SET => StorageTy::SparseSet,
s => {
return Err(nested.error(format!(
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
)));
}
};
Ok(())
} else if nested.path.is_ident(ON_ADD) {
attrs.on_add = Some(nested.value()?.parse::<HookAttributeKind>()?);
Ok(())
} else if nested.path.is_ident(ON_INSERT) {
attrs.on_insert = Some(nested.value()?.parse::<HookAttributeKind>()?);
Ok(())
} else if nested.path.is_ident(ON_REPLACE) {
attrs.on_replace = Some(nested.value()?.parse::<HookAttributeKind>()?);
Ok(())
} else if nested.path.is_ident(ON_REMOVE) {
attrs.on_remove = Some(nested.value()?.parse::<HookAttributeKind>()?);
Ok(())
} else if nested.path.is_ident(ON_DESPAWN) {
attrs.on_despawn = Some(nested.value()?.parse::<HookAttributeKind>()?);
Ok(())
} else if nested.path.is_ident(IMMUTABLE) {
attrs.immutable = true;
Ok(())
} else if nested.path.is_ident(CLONE_BEHAVIOR) {
attrs.clone_behavior = Some(nested.value()?.parse()?);
Ok(())
} else {
Err(nested.error("Unsupported attribute"))
}
})?;
} else if attr.path().is_ident(REQUIRE) {
let punctuated =
attr.parse_args_with(Punctuated::<Require, Comma>::parse_terminated)?;
for require in punctuated.iter() {
if !require_paths.insert(require.path.to_token_stream().to_string()) {
return Err(syn::Error::new(
require.path.span(),
"Duplicate required components are not allowed.",
));
}
}
if let Some(current) = &mut attrs.requires {
current.extend(punctuated);
} else {
attrs.requires = Some(punctuated);
}
} else if attr.path().is_ident(RELATIONSHIP) {
let relationship = attr.parse_args::<Relationship>()?;
attrs.relationship = Some(relationship);
} else if attr.path().is_ident(RELATIONSHIP_TARGET) {
let relationship_target = attr.parse_args::<RelationshipTarget>()?;
attrs.relationship_target = Some(relationship_target);
}
}
if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {
return Err(syn::Error::new(
attrs.clone_behavior.span(),
"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",
));
}
Ok(attrs)
}
impl Parse for Require {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let mut path = input.parse::<Path>()?;
let mut last_segment_is_lower = false;
let mut is_constructor_call = false;
// Use the case of the type name to check if it's an enum
// This doesn't match everything that can be an enum according to the rust spec
// but it matches what clippy is OK with
let is_enum = {
let mut first_chars = path
.segments
.iter()
.rev()
.filter_map(|s| s.ident.to_string().chars().next());
if let Some(last) = first_chars.next() {
if last.is_uppercase() {
if let Some(last) = first_chars.next() {
last.is_uppercase()
} else {
false
}
} else {
last_segment_is_lower = true;
false
}
} else {
false
}
};
let func = if input.peek(Token![=]) {
// If there is an '=', then this is a "function style" require
input.parse::<Token![=]>()?;
let expr: Expr = input.parse()?;
Some(quote!(|| #expr ))
} else if input.peek(Brace) {
// This is a "value style" named-struct-like require
let content;
braced!(content in input);
let content = content.parse::<TokenStream2>()?;
Some(quote!(|| #path { #content }))
} else if input.peek(Paren) {
// This is a "value style" tuple-struct-like require
let content;
parenthesized!(content in input);
let content = content.parse::<TokenStream2>()?;
is_constructor_call = last_segment_is_lower;
Some(quote!(|| #path (#content)))
} else if is_enum {
// if this is an enum, then it is an inline enum component declaration
Some(quote!(|| #path))
} else {
// if this isn't any of the above, then it is a component ident, which will use Default
None
};
if is_enum || is_constructor_call {
path.segments.pop();
path.segments.pop_punct();
}
Ok(Require { path, func })
}
}
fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
let storage_type = match ty {
StorageTy::Table => Ident::new("Table", Span::call_site()),
StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()),
};
quote! { #bevy_ecs_path::component::StorageType::#storage_type }
}
fn hook_register_function_call(
bevy_ecs_path: &Path,
hook: TokenStream2,
function: Option<TokenStream2>,
) -> Option<TokenStream2> {
function.map(|meta| {
quote! {
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
::core::option::Option::Some(#meta)
}
}
})
}
mod kw {
syn::custom_keyword!(relationship_target);
syn::custom_keyword!(relationship);
syn::custom_keyword!(linked_spawn);
}
impl Parse for Relationship {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
input.parse::<kw::relationship_target>()?;
input.parse::<Token![=]>()?;
Ok(Relationship {
relationship_target: input.parse::<Type>()?,
})
}
}
impl Parse for RelationshipTarget {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let mut relationship: Option<Type> = None;
let mut linked_spawn: bool = false;
while !input.is_empty() {
let lookahead = input.lookahead1();
if lookahead.peek(kw::linked_spawn) {
input.parse::<kw::linked_spawn>()?;
linked_spawn = true;
} else if lookahead.peek(kw::relationship) {
input.parse::<kw::relationship>()?;
input.parse::<Token![=]>()?;
relationship = Some(input.parse()?);
} else {
return Err(lookahead.error());
}
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(RelationshipTarget {
relationship: relationship.ok_or_else(|| {
syn::Error::new(input.span(), "Missing `relationship = X` attribute")
})?,
linked_spawn,
})
}
}
fn derive_relationship(
ast: &DeriveInput,
attrs: &Attrs,
bevy_ecs_path: &Path,
) -> Result<Option<TokenStream2>> {
let Some(relationship) = &attrs.relationship else {
return Ok(None);
};
let Data::Struct(DataStruct {
fields,
struct_token,
..
}) = &ast.data
else {
return Err(syn::Error::new(
ast.span(),
"Relationship can only be derived for structs.",
));
};
let field = relationship_field(fields, "Relationship", struct_token.span())?;
let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);
let members = fields
.members()
.filter(|member| member != &relationship_member);
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let relationship_target = &relationship.relationship_target;
Ok(Some(quote! {
impl #impl_generics #bevy_ecs_path::relationship::Relationship for #struct_name #type_generics #where_clause {
type RelationshipTarget = #relationship_target;
#[inline(always)]
fn get(&self) -> #bevy_ecs_path::entity::Entity {
self.#relationship_member
}
#[inline]
fn from(entity: #bevy_ecs_path::entity::Entity) -> Self {
Self {
#(#members: core::default::Default::default(),)*
#relationship_member: entity
}
}
}
}))
}
fn derive_relationship_target(
ast: &DeriveInput,
attrs: &Attrs,
bevy_ecs_path: &Path,
) -> Result<Option<TokenStream2>> {
let Some(relationship_target) = &attrs.relationship_target else {
return Ok(None);
};
let Data::Struct(DataStruct {
fields,
struct_token,
..
}) = &ast.data
else {
return Err(syn::Error::new(
ast.span(),
"RelationshipTarget can only be derived for structs.",
));
};
let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?;
if field.vis != Visibility::Inherited {
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."));
}
let collection = &field.ty;
let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named);
let members = fields
.members()
.filter(|member| member != &relationship_member);
let relationship = &relationship_target.relationship;
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let linked_spawn = relationship_target.linked_spawn;
Ok(Some(quote! {
impl #impl_generics #bevy_ecs_path::relationship::RelationshipTarget for #struct_name #type_generics #where_clause {
const LINKED_SPAWN: bool = #linked_spawn;
type Relationship = #relationship;
type Collection = #collection;
#[inline]
fn collection(&self) -> &Self::Collection {
&self.#relationship_member
}
#[inline]
fn collection_mut_risky(&mut self) -> &mut Self::Collection {
&mut self.#relationship_member
}
#[inline]
fn from_collection_risky(collection: Self::Collection) -> Self {
Self {
#(#members: core::default::Default::default(),)*
#relationship_member: collection
}
}
}
}))
}
/// Returns the field with the `#[relationship]` attribute, the only field if unnamed,
/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`.
fn relationship_field<'a>(
fields: &'a Fields,
derive: &'static str,
span: Span,
) -> Result<&'a Field> {
match fields {
Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()),
Fields::Named(fields) => fields.named.iter().find(|field| {
field
.attrs
.iter()
.any(|attr| attr.path().is_ident(RELATIONSHIP))
}).ok_or(syn::Error::new(
span,
format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].")
)),
Fields::Unnamed(fields) => fields
.unnamed
.len()
.eq(&1)
.then(|| fields.unnamed.first())
.flatten()
.ok_or(syn::Error::new(
span,
format!("{derive} derive expected unnamed structs with one field."),
)),
Fields::Unit => Err(syn::Error::new(
span,
format!("{derive} derive expected named or unnamed struct, found unit struct."),
)),
}
}