From 4c4477ff01132b34581c530b4242ae515c17c757 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Thu, 12 Mar 2026 22:52:38 +0800 Subject: [PATCH 1/5] Re-add `Out` to `ExtractComponent` --- .../src/contrast_adaptive_sharpening/mod.rs | 3 +- crates/bevy_anti_alias/src/smaa/mod.rs | 20 ++++-------- crates/bevy_anti_alias/src/taa/mod.rs | 2 +- crates/bevy_core_pipeline/src/oit/mod.rs | 3 +- .../src/prepass/background_motion_vectors.rs | 3 +- crates/bevy_pbr/src/atmosphere/mod.rs | 3 +- crates/bevy_pbr/src/contact_shadows.rs | 3 +- crates/bevy_pbr/src/decal/clustered.rs | 2 +- crates/bevy_pbr/src/lib.rs | 10 +++--- crates/bevy_pbr/src/light_probe/generate.rs | 2 +- crates/bevy_pbr/src/ssr/mod.rs | 7 +++- crates/bevy_pbr/src/volumetric_fog/mod.rs | 2 +- .../bevy_post_process/src/bloom/settings.rs | 3 +- crates/bevy_post_process/src/dof/mod.rs | 4 +-- .../src/effect_stack/chromatic_aberration.rs | 3 +- .../src/effect_stack/vignette.rs | 3 +- .../bevy_post_process/src/motion_blur/mod.rs | 3 +- .../macros/src/extract_component.rs | 23 +++++++++++-- crates/bevy_render/macros/src/lib.rs | 5 ++- crates/bevy_render/src/camera.rs | 9 ++++-- crates/bevy_render/src/extract_component.rs | 10 ++++-- crates/bevy_render/src/extract_plugin.rs | 4 +-- crates/bevy_render/src/sync_component.rs | 16 ++-------- examples/2d/mesh2d_manual.rs | 2 +- .../custom_shader_instancing.rs | 3 +- .../migration-guides/extract_refactor.md | 32 ++++++++----------- 26 files changed, 102 insertions(+), 78 deletions(-) diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs index 6a6b854a5b951..2092f764aece1 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs @@ -77,12 +77,13 @@ pub struct CasUniform { } impl SyncComponent for ContrastAdaptiveSharpening { - type Out = (DenoiseCas, CasUniform); + type Target = (DenoiseCas, CasUniform); } impl ExtractComponent for ContrastAdaptiveSharpening { type QueryData = &'static Self; type QueryFilter = With; + type Out = (DenoiseCas, CasUniform); fn extract_component(item: QueryItem) -> Option { if !item.enabled || item.sharpening_strength == 0.0 { diff --git a/crates/bevy_anti_alias/src/smaa/mod.rs b/crates/bevy_anti_alias/src/smaa/mod.rs index bb496e9cabc2a..513a6bb938ab0 100644 --- a/crates/bevy_anti_alias/src/smaa/mod.rs +++ b/crates/bevy_anti_alias/src/smaa/mod.rs @@ -41,8 +41,6 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, - lifecycle::Remove, - observer::On, query::With, reflect::ReflectComponent, resource::Resource, @@ -85,6 +83,13 @@ pub struct SmaaPlugin; /// for a [`bevy_camera::Camera`]. #[derive(Clone, Copy, Default, Component, Reflect, ExtractComponent)] #[reflect(Component, Default, Clone)] +#[extract_component_sync_target(( + Self, + SmaaTextures, + SmaaPipelines, + SmaaBindGroups, + ViewSmaaPipelines, +))] #[doc(alias = "SubpixelMorphologicalAntiAliasing")] pub struct Smaa { /// A predefined set of SMAA parameters: i.e. a quality level. @@ -331,17 +336,6 @@ impl Plugin for SmaaPlugin { return; }; - // TODO: remove this manual cleanup when ExtractComponent gets support - // for cleanup of derived components - render_app.add_observer(|event: On, mut commands: Commands| { - commands.entity(event.entity).remove::<( - SmaaTextures, - SmaaPipelines, - SmaaBindGroups, - ViewSmaaPipelines, - )>(); - }); - render_app .insert_resource(smaa_luts) .init_resource::() diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index 022f5f37a6353..a34c96aa42429 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -134,7 +134,7 @@ impl Default for TemporalAntiAliasing { } impl SyncComponent for TemporalAntiAliasing { - type Out = Self; + type Target = Self; } fn temporal_anti_alias( diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 36e6bd98a9f5c..993631783c2e5 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -23,7 +23,7 @@ use resolve::OitResolvePlugin; use crate::{ core_3d::main_transparent_pass_3d, - oit::resolve::node::oit_resolve, + oit::resolve::{node::oit_resolve, OitResolvePipelineId}, schedule::{Core3d, Core3dSystems}, }; @@ -37,6 +37,7 @@ pub mod resolve; // This should probably be done by adding an enum to this component. // We use the same struct to pass on the settings to the drawing shader. #[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType, Component)] +#[extract_component_sync_target((Self, OrderIndependentTransparencySettingsOffset, OitResolvePipelineId))] #[reflect(Clone, Default)] pub struct OrderIndependentTransparencySettings { /// Controls how many fragments will be exactly sorted. diff --git a/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs b/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs index ed9c5bb7c711c..645fb7e3d5b18 100644 --- a/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs +++ b/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs @@ -56,12 +56,13 @@ use crate::{ pub struct NoBackgroundMotionVectors; impl SyncComponent for NoBackgroundMotionVectors { - type Out = Self; + type Target = Self; } impl ExtractComponent for NoBackgroundMotionVectors { type QueryData = Read; type QueryFilter = (); + type Out = Self; fn extract_component(_item: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(NoBackgroundMotionVectors) diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 7c786d4e7d91a..3133f7bbcabe3 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -373,12 +373,13 @@ impl From for GpuAtmosphereSettings { } impl SyncComponent for GpuAtmosphereSettings { - type Out = Self; + type Target = Self; } impl ExtractComponent for GpuAtmosphereSettings { type QueryData = Read; type QueryFilter = (With, With); + type Out = Self; fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(item.clone().into()) diff --git a/crates/bevy_pbr/src/contact_shadows.rs b/crates/bevy_pbr/src/contact_shadows.rs index 2e6d1c585c495..d5ebdad3108f5 100644 --- a/crates/bevy_pbr/src/contact_shadows.rs +++ b/crates/bevy_pbr/src/contact_shadows.rs @@ -81,12 +81,13 @@ impl From for ContactShadowsUniform { } impl SyncComponent for ContactShadows { - type Out = Self; + type Target = Self; } impl ExtractComponent for ContactShadows { type QueryData = &'static ContactShadows; type QueryFilter = (); + type Out = Self; fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(*settings) diff --git a/crates/bevy_pbr/src/decal/clustered.rs b/crates/bevy_pbr/src/decal/clustered.rs index 712051157df10..6d3fc29b7c97e 100644 --- a/crates/bevy_pbr/src/decal/clustered.rs +++ b/crates/bevy_pbr/src/decal/clustered.rs @@ -185,7 +185,7 @@ impl Plugin for ClusteredDecalPlugin { } impl SyncComponent for ClusteredDecal { - type Out = Self; + type Target = Self; } // This is needed because of the orphan rule not allowing implementing diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 8a6ac08df3b6a..5ca986f2e3686 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -382,17 +382,17 @@ pub fn stbn_placeholder() -> Image { } impl SyncComponent for DirectionalLight { - type Out = Self; + type Target = Self; } impl SyncComponent for PointLight { - type Out = Self; + type Target = Self; } impl SyncComponent for SpotLight { - type Out = Self; + type Target = Self; } impl SyncComponent for AmbientLight { - type Out = Self; + type Target = Self; } impl SyncComponent for ShadowFilteringMethod { - type Out = Self; + type Target = Self; } diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index 99c30e2d81180..dc99a9688a97e 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -1107,5 +1107,5 @@ pub fn generate_environment_map_light( } impl SyncComponent for GeneratedEnvironmentMapLight { - type Out = RenderEnvironmentMap; + type Target = RenderEnvironmentMap; } diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 9c5ec9bc63837..ae556bbc7b024 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -516,12 +516,17 @@ pub fn prepare_ssr_settings( } impl SyncComponent for ScreenSpaceReflections { - type Out = ScreenSpaceReflectionsUniform; + type Target = ( + ScreenSpaceReflectionsUniform, + ViewScreenSpaceReflectionsUniformOffset, + ScreenSpaceReflectionsPipelineId, + ); } impl ExtractComponent for ScreenSpaceReflections { type QueryData = Read; type QueryFilter = (); + type Out = ScreenSpaceReflectionsUniform; fn extract_component(settings: QueryItem<'_, '_, Self::QueryData>) -> Option { if !DEPTH_TEXTURE_SAMPLING_SUPPORTED { diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 8248baa6b62d1..822f4d0c227d5 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -108,5 +108,5 @@ impl Plugin for VolumetricFogPlugin { } impl SyncComponent for FogVolume { - type Out = Self; + type Target = Self; } diff --git a/crates/bevy_post_process/src/bloom/settings.rs b/crates/bevy_post_process/src/bloom/settings.rs index 9d42ea641a00c..fc6ada8d82e7e 100644 --- a/crates/bevy_post_process/src/bloom/settings.rs +++ b/crates/bevy_post_process/src/bloom/settings.rs @@ -221,12 +221,13 @@ pub enum BloomCompositeMode { } impl SyncComponent for Bloom { - type Out = (Self, BloomUniforms); + type Target = (Self, BloomUniforms); } impl ExtractComponent for Bloom { type QueryData = (&'static Self, &'static Camera); type QueryFilter = With; + type Out = (Self, BloomUniforms); fn extract_component((bloom, camera): QueryItem<'_, '_, Self::QueryData>) -> Option { match ( diff --git a/crates/bevy_post_process/src/dof/mod.rs b/crates/bevy_post_process/src/dof/mod.rs index 64a280964fa39..02f20f24437f5 100644 --- a/crates/bevy_post_process/src/dof/mod.rs +++ b/crates/bevy_post_process/src/dof/mod.rs @@ -655,7 +655,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline { } impl SyncComponent for DepthOfField { - type Out = ( + type Target = ( DepthOfField, DepthOfFieldUniform, DepthOfFieldPipelines, @@ -683,7 +683,7 @@ fn extract_depth_of_field_settings( // Depth of field is nonsensical without a perspective projection. let Projection::Perspective(ref perspective_projection) = *projection else { - entity_commands.remove::<::Out>(); + entity_commands.remove::<::Target>(); continue; }; diff --git a/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs b/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs index 4dc06716db368..171e518cff400 100644 --- a/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs +++ b/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs @@ -86,12 +86,13 @@ impl Default for ChromaticAberration { } impl SyncComponent for ChromaticAberration { - type Out = Self; + type Target = Self; } impl ExtractComponent for ChromaticAberration { type QueryData = Read; type QueryFilter = With; + type Out = Self; fn extract_component( chromatic_aberration: QueryItem<'_, '_, Self::QueryData>, diff --git a/crates/bevy_post_process/src/effect_stack/vignette.rs b/crates/bevy_post_process/src/effect_stack/vignette.rs index 45740fbd4bd68..77e26d165f08a 100644 --- a/crates/bevy_post_process/src/effect_stack/vignette.rs +++ b/crates/bevy_post_process/src/effect_stack/vignette.rs @@ -103,12 +103,13 @@ impl Default for Vignette { } impl SyncComponent for Vignette { - type Out = Self; + type Target = Self; } impl ExtractComponent for Vignette { type QueryData = Read; type QueryFilter = With; + type Out = Self; fn extract_component(vignette: QueryItem<'_, '_, Self::QueryData>) -> Option { // Skip the postprocessing phase entirely if the intensity is zero. diff --git a/crates/bevy_post_process/src/motion_blur/mod.rs b/crates/bevy_post_process/src/motion_blur/mod.rs index b6c34815cb533..92e1c41178319 100644 --- a/crates/bevy_post_process/src/motion_blur/mod.rs +++ b/crates/bevy_post_process/src/motion_blur/mod.rs @@ -116,12 +116,13 @@ impl Default for MotionBlur { } impl SyncComponent for MotionBlur { - type Out = MotionBlurUniform; + type Target = MotionBlurUniform; } impl ExtractComponent for MotionBlur { type QueryData = &'static Self; type QueryFilter = With; + type Out = MotionBlurUniform; fn extract_component(item: QueryItem) -> Option { Some(MotionBlurUniform { diff --git a/crates/bevy_render/macros/src/extract_component.rs b/crates/bevy_render/macros/src/extract_component.rs index 8b57d0a47d646..5521f9429854b 100644 --- a/crates/bevy_render/macros/src/extract_component.rs +++ b/crates/bevy_render/macros/src/extract_component.rs @@ -38,15 +38,34 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { } }; + let sync_target = if let Some(attr) = ast + .attrs + .iter() + .find(|a| a.path().is_ident("extract_component_sync_target")) + { + let sync_target = match attr.parse_args::() { + Ok(sync_target) => sync_target, + Err(e) => return e.to_compile_error().into(), + }; + + quote! { + #sync_target + } + } else { + quote! { + Self + } + }; + TokenStream::from(quote! { impl #impl_generics #bevy_render_path::sync_component::SyncComponent for #struct_name #type_generics #where_clause { - type Out = Self; + type Target = #sync_target; } impl #impl_generics #bevy_render_path::extract_component::ExtractComponent for #struct_name #type_generics #where_clause { type QueryData = &'static Self; - type QueryFilter = #filter; + type Out = Self; fn extract_component(item: #bevy_ecs_path::query::QueryItem<'_, '_, Self::QueryData>) -> Option { Some(item.clone()) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 40c01e10f6ec0..db14126dc4ddb 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -51,7 +51,10 @@ pub fn derive_extract_resource(input: TokenStream) -> TokenStream { /// pub should_bar: bool, /// } /// ``` -#[proc_macro_derive(ExtractComponent, attributes(extract_component_filter))] +#[proc_macro_derive( + ExtractComponent, + attributes(extract_component_filter, extract_component_sync_target) +)] pub fn derive_extract_component(input: TokenStream) -> TokenStream { extract_component::derive_extract_component(input) } diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 3d55e16a3b4c5..df661552b6d5e 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -125,12 +125,13 @@ impl ExtractResource for ClearColor { } impl SyncComponent for CameraMainTextureUsages { - type Out = Self; + type Target = Self; } impl ExtractComponent for CameraMainTextureUsages { type QueryData = &'static Self; type QueryFilter = (); + type Out = Self; fn extract_component(item: QueryItem) -> Option { Some(*item) @@ -138,12 +139,13 @@ impl ExtractComponent for CameraMainTextureUsages { } impl SyncComponent for Camera2d { - type Out = Self; + type Target = Self; } impl ExtractComponent for Camera2d { type QueryData = &'static Self; type QueryFilter = With; + type Out = Self; fn extract_component(item: QueryItem) -> Option { Some(item.clone()) @@ -151,12 +153,13 @@ impl ExtractComponent for Camera2d { } impl SyncComponent for Camera3d { - type Out = Self; + type Target = Self; } impl ExtractComponent for Camera3d { type QueryData = &'static Self; type QueryFilter = With; + type Out = Self; fn extract_component(item: QueryItem) -> Option { Some(item.clone()) diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 0aa6aafc99696..52ddbc75aa6fd 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -8,6 +8,7 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_camera::visibility::ViewVisibility; use bevy_ecs::{ + bundle::NoBundleEffect, component::Component, prelude::*, query::{QueryFilter, QueryItem, ReadOnlyQueryData}, @@ -46,8 +47,13 @@ pub trait ExtractComponent: SyncComponent { type QueryData: ReadOnlyQueryData; /// Filters the entities with additional constraints. type QueryFilter: QueryFilter; + /// The output from extraction. + type Out: Bundle; /// Defines how the component is transferred into the "render world". + /// + /// Returning `None` based on the queried item will remove the `Target` of `SyncComponent` from the entity in + /// the render world. fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } @@ -203,7 +209,7 @@ fn extract_components, F>( if let Some(component) = C::extract_component(query_item) { values.push((entity, component)); } else { - commands.entity(entity).remove::(); + commands.entity(entity).remove::(); } } *previous_len = values.len(); @@ -222,7 +228,7 @@ fn extract_visible_components, F>( if let Some(component) = C::extract_component(query_item) { values.push((entity, component)); } else { - commands.entity(entity).remove::(); + commands.entity(entity).remove::(); } } } diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index 47a636ab48865..40b5f801bf982 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -151,13 +151,13 @@ mod test { struct RenderComponentNoExtract; impl SyncComponent for RenderComponent { - type Out = (RenderComponent, RenderComponentExtra); + type Target = (RenderComponent, RenderComponentExtra); } impl ExtractComponent for RenderComponent { type QueryData = &'static Self; - type QueryFilter = (); + type Out = (RenderComponent, RenderComponentExtra); fn extract_component( _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>, diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_render/src/sync_component.rs index fd89f5d74166e..b9245ea9e721f 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_render/src/sync_component.rs @@ -34,25 +34,15 @@ impl, F> Default for SyncComponentPlugin { /// Trait that links components from the main world with output components in /// the render world. It is used by [`SyncComponentPlugin`]. /// -/// This trait is a subtrait of [`ExtractComponent`], which uses it to determine -/// which components to extract. -/// /// The marker type `F` is only used as a way to bypass the orphan rules. To /// implement the trait for a foreign type you can use a local type as the /// marker, e.g. the type of the plugin that calls [`SyncComponentPlugin`]. -/// -/// [`ExtractComponent`]: crate::extract_component::ExtractComponent pub trait SyncComponent: Component { /// Describes what components should be removed from the render world if the /// implementing component is removed. - /// - /// It is also used by the [`ExtractComponent`] trait to determine which - /// components are generated during extraction. - /// - /// [`ExtractComponent`]: crate::extract_component::ExtractComponent - type Out: Bundle; + type Target: Bundle; // TODO: https://github.com/rust-lang/rust/issues/29661 - // type Out: Component = Self; + // type Target: Component = Self; } impl, F: Send + Sync + 'static> Plugin for SyncComponentPlugin { @@ -66,7 +56,7 @@ impl, F: Send + Sync + 'static> Plugin for SyncComponentPlug pending.push(EntityRecord::ComponentRemoved( context.entity, |mut entity| { - entity.remove::(); + entity.remove::(); }, )); }); diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index ea21b2643e7d6..b7e7e0b5c664a 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -126,7 +126,7 @@ fn star( pub struct ColoredMesh2d; impl SyncComponent for ColoredMesh2d { - type Out = Self; + type Target = Self; } /// Custom pipeline for 2d meshes with vertex colors diff --git a/examples/shader_advanced/custom_shader_instancing.rs b/examples/shader_advanced/custom_shader_instancing.rs index 85de265da88b1..700ae1a3397e4 100644 --- a/examples/shader_advanced/custom_shader_instancing.rs +++ b/examples/shader_advanced/custom_shader_instancing.rs @@ -87,12 +87,13 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { struct InstanceMaterialData(Vec); impl SyncComponent for InstanceMaterialData { - type Out = Self; + type Target = Self; } impl ExtractComponent for InstanceMaterialData { type QueryData = &'static InstanceMaterialData; type QueryFilter = (); + type Out = Self; fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option { Some(InstanceMaterialData(item.0.clone())) diff --git a/release-content/migration-guides/extract_refactor.md b/release-content/migration-guides/extract_refactor.md index bc33d5bb9bf96..e405a1586e541 100644 --- a/release-content/migration-guides/extract_refactor.md +++ b/release-content/migration-guides/extract_refactor.md @@ -1,13 +1,17 @@ --- title: "`ExtractComponent` refactor" -pull_requests: [22766] +pull_requests: [22766, 23334] --- -The `Out` type from `ExtractComponent` has been split into a separate `SyncComponent` trait. +Previously, `SyncComponentPlugin`/`ExtractComponentPlugin` would despawn the render entity thus removing all the derived components if the component was removed. Now the render entity is no longer despawned and only the `Target` components of `SyncComponent` trait are removed. -Both traits have also gotten an optional marker type that can be used to bypass orphan rules, see the docs for details. +`SyncComponent` is a subtrait of `ExtractComponent` and you must implement it to clean up extracted and derived components. ```rust,ignore +impl SyncComponent for MyComponent { + type Target = (Self, OtherDerivedComponents); +} + impl ExtractComponent for MyComponent { type QueryData = (); type QueryFilter = (); @@ -21,21 +25,11 @@ impl ExtractComponent for MyComponent { } ``` -After: - +You can also specify the sync target (default to `Self`) using `extract_component_sync_target` attribute in derive macros. ```rust,ignore -impl SyncComponent for MyComponent { - type Out = Self; -} - -impl ExtractComponent for MyComponent { - type QueryData = (); - type QueryFilter = (); - - fn extract_component( - item: QueryItem<'_, '_, Self::QueryData>, - ) -> Option { - Some(*item) - } -} +#[derive(Component, ExtractComponent)] +#[extract_component_sync_target((Self, OtherDerivedComponents))] +struct MyComponent; ``` + +Both `SyncComponent` and `ExtractComponent` have also gotten an optional marker type that can be used to bypass orphan rules, see the docs for details. From 89b54efe2bcd07b0691b5e8f309e4d551a2ccb8f Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Thu, 12 Mar 2026 23:42:12 +0800 Subject: [PATCH 2/5] doc --- crates/bevy_render/macros/src/lib.rs | 2 ++ release-content/migration-guides/extract_refactor.md | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index db14126dc4ddb..15115b2157987 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -32,6 +32,7 @@ pub fn derive_extract_resource(input: TokenStream) -> TokenStream { /// See `ExtractComponentPlugin` to actually perform the extraction. /// /// If you only want to extract a component conditionally, you may use the `extract_component_filter` attribute. +/// To specify `SyncComponent::Target`, you can use the `extract_component_sync_target` attribute. /// /// # Example /// @@ -41,6 +42,7 @@ pub fn derive_extract_resource(input: TokenStream) -> TokenStream { /// /// #[derive(Component, Clone, ExtractComponent)] /// #[extract_component_filter(With)] +/// #[extract_component_sync_target((Self, OtherNeedsCleanup))] /// pub struct Foo { /// pub should_foo: bool, /// } diff --git a/release-content/migration-guides/extract_refactor.md b/release-content/migration-guides/extract_refactor.md index e405a1586e541..fc3343d7e8b82 100644 --- a/release-content/migration-guides/extract_refactor.md +++ b/release-content/migration-guides/extract_refactor.md @@ -26,6 +26,7 @@ impl ExtractComponent for MyComponent { ``` You can also specify the sync target (default to `Self`) using `extract_component_sync_target` attribute in derive macros. + ```rust,ignore #[derive(Component, ExtractComponent)] #[extract_component_sync_target((Self, OtherDerivedComponents))] From 1d7d4eb77e23df3b7420ff25c8d1fdd3e4ad9f51 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Sat, 14 Mar 2026 14:11:45 +0800 Subject: [PATCH 3/5] doc --- crates/bevy_render/src/extract_component.rs | 9 +++++++-- crates/bevy_render/src/sync_component.rs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 52ddbc75aa6fd..1ebfcd033e871 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -47,12 +47,17 @@ pub trait ExtractComponent: SyncComponent { type QueryData: ReadOnlyQueryData; /// Filters the entities with additional constraints. type QueryFilter: QueryFilter; - /// The output from extraction. + /// The output from extraction, i.e. [`ExtractComponent::extract_component`]. + /// + /// The output components won't be removed automatically from the render world if the implementing component is removed, + /// unless you set them in the [`SyncComponent::Target`]. type Out: Bundle; + // TODO: https://github.com/rust-lang/rust/issues/29661 + // type Out: Bundle = Self; /// Defines how the component is transferred into the "render world". /// - /// Returning `None` based on the queried item will remove the `Target` of `SyncComponent` from the entity in + /// Returning `None` based on the queried item will remove the [`SyncComponent::Target`] from the entity in /// the render world. fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_render/src/sync_component.rs index b9245ea9e721f..e2c85d470fbfb 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_render/src/sync_component.rs @@ -42,7 +42,7 @@ pub trait SyncComponent: Component { /// implementing component is removed. type Target: Bundle; // TODO: https://github.com/rust-lang/rust/issues/29661 - // type Target: Component = Self; + // type Target: Bundle = Self; } impl, F: Send + Sync + 'static> Plugin for SyncComponentPlugin { From 8d747bd240163e505d71fe077d0796247e755376 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Tue, 17 Mar 2026 09:48:56 +0800 Subject: [PATCH 4/5] Fix togging bloom in `transmission` example --- examples/3d/transmission.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/3d/transmission.rs b/examples/3d/transmission.rs index 3e969ffdfa3ac..52cdbb41c48ca 100644 --- a/examples/3d/transmission.rs +++ b/examples/3d/transmission.rs @@ -461,9 +461,11 @@ fn example_control_system( if input.just_pressed(KeyCode::KeyH) { if hdr { - commands.entity(camera_entity).remove::(); + commands.entity(camera_entity).remove::<(Bloom, Hdr)>(); } else { - commands.entity(camera_entity).insert(Hdr); + commands + .entity(camera_entity) + .insert((Bloom::default(), Hdr)); } } From 61468f091ad36d9925547dfa7eb74eb6371903e0 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Sat, 28 Mar 2026 09:59:42 +0800 Subject: [PATCH 5/5] revert `transmission` example --- examples/3d/transmission.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/3d/transmission.rs b/examples/3d/transmission.rs index 52cdbb41c48ca..3e969ffdfa3ac 100644 --- a/examples/3d/transmission.rs +++ b/examples/3d/transmission.rs @@ -461,11 +461,9 @@ fn example_control_system( if input.just_pressed(KeyCode::KeyH) { if hdr { - commands.entity(camera_entity).remove::<(Bloom, Hdr)>(); + commands.entity(camera_entity).remove::(); } else { - commands - .entity(camera_entity) - .insert((Bloom::default(), Hdr)); + commands.entity(camera_entity).insert(Hdr); } }