diff --git a/_release-content/migration-guides/extract_refactor.md b/_release-content/migration-guides/extract_refactor.md index bc33d5bb9bf96..fc3343d7e8b82 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,12 @@ 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. 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 0862234c44cfc..0caf090e450cc 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 12e1468e45b55..2d15f949378f4 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -130,7 +130,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 0356ce615d7de..8ac7fd7e2b5cf 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 91a568aa93c84..34d165ba258fc 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 1cfda9f85b087..a8925259d8b61 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 371e298745054..31f9f2e436f40 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 e603f77eb739b..40ef01c3a2f93 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -381,17 +381,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 0c8f8ca05b03a..65ff09dab295c 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 2c4c031e136f7..6fe8625aa3b6b 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 2b753f946dd76..7cd15a4121667 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 b9bd035492ed8..81ed7bf118f3e 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..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, /// } @@ -51,7 +53,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 f1c1fd88ff1fb..199e07f5a0acf 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -126,12 +126,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) @@ -139,12 +140,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()) @@ -152,12 +154,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 255850610701f..4d32b3f369a13 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -6,6 +6,7 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_camera::visibility::ViewVisibility; use bevy_ecs::{ + bundle::NoBundleEffect, prelude::*, query::{QueryFilter, QueryItem, ReadOnlyQueryData}, }; @@ -31,8 +32,18 @@ pub trait ExtractComponent: SyncComponent { type QueryData: ReadOnlyQueryData; /// Filters the entities with additional constraints. type QueryFilter: QueryFilter; + /// 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 [`SyncComponent::Target`] from the entity in + /// the render world. fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } @@ -94,7 +105,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(); @@ -113,7 +124,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..e2c85d470fbfb 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: Bundle = 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 6d292168d2490..5dc16ec38d7c4 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 29295e67b745d..96087d9fbb083 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()))