diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 7112ccba63d9d..eff16d3315805 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -2,6 +2,7 @@ use crate::{core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::{ + pairs_with, prelude::{Component, Entity}, query::{QueryItem, QueryState, With}, system::{Commands, Query, Res, ResMut, Resource}, @@ -27,7 +28,6 @@ use bevy_render::{ use bevy_utils::tracing::info_span; use bevy_utils::HashMap; use std::num::NonZeroU32; - const BLOOM_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 929599476923908); @@ -114,6 +114,7 @@ impl Plugin for BloomPlugin { /// /// See also . #[derive(Component, Reflect, Clone)] +#[pairs_with(Camera)] pub struct BloomSettings { /// Baseline of the threshold curve (default: 1.0). /// diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index d722f411a21d6..2df95836e296c 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -57,6 +57,7 @@ impl ExtractComponent for Camera3d { } } +/// For components that can be added alongside the bundle, see [`PairsWithCamera`](bevy_render::camera::PairsWithCamera). #[derive(Bundle)] pub struct Camera3dBundle { pub camera: Camera, diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index c843d0df1e87a..a01b8e2aa0c09 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -2,7 +2,7 @@ use crate::{core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_derive::Deref; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::{pairs_with, prelude::*, query::QueryItem}; use bevy_reflect::TypeUuid; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, @@ -41,6 +41,7 @@ impl Sensitivity { } #[derive(Component, Clone)] +#[pairs_with(Camera)] pub struct Fxaa { /// Enable render passes for FXAA. pub enabled: bool, diff --git a/crates/bevy_ecs/macros/Cargo.toml b/crates/bevy_ecs/macros/Cargo.toml index ad77ec9b53503..448f0bc508f0a 100644 --- a/crates/bevy_ecs/macros/Cargo.toml +++ b/crates/bevy_ecs/macros/Cargo.toml @@ -14,3 +14,4 @@ bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.9.0" } syn = "1.0" quote = "1.0" proc-macro2 = "1.0" +once_cell = "1.17.0" diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 3d8a10b3af68e..17552a13b3bc0 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; mod component; mod fetch; +mod pairs; use crate::fetch::derive_world_query_impl; use bevy_macro_utils::{derive_label, get_named_struct_fields, BevyManifest}; @@ -578,3 +579,13 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { pub fn derive_component(input: TokenStream) -> TokenStream { component::derive_component(input) } + +#[proc_macro_derive(PairsWithOthers)] +pub fn derive_pairs_with_others(input: TokenStream) -> TokenStream { + pairs::derive_pairs_with_others(input) +} + +#[proc_macro_attribute] +pub fn pairs_with(attr: TokenStream, item: TokenStream) -> TokenStream { + pairs::derive_pairs_with(attr, item) +} diff --git a/crates/bevy_ecs/macros/src/pairs.rs b/crates/bevy_ecs/macros/src/pairs.rs new file mode 100644 index 0000000000000..567549750fbef --- /dev/null +++ b/crates/bevy_ecs/macros/src/pairs.rs @@ -0,0 +1,81 @@ +use std::collections::HashMap; + +use bevy_macro_utils::BevyManifest; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, AttributeArgs, DeriveInput, Error}; + +static CRATE_LOOKUP: once_cell::sync::Lazy< + HashMap<&'static str, (&'static str, Option<&'static str>)>, +> = once_cell::sync::Lazy::new(|| HashMap::from([("Camera", ("bevy_render", Some("camera")))])); + +pub fn derive_pairs_with_others(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + #[allow(unused)] + let component_name = &ast.ident; + let trait_name = format_ident!("PairsWith{}", ast.ident); + + TokenStream::from(quote! { + /// Lists component types that can be added to entities with the given component. + /// Note: in the bevy project this will only show components included in with engine. If + /// your project uses plugins that add to this list, they will be visible in the + /// bevy documentation in your project tree. + pub trait #trait_name : Component {} + }) +} + +pub fn derive_pairs_with(attrs: TokenStream, mut item: TokenStream) -> TokenStream { + let attrs = parse_macro_input!(attrs as AttributeArgs); + + let mut pairs_with = None; + for meta in attrs { + use syn::NestedMeta::{Lit, Meta}; + match meta { + Meta(syn::Meta::Path(path)) => { + if pairs_with.is_some() { + return Error::new_spanned( + path, + "multiple parse_with attributes not supported", + ) + .into_compile_error() + .into(); + } + pairs_with = Some(path.get_ident().unwrap().to_owned()); + } + Lit(tok) => { + return Error::new_spanned(tok, "unexpected token in parse_with attribute") + .into_compile_error() + .into(); + } + Meta(tok) => { + return Error::new_spanned(tok, "unexpected token in parse_with attribute") + .into_compile_error() + .into(); + } + } + } + + let pairs_with = pairs_with.unwrap(); + let pairs_with_string = pairs_with.to_string(); + let (crate_path, subcrate) = CRATE_LOOKUP.get(pairs_with_string.as_str()).unwrap(); + let crate_path = BevyManifest::get_path_direct(crate_path); + let crate_path = match subcrate { + Some(subcrate) => { + let subcrate: syn::Path = syn::parse(subcrate.parse::().unwrap()).unwrap(); + quote! { #crate_path::#subcrate } + } + None => { + quote! { crate_path } + } + }; + let trait_name = format_ident!("PairsWith{}", pairs_with); + + let ast = item.clone(); + let ast = parse_macro_input!(ast as DeriveInput); + let component_name = &ast.ident; + + item.extend(TokenStream::from(quote! { + impl #crate_path::#trait_name for #component_name {} + })); + item +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 47d93beab837d..6458fb3908b15 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -48,6 +48,7 @@ pub mod prelude { } pub use bevy_ecs_macros::all_tuples; +pub use bevy_ecs_macros::{pairs_with, PairsWithOthers}; #[cfg(test)] mod tests { diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 72bcfa3556df0..931517199a93b 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -8,6 +8,7 @@ use crate::{ }; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::PairsWithOthers; use bevy_ecs::{ change_detection::DetectChanges, component::Component, @@ -81,7 +82,9 @@ pub struct ComputedCameraValues { /// /// Adding a camera is typically done by adding a bundle, either the `Camera2dBundle` or the /// `Camera3dBundle`. -#[derive(Component, Debug, Reflect, FromReflect, Clone)] +/// +/// For components that can be added to the camera, see [`PairsWithCamera`]. +#[derive(Component, Debug, Reflect, FromReflect, Clone, PairsWithOthers)] #[reflect(Component)] pub struct Camera { /// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`].