From 67aa2953d03f689be0fbdd118a8dea2ec5e2743f Mon Sep 17 00:00:00 2001 From: Torstein Grindvik Date: Mon, 30 Jan 2023 18:12:16 +0000 Subject: [PATCH] Extract component derive (#7399) # Objective In simple cases we might want to derive the `ExtractComponent` trait. This adds symmetry to the existing `ExtractResource` derive. ## Solution Add an implementation of `#[derive(ExtractComponent)]`. The implementation is adapted from the existing `ExtractResource` derive macro. Additionally, there is an attribute called `extract_component_filter`. This allows specifying a query filter type used when extracting. If not specified, no filter (equal to `()`) is used. So: ```rust #[derive(Component, Clone, ExtractComponent)] #[extract_component_filter(With)] pub struct Car { pub wheels: usize, } ``` would expand to (a bit cleaned up here): ```rust impl ExtractComponent for Car { type Query = &'static Self; type Filter = With; type Out = Self; fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { Some(item.clone()) } } ``` --- ## Changelog - Added the ability to `#[derive(ExtractComponent)]` with an optional filter. --- .../src/core_2d/camera_2d.rs | 15 ++---- .../src/core_3d/camera_3d.rs | 15 ++---- crates/bevy_core_pipeline/src/fxaa/mod.rs | 15 ++---- .../bevy_core_pipeline/src/tonemapping/mod.rs | 14 +---- crates/bevy_pbr/src/wireframe.rs | 14 ++--- .../macros/src/extract_component.rs | 51 +++++++++++++++++++ crates/bevy_render/macros/src/lib.rs | 32 ++++++++++++ crates/bevy_render/src/extract_component.rs | 2 + crates/bevy_ui/src/camera_config.rs | 14 +---- 9 files changed, 102 insertions(+), 70 deletions(-) create mode 100644 crates/bevy_render/macros/src/extract_component.rs diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index dce52de82b..0eb793228b 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -1,5 +1,5 @@ use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use bevy_render::{ camera::{Camera, CameraProjection, CameraRenderGraph, OrthographicProjection}, @@ -9,22 +9,13 @@ use bevy_render::{ }; use bevy_transform::prelude::{GlobalTransform, Transform}; -#[derive(Component, Default, Reflect, Clone)] +#[derive(Component, Default, Reflect, Clone, ExtractComponent)] +#[extract_component_filter(With)] #[reflect(Component)] pub struct Camera2d { pub clear_color: ClearColorConfig, } -impl ExtractComponent for Camera2d { - type Query = &'static Self; - type Filter = With; - type Out = Self; - - fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { - Some(item.clone()) - } -} - #[derive(Bundle)] pub struct Camera2dBundle { pub camera: Camera, 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 d722f411a2..045ac906f7 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -1,5 +1,5 @@ use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::{ camera::{Camera, CameraRenderGraph, Projection}, @@ -12,7 +12,8 @@ use bevy_transform::prelude::{GlobalTransform, Transform}; use serde::{Deserialize, Serialize}; /// Configuration for the "main 3d render graph". -#[derive(Component, Reflect, Clone, Default)] +#[derive(Component, Reflect, Clone, Default, ExtractComponent)] +#[extract_component_filter(With)] #[reflect(Component)] pub struct Camera3d { /// The clear color operation to perform for the main 3d pass. @@ -47,16 +48,6 @@ impl From for LoadOp { } } -impl ExtractComponent for Camera3d { - type Query = &'static Self; - type Filter = With; - type Out = Self; - - fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { - Some(item.clone()) - } -} - #[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 4b2a7a2ea9..58ebf04d51 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::prelude::*; use bevy_reflect::TypeUuid; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, @@ -40,7 +40,8 @@ impl Sensitivity { } } -#[derive(Component, Clone)] +#[derive(Component, Clone, ExtractComponent)] +#[extract_component_filter(With)] pub struct Fxaa { /// Enable render passes for FXAA. pub enabled: bool, @@ -67,16 +68,6 @@ impl Default for Fxaa { } } -impl ExtractComponent for Fxaa { - type Query = &'static Self; - type Filter = With; - type Out = Self; - - fn extract_component(item: QueryItem) -> Option { - Some(item.clone()) - } -} - const FXAA_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4182761465141723543); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index f7233fd575..9839e07baf 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -2,7 +2,6 @@ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::prelude::*; -use bevy_ecs::query::QueryItem; use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::camera::Camera; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; @@ -145,7 +144,8 @@ pub fn queue_view_tonemapping_pipelines( } } -#[derive(Component, Clone, Reflect, Default)] +#[derive(Component, Clone, Reflect, Default, ExtractComponent)] +#[extract_component_filter(With)] #[reflect(Component)] pub enum Tonemapping { #[default] @@ -160,13 +160,3 @@ impl Tonemapping { matches!(self, Tonemapping::Enabled { .. }) } } - -impl ExtractComponent for Tonemapping { - type Query = &'static Self; - type Filter = With; - type Out = Self; - - fn extract_component(item: QueryItem) -> Option { - Some(item.clone()) - } -} diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index a680a07905..471f3b94f8 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -6,7 +6,7 @@ use bevy_core_pipeline::core_3d::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{Reflect, TypeUuid}; -use bevy_render::Extract; +use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy_render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, mesh::{Mesh, MeshVertexBufferLayout}, @@ -39,27 +39,21 @@ impl Plugin for WireframePlugin { app.register_type::() .register_type::() .init_resource::() - .add_plugin(ExtractResourcePlugin::::default()); + .add_plugin(ExtractResourcePlugin::::default()) + .add_plugin(ExtractComponentPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .add_render_command::() .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Extract, extract_wireframes) .add_system_to_stage(RenderStage::Queue, queue_wireframes); } } } -fn extract_wireframes(mut commands: Commands, query: Extract>>) { - for entity in &query { - commands.get_or_spawn(entity).insert(Wireframe); - } -} - /// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled -#[derive(Component, Debug, Clone, Default, Reflect)] +#[derive(Component, Debug, Clone, Default, ExtractComponent, Reflect)] #[reflect(Component, Default)] pub struct Wireframe; diff --git a/crates/bevy_render/macros/src/extract_component.rs b/crates/bevy_render/macros/src/extract_component.rs new file mode 100644 index 0000000000..3def17a7b8 --- /dev/null +++ b/crates/bevy_render/macros/src/extract_component.rs @@ -0,0 +1,51 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, parse_quote, DeriveInput, Path}; + +pub fn derive_extract_component(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_render_path: Path = crate::bevy_render_path(); + let bevy_ecs_path: Path = bevy_macro_utils::BevyManifest::default() + .maybe_get_path("bevy_ecs") + .expect("bevy_ecs should be found in manifest"); + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: Clone }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + let filter = if let Some(attr) = ast + .attrs + .iter() + .find(|a| a.path.is_ident("extract_component_filter")) + { + let filter = match attr.parse_args::() { + Ok(filter) => filter, + Err(e) => return e.to_compile_error().into(), + }; + + quote! { + #filter + } + } else { + quote! { + () + } + }; + + TokenStream::from(quote! { + impl #impl_generics #bevy_render_path::extract_component::ExtractComponent for #struct_name #type_generics #where_clause { + type Query = &'static Self; + + type Filter = #filter; + type Out = Self; + + fn extract_component(item: #bevy_ecs_path::query::QueryItem<'_, Self::Query>) -> Option { + Some(item.clone()) + } + } + }) +} diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index e8da33ad3c..89eec6b220 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -1,4 +1,5 @@ mod as_bind_group; +mod extract_component; mod extract_resource; use bevy_macro_utils::BevyManifest; @@ -17,6 +18,37 @@ pub fn derive_extract_resource(input: TokenStream) -> TokenStream { extract_resource::derive_extract_resource(input) } +/// Implements `ExtractComponent` trait for a component. +/// The component must implement [`Clone`]. +/// The component will be extracted into the render world via cloning. +/// Note that this only enables extraction of the component, it does not execute the extraction. +/// See `ExtractComponentPlugin` to actually perform the extraction. +/// +/// If you only want to extract a component conditionally, you may use the `extract_component_filter` attribute. +/// +/// # Example +/// +/// ```no_compile +/// use bevy_ecs::component::Component; +/// use bevy_render_macros::ExtractComponent; +/// +/// #[derive(Component, Clone, ExtractComponent)] +/// #[extract_component_filter(With)] +/// pub struct Foo { +/// pub should_foo: bool, +/// } +/// +/// // Without a filter (unconditional). +/// #[derive(Component, Clone, ExtractComponent)] +/// pub struct Bar { +/// pub should_bar: bool, +/// } +/// ``` +#[proc_macro_derive(ExtractComponent, attributes(extract_component_filter))] +pub fn derive_extract_component(input: TokenStream) -> TokenStream { + extract_component::derive_extract_component(input) +} + #[proc_macro_derive( AsBindGroup, attributes(uniform, texture, sampler, bind_group_data, storage) diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 7cae7d8065..f29905c1a7 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -14,6 +14,8 @@ use bevy_ecs::{ }; use std::{marker::PhantomData, ops::Deref}; +pub use bevy_render_macros::ExtractComponent; + /// Stores the index of a uniform inside of [`ComponentUniforms`]. #[derive(Component)] pub struct DynamicUniformIndex { diff --git a/crates/bevy_ui/src/camera_config.rs b/crates/bevy_ui/src/camera_config.rs index e653e15b97..097437ac73 100644 --- a/crates/bevy_ui/src/camera_config.rs +++ b/crates/bevy_ui/src/camera_config.rs @@ -2,7 +2,6 @@ use bevy_ecs::component::Component; use bevy_ecs::prelude::With; -use bevy_ecs::query::QueryItem; use bevy_render::camera::Camera; use bevy_render::extract_component::ExtractComponent; @@ -11,7 +10,8 @@ use bevy_render::extract_component::ExtractComponent; /// When a [`Camera`] doesn't have the [`UiCameraConfig`] component, /// it will display the UI by default. /// -#[derive(Component, Clone)] +#[derive(Component, Clone, ExtractComponent)] +#[extract_component_filter(With)] pub struct UiCameraConfig { /// Whether to output UI to this camera view. /// @@ -25,13 +25,3 @@ impl Default for UiCameraConfig { Self { show_ui: true } } } - -impl ExtractComponent for UiCameraConfig { - type Query = &'static Self; - type Filter = With; - type Out = Self; - - fn extract_component(item: QueryItem<'_, Self::Query>) -> Option { - Some(item.clone()) - } -}