From 751031749f80b67bd2f31870d2ac4e5da6838330 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 27 May 2026 18:09:46 +0800 Subject: [PATCH 01/14] Add Default to AppLabel's --- crates/bevy_app/src/app.rs | 2 +- crates/bevy_app/src/sub_app.rs | 2 +- crates/bevy_render/src/pipelined_rendering.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index bd6bba5dea23c..64d817a7a8ff6 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1954,7 +1954,7 @@ mod tests { fn test_extract_sees_changes() { use super::AppLabel; - #[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)] + #[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug, Default)] struct MySubApp; #[derive(Resource)] diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index c7194135535ef..347f87986fd16 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -34,7 +34,7 @@ type ExtractFn = Box; /// #[derive(Resource, Default)] /// struct Val(pub i32); /// -/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] +/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel, Default)] /// struct ExampleApp; /// /// // Create an app with a certain resource. diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index 9806ff45015cd..6f0b2b1fa6b83 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -14,7 +14,7 @@ use crate::RenderApp; /// /// The Main schedule of this app can be used to run logic after the render schedule starts, but /// before I/O processing. This can be useful for something like frame pacing. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel, Default)] pub struct RenderExtractApp; /// Channels used by the main app to send and receive the render app. From ff2fd568579995cb1dbb7c7062d026fccffa58af Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 27 May 2026 21:56:17 +0800 Subject: [PATCH 02/14] Generalise SyncToRenderWorld and RenderEntity --- crates/bevy_render/src/sync_world.rs | 40 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 76e98ec266592..8a73bab794c0f 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -111,7 +111,8 @@ impl Plugin for SyncWorldPlugin { ); } } -/// Marker component that indicates that its entity needs to be synchronized to the render world. + +/// Marker component that indicates that its entity needs to be synchronized to the sub world. /// /// This component is automatically added as a required component by [`ExtractComponentPlugin`] and [`SyncComponentPlugin`]. /// For more information see [`SyncWorldPlugin`]. @@ -124,36 +125,41 @@ impl Plugin for SyncWorldPlugin { #[derive(Component, Copy, Clone, Debug, Default, Reflect)] #[reflect(Component, Default, Clone)] #[component(storage = "SparseSet")] -pub struct SyncToRenderWorld; +pub struct SyncToSubWorld(PhantomData); + +pub type SyncToRenderWorld = SyncToSubWorld; -/// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity. +/// Component added on the main world entities that are synced to the Sub World in order to keep track of the corresponding sub world entity. /// -/// Can also be used as a newtype wrapper for render world entities. +/// Can also be used as a newtype wrapper for sub world entities. #[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Reflect)] #[component(clone_behavior = Ignore)] #[reflect(Component, Clone)] -pub struct RenderEntity(Entity); -impl RenderEntity { +pub struct SubEntity(#[deref] Entity, PhantomData); + +pub type RenderEntity = SubEntity; + +impl SubEntity { #[inline] pub fn id(&self) -> Entity { self.0 } } -impl From for RenderEntity { +impl From for SubEntity { fn from(entity: Entity) -> Self { - RenderEntity(entity) + SubEntity(entity, PhantomData) } } -impl ContainsEntity for RenderEntity { +impl ContainsEntity for SubEntity { fn entity(&self) -> Entity { self.id() } } -// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. -unsafe impl EntityEquivalent for RenderEntity {} +// SAFETY: SubEntity is a newtype around Entity that derives its comparison traits. +unsafe impl EntityEquivalent for SubEntity {} /// Component added on the render world entities to keep track of the corresponding main world entity. /// @@ -180,7 +186,7 @@ impl ContainsEntity for MainEntity { } } -// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. +// SAFETY: MainEntity is a newtype around Entity that derives its comparison traits. unsafe impl EntityEquivalent for MainEntity {} /// A [`HashMap`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]. @@ -236,7 +242,7 @@ pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut Worl bevy_ecs::world::ComponentEntry::Vacant(entry) => { let id = render_world.spawn(MainEntity(e)).id(); - entry.insert(RenderEntity(id)); + entry.insert(SubEntity::(id, PhantomData)); } }; } @@ -540,6 +546,8 @@ mod render_entities_world_query_impls { #[cfg(test)] mod tests { + use core::marker::PhantomData; + use bevy_ecs::{ component::Component, entity::Entity, @@ -550,9 +558,11 @@ mod tests { world::World, }; + use crate::RenderApp; + use super::{ entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, RenderEntity, - SyncToRenderWorld, + SyncToRenderWorld, SyncToSubWorld, }; #[derive(Component)] @@ -588,7 +598,7 @@ mod tests { let main_entity = main_world .spawn(RenderDataComponent) // indicates that its entity needs to be synchronized to the render world - .insert(SyncToRenderWorld) + .insert(SyncToSubWorld::(PhantomData)) .id(); entity_sync_system(&mut main_world, &mut render_world); From 8d7d5a4f70492e3287b47ef97046690bbfafa31f Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 27 May 2026 22:47:56 +0800 Subject: [PATCH 03/14] Entity sync --- crates/bevy_render/src/extract_plugin.rs | 2 +- crates/bevy_render/src/sync_component.rs | 2 +- crates/bevy_render/src/sync_world.rs | 164 ++++++++++++----------- 3 files changed, 89 insertions(+), 79 deletions(-) diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index a5c478f81f5a9..b177ffb465268 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -65,7 +65,7 @@ impl Plugin for ExtractPlugin { { #[cfg(feature = "trace")] let _stage_span = bevy_log::info_span!("entity_sync").entered(); - entity_sync_system(main_world, render_world); + entity_sync_system::(main_world, render_world); } // run extract schedule diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_render/src/sync_component.rs index 5ec00b2ffc896..339c3fc8632a7 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_render/src/sync_component.rs @@ -57,7 +57,7 @@ impl, F: Send + Sync + 'static> Plugin app.world_mut() .register_component_hooks::() .on_remove(|mut world, context| { - let mut pending = world.resource_mut::(); + let mut pending = world.resource_mut::>(); pending.push(EntityRecord::ComponentRemoved( context.entity, |mut entity| { diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 8a73bab794c0f..2d13d4bae5c63 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -94,15 +94,15 @@ pub struct SyncWorldPlugin; impl Plugin for SyncWorldPlugin { fn build(&self, app: &mut bevy_app::App) { - app.init_resource::(); + app.init_resource::>(); app.add_observer( - |add: On, mut pending: ResMut| { + |add: On, mut pending: ResMut>| { pending.push(EntityRecord::Added(add.entity)); }, ); app.add_observer( |remove: On, - mut pending: ResMut, + mut pending: ResMut>, query: Query<&RenderEntity>| { if let Ok(e) = query.get(remove.entity) { pending.push(EntityRecord::Removed(*e)); @@ -161,7 +161,7 @@ impl ContainsEntity for SubEntity { // SAFETY: SubEntity is a newtype around Entity that derives its comparison traits. unsafe impl EntityEquivalent for SubEntity {} -/// Component added on the render world entities to keep track of the corresponding main world entity. +/// Component added on the sub world entities to keep track of the corresponding main world entity. /// /// Can also be used as a newtype wrapper for main world entities. #[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Reflect)] @@ -208,56 +208,61 @@ pub struct TemporaryEntity(PhantomDat pub type TemporaryRenderEntity = TemporaryEntity; -/// A record enum to what entities with [`SyncToRenderWorld`] have been added or removed. +/// A record enum to what entities with [`SyncToSubWorld`] have been added or removed. #[derive(Debug)] -pub(crate) enum EntityRecord { - /// When an entity is spawned on the main world, notify the render world so that it can spawn a corresponding +pub(crate) enum EntityRecord { + /// When an entity is spawned on the main world, notify the sub world so that it can spawn a corresponding /// entity. This contains the main world entity. Added(Entity), - /// When an entity is despawned on the main world, notify the render world so that the corresponding entity can be - /// despawned. This contains the render world entity. - Removed(RenderEntity), - /// When a component is removed from an entity, notify the render world so that the corresponding component can be + /// When an entity is despawned on the main world, notify the sub world so that the corresponding entity can be + /// despawned. This contains the sub world entity. + Removed(SubEntity), + /// When a component is removed from an entity, notify the sub world so that the corresponding component can be /// removed. This contains the main world entity. ComponentRemoved(Entity, fn(EntityWorldMut<'_>)), } // Entity Record in MainWorld pending to Sync #[derive(Resource, Default, Deref, DerefMut)] -pub(crate) struct PendingSyncEntity { - records: Vec, +pub(crate) struct PendingSyncEntity { + #[deref] + records: Vec>, + marker: PhantomData, } -pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut World) { - main_world.resource_scope(|world, mut pending: Mut| { +pub(crate) fn entity_sync_system( + main_world: &mut World, + sub_world: &mut World, +) { + main_world.resource_scope(|world, mut pending: Mut>| { // TODO : batching record for record in pending.drain(..) { match record { EntityRecord::Added(e) => { if let Ok(mut main_entity) = world.get_entity_mut(e) { - match main_entity.entry::() { + match main_entity.entry::>() { bevy_ecs::world::ComponentEntry::Occupied(_) => { panic!("Attempting to synchronize an entity that has already been synchronized!"); } bevy_ecs::world::ComponentEntry::Vacant(entry) => { - let id = render_world.spawn(MainEntity(e)).id(); + let id = sub_world.spawn(MainEntity(e)).id(); - entry.insert(SubEntity::(id, PhantomData)); + entry.insert(SubEntity::(id, PhantomData)); } }; } } - EntityRecord::Removed(render_entity) => { - if let Ok(ec) = render_world.get_entity_mut(render_entity.id()) { + EntityRecord::Removed(sub_entity) => { + if let Ok(ec) = sub_world.get_entity_mut(sub_entity.id()) { ec.despawn(); }; } EntityRecord::ComponentRemoved(main_entity, removal_function) => { - let Some(render_entity) = world.get::(main_entity) else { + let Some(sub_entity) = world.get::>(main_entity) else { continue; }; - if let Ok(render_world_entity) = render_world.get_entity_mut(render_entity.id()) { - removal_function(render_world_entity); + if let Ok(sub_world_entity) = sub_world.get_entity_mut(sub_entity.id()) { + removal_function(sub_world_entity); } }, } @@ -283,11 +288,12 @@ pub(crate) fn despawn_temporary_entities( /// This module exists to keep the complex unsafe code out of the main module. /// -/// The implementations for both [`MainEntity`] and [`RenderEntity`] should stay in sync, +/// The implementations for both [`MainEntity`] and [`SubEntity`] should stay in sync, /// and are based off of the `&T` implementation in `bevy_ecs`. -mod render_entities_world_query_impls { - use super::{MainEntity, RenderEntity}; +mod sub_entities_world_query_impls { + use super::{MainEntity, SubEntity}; + use bevy_app::AppLabel; use bevy_ecs::{ archetype::Archetype, change_detection::Tick, @@ -301,11 +307,11 @@ mod render_entities_world_query_impls { world::{unsafe_world_cell::UnsafeWorldCell, World}, }; - // SAFETY: defers completely to `&RenderEntity` implementation, + // SAFETY: defers completely to `&SubEntity` implementation, // and then only modifies the output safely. - unsafe impl WorldQuery for RenderEntity { - type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>; - type State = <&'static RenderEntity as WorldQuery>::State; + unsafe impl WorldQuery for SubEntity { + type Fetch<'w> = <&'static SubEntity as WorldQuery>::Fetch<'w>; + type State = <&'static SubEntity as WorldQuery>::State; fn shrink_fetch<'wlong: 'wshort, 'wshort>( fetch: Self::Fetch<'wlong>, @@ -320,13 +326,13 @@ mod render_entities_world_query_impls { last_run: Tick, this_run: Tick, ) -> Self::Fetch<'w> { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. unsafe { - <&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) + <&SubEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) } } - const IS_DENSE: bool = <&'static RenderEntity as WorldQuery>::IS_DENSE; + const IS_DENSE: bool = <&'static SubEntity as WorldQuery>::IS_DENSE; #[inline] unsafe fn set_archetype<'w, 's>( @@ -335,9 +341,9 @@ mod render_entities_world_query_impls { archetype: &'w Archetype, table: &'w Table, ) { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. unsafe { - <&RenderEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table); + <&SubEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table); } } @@ -347,36 +353,36 @@ mod render_entities_world_query_impls { &component_id: &'s ComponentId, table: &'w Table, ) { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. - unsafe { <&RenderEntity as WorldQuery>::set_table(fetch, &component_id, table) } + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. + unsafe { <&SubEntity as WorldQuery>::set_table(fetch, &component_id, table) } } fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { - <&RenderEntity as WorldQuery>::update_component_access(&component_id, access); + <&SubEntity as WorldQuery>::update_component_access(&component_id, access); } fn init_state(world: &mut World) -> ComponentId { - <&RenderEntity as WorldQuery>::init_state(world) + <&SubEntity as WorldQuery>::init_state(world) } fn get_state(components: &Components) -> Option { - <&RenderEntity as WorldQuery>::get_state(components) + <&SubEntity as WorldQuery>::get_state(components) } fn matches_component_set( &state: &ComponentId, set_contains_id: &impl Fn(ComponentId) -> bool, ) -> bool { - <&RenderEntity as WorldQuery>::matches_component_set(&state, set_contains_id) + <&SubEntity as WorldQuery>::matches_component_set(&state, set_contains_id) } } // SAFETY: Component access of Self::ReadOnly is a subset of Self. // Self::ReadOnly matches exactly the same archetypes/tables as Self. - unsafe impl QueryData for RenderEntity { + unsafe impl QueryData for SubEntity { const IS_READ_ONLY: bool = true; const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL; - type ReadOnly = RenderEntity; + type ReadOnly = SubEntity; type Item<'w, 's> = Entity; fn shrink<'wlong: 'wshort, 'wshort, 's>( @@ -392,37 +398,37 @@ mod render_entities_world_query_impls { entity: Entity, table_row: TableRow, ) -> Option> { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. let component = - unsafe { <&RenderEntity as QueryData>::fetch(state, fetch, entity, table_row) }; - component.map(RenderEntity::id) + unsafe { <&SubEntity as QueryData>::fetch(state, fetch, entity, table_row) }; + component.map(SubEntity::id) } fn iter_access( state: &Self::State, ) -> impl Iterator> { - <&RenderEntity as QueryData>::iter_access(state) + <&SubEntity as QueryData>::iter_access(state) } } /// SAFETY: access is read only and only on the current entity - unsafe impl IterQueryData for RenderEntity {} + unsafe impl IterQueryData for SubEntity {} /// SAFETY: access is read only - unsafe impl ReadOnlyQueryData for RenderEntity {} + unsafe impl ReadOnlyQueryData for SubEntity {} /// SAFETY: access is only on the current entity - unsafe impl SingleEntityQueryData for RenderEntity {} + unsafe impl SingleEntityQueryData for SubEntity {} - impl ArchetypeQueryData for RenderEntity {} + impl ArchetypeQueryData for SubEntity {} - impl ReleaseStateQueryData for RenderEntity { + impl ReleaseStateQueryData for SubEntity { fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { item } } - // SAFETY: defers completely to `&RenderEntity` implementation, + // SAFETY: defers completely to `&SubEntity` implementation, // and then only modifies the output safely. unsafe impl WorldQuery for MainEntity { type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>; @@ -548,6 +554,7 @@ mod render_entities_world_query_impls { mod tests { use core::marker::PhantomData; + use bevy_app::AppLabel; use bevy_ecs::{ component::Component, entity::Entity, @@ -558,31 +565,32 @@ mod tests { world::World, }; - use crate::RenderApp; - use super::{ - entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, RenderEntity, - SyncToRenderWorld, SyncToSubWorld, + entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, SubEntity, SyncToSubWorld, }; #[derive(Component)] - struct RenderDataComponent; + struct SubDataComponent; + + #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] + struct ExtractApp; #[test] fn sync_world() { let mut main_world = World::new(); - let mut render_world = World::new(); - main_world.init_resource::(); + let mut sub_world = World::new(); + main_world.init_resource::>(); main_world.add_observer( - |add: On, mut pending: ResMut| { + |add: On>, + mut pending: ResMut>| { pending.push(EntityRecord::Added(add.entity)); }, ); main_world.add_observer( - |remove: On, - mut pending: ResMut, - query: Query<&RenderEntity>| { + |remove: On>, + mut pending: ResMut>, + query: Query<&SubEntity>| { if let Ok(e) = query.get(remove.entity) { pending.push(EntityRecord::Removed(*e)); }; @@ -596,25 +604,27 @@ mod tests { // spawn let main_entity = main_world - .spawn(RenderDataComponent) - // indicates that its entity needs to be synchronized to the render world - .insert(SyncToSubWorld::(PhantomData)) + .spawn(SubDataComponent) + // indicates that its entity needs to be synchronized to the sub world + .insert(SyncToSubWorld::(PhantomData)) .id(); - entity_sync_system(&mut main_world, &mut render_world); + entity_sync_system::(&mut main_world, &mut sub_world); - let mut q = render_world.query_filtered::>(); + let mut q = sub_world.query_filtered::>(); // Only one synchronized entity - assert!(q.iter(&render_world).count() == 1); + assert!(q.iter(&sub_world).count() == 1); - let render_entity = q.single(&render_world).unwrap(); - let render_entity_component = main_world.get::(main_entity).unwrap(); + let sub_entity = q.single(&sub_world).unwrap(); + let sub_entity_component = main_world + .get::>(main_entity) + .unwrap(); - assert!(render_entity_component.id() == render_entity); + assert!(sub_entity_component.id() == sub_entity); - let main_entity_component = render_world - .get::(render_entity_component.id()) + let main_entity_component = sub_world + .get::(sub_entity_component.id()) .unwrap(); assert!(main_entity_component.id() == main_entity); @@ -622,9 +632,9 @@ mod tests { // despawn main_world.despawn(main_entity); - entity_sync_system(&mut main_world, &mut render_world); + entity_sync_system::(&mut main_world, &mut sub_world); // Only one synchronized entity - assert!(q.iter(&render_world).count() == 0); + assert!(q.iter(&sub_world).count() == 0); } } From c7052bdd50f0813240497560980ed4498faf601d Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 27 May 2026 22:51:30 +0800 Subject: [PATCH 04/14] Extract instances --- .../src/light_probe/environment_map.rs | 3 +- crates/bevy_pbr/src/light_probe/mod.rs | 2 +- crates/bevy_render/src/extract_instances.rs | 75 ++++++++++--------- crates/bevy_render/src/sync_world.rs | 3 +- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 3ba44e3247fb4..329cdbb3c5bc6 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -61,6 +61,7 @@ use bevy_render::{ }, renderer::{RenderAdapter, RenderDevice}, texture::{FallbackImage, GpuImage}, + RenderApp, }; use core::{num::NonZero, ops::Deref}; @@ -139,7 +140,7 @@ pub struct EnvironmentMapViewLightProbeInfo { pub(crate) rotation: Quat, } -impl ExtractInstance for EnvironmentMapIds { +impl ExtractInstance for EnvironmentMapIds { type QueryData = Read; type QueryFilter = (); diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index dd300f5b3f4d8..1a39e93810792 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -378,7 +378,7 @@ impl Plugin for LightProbePlugin { app.add_plugins(( EnvironmentMapGenerationPlugin, - ExtractInstancesPlugin::::new(), + ExtractInstancesPlugin::::new(), )); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_render/src/extract_instances.rs b/crates/bevy_render/src/extract_instances.rs index d85f8fa646b34..a259492f3ef02 100644 --- a/crates/bevy_render/src/extract_instances.rs +++ b/crates/bevy_render/src/extract_instances.rs @@ -1,12 +1,12 @@ //! Convenience logic for turning components from the main world into extracted -//! instances in the render world. +//! instances in the sub world. //! //! This is essentially the same as the `extract_component` module, but //! higher-performance because it avoids the ECS overhead. use core::marker::PhantomData; -use bevy_app::{App, Plugin}; +use bevy_app::{App, AppLabel, Plugin}; use bevy_camera::visibility::ViewVisibility; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -17,62 +17,66 @@ use bevy_ecs::{ }; use crate::sync_world::MainEntityHashMap; -use crate::{Extract, ExtractSchedule, RenderApp}; +use crate::{Extract, ExtractSchedule}; -/// Describes how to extract data needed for rendering from a component or +/// Describes how to extract data needed for processing from a component or /// components. /// -/// Before rendering, any applicable components will be transferred from the -/// main world to the render world in the [`ExtractSchedule`] step. +/// Before processing, any applicable components will be transferred from the +/// main world to the sub world in the [`ExtractSchedule`] step. /// /// This is essentially the same as /// [`ExtractComponent`](crate::extract_component::ExtractComponent), but /// higher-performance because it avoids the ECS overhead. -pub trait ExtractInstance: Send + Sync + Sized + 'static { +pub trait ExtractInstance: Send + Sync + Sized + 'static { /// ECS [`ReadOnlyQueryData`] to fetch the components to extract. type QueryData: ReadOnlyQueryData; /// Filters the entities with additional constraints. type QueryFilter: QueryFilter; - /// Defines how the component is transferred into the "render world". + /// Defines how the component is transferred into the "sub world". fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } -/// This plugin extracts one or more components into the "render world" as +/// This plugin extracts one or more components into the "sub world" as /// extracted instances. /// /// Therefore it sets up the [`ExtractSchedule`] step for the specified /// [`ExtractedInstances`]. #[derive(Default)] -pub struct ExtractInstancesPlugin +pub struct ExtractInstancesPlugin where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { only_extract_visible: bool, - marker: PhantomData EI>, + marker: PhantomData (L, EI)>, } -/// Stores all extract instances of a type in the render world. +/// Stores all extract instances of a type in the sub world. #[derive(Resource, Deref, DerefMut)] -pub struct ExtractedInstances(MainEntityHashMap) +pub struct ExtractedInstances(#[deref] MainEntityHashMap, PhantomData) where - EI: ExtractInstance; + L: AppLabel, + EI: ExtractInstance; -impl Default for ExtractedInstances +impl Default for ExtractedInstances where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { fn default() -> Self { - Self(Default::default()) + Self(Default::default(), PhantomData) } } -impl ExtractInstancesPlugin +impl ExtractInstancesPlugin where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { /// Creates a new [`ExtractInstancesPlugin`] that unconditionally extracts to - /// the render world, whether the entity is visible or not. + /// the sub world, whether the entity is visible or not. pub fn new() -> Self { Self { only_extract_visible: false, @@ -80,7 +84,7 @@ where } } - /// Creates a new [`ExtractInstancesPlugin`] that extracts to the render world + /// Creates a new [`ExtractInstancesPlugin`] that extracts to the sub world /// if and only if the entity it's attached to is visible. pub fn extract_visible() -> Self { Self { @@ -90,27 +94,29 @@ where } } -impl Plugin for ExtractInstancesPlugin +impl Plugin for ExtractInstancesPlugin where - EI: ExtractInstance, + L: AppLabel + Default, + EI: ExtractInstance, { fn build(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::>(); + if let Some(sub_app) = app.get_sub_app_mut(L::default()) { + sub_app.init_resource::>(); if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible::); + sub_app.add_systems(ExtractSchedule, extract_visible::); } else { - render_app.add_systems(ExtractSchedule, extract_all::); + sub_app.add_systems(ExtractSchedule, extract_all::); } } } } -fn extract_all( - mut extracted_instances: ResMut>, +fn extract_all( + mut extracted_instances: ResMut>, query: Extract>, ) where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { extracted_instances.clear(); for (entity, other) in &query { @@ -120,11 +126,12 @@ fn extract_all( } } -fn extract_visible( - mut extracted_instances: ResMut>, +fn extract_visible( + mut extracted_instances: ResMut>, query: Extract>, ) where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { extracted_instances.clear(); for (entity, view_visibility, other) in &query { diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 2d13d4bae5c63..66c2fa21884aa 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -96,7 +96,8 @@ impl Plugin for SyncWorldPlugin { fn build(&self, app: &mut bevy_app::App) { app.init_resource::>(); app.add_observer( - |add: On, mut pending: ResMut>| { + |add: On, + mut pending: ResMut>| { pending.push(EntityRecord::Added(add.entity)); }, ); From 1816340fae4444f6af844ef318d562a06213ac99 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 27 May 2026 23:11:05 +0800 Subject: [PATCH 05/14] Plugins --- .../bevy_render/extract_render_asset.rs | 14 ++- crates/bevy_render/src/camera.rs | 3 +- crates/bevy_render/src/extract_component.rs | 37 ++++---- crates/bevy_render/src/extract_plugin.rs | 95 ++++++++++++++----- crates/bevy_render/src/extract_resource.rs | 21 ++-- crates/bevy_render/src/lib.rs | 11 ++- crates/bevy_render/src/pipelined_rendering.rs | 3 +- crates/bevy_render/src/sync_component.rs | 23 +++-- crates/bevy_render/src/sync_world.rs | 37 ++++---- 9 files changed, 163 insertions(+), 81 deletions(-) diff --git a/benches/benches/bevy_render/extract_render_asset.rs b/benches/benches/bevy_render/extract_render_asset.rs index 350de956b4e75..8c55844271836 100644 --- a/benches/benches/bevy_render/extract_render_asset.rs +++ b/benches/benches/bevy_render/extract_render_asset.rs @@ -1,11 +1,11 @@ use bevy_app::{App, AppLabel}; use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, Assets, RenderAssetUsages}; -use bevy_ecs::prelude::*; +use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use bevy_reflect::TypePath; use bevy_render::{ extract_plugin::ExtractPlugin, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, - RenderApp, + Render, RenderApp, RenderSystems, }; use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use std::time::{Duration, Instant}; @@ -33,6 +33,8 @@ impl RenderAsset for DummyRenderAsset { } } +pub(crate) fn pre_extract(_main_world: &mut World, _render_world: &mut World) {} + fn extract_render_asset_bench(c: &mut Criterion) { let mut group = c.benchmark_group("extract_render_asset"); @@ -45,7 +47,13 @@ fn extract_render_asset_bench(c: &mut Criterion) { app.add_plugins(bevy_asset::AssetPlugin::default()); app.init_asset::(); - app.add_plugins(ExtractPlugin::default()); + app.add_plugins(ExtractPlugin::::new( + pre_extract, + Render::base_schedule, + Render.intern(), + RenderSystems::ExtractCommands.intern(), + RenderSystems::PostCleanup.intern(), + )); app.add_plugins(RenderAssetPlugin::::default()); app.finish(); diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 572ecfaecffe1..8ac5e672dabe8 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -102,7 +102,8 @@ impl Plugin for CameraPlugin { .add_systems( ExtractSchedule, ( - extract_cameras.after(extract_resource::), + extract_cameras + .after(extract_resource::), clear_dirty_specializations.in_set(DirtySpecializationSystems::Clear), clear_dirty_wireframe_specializations .in_set(DirtySpecializationSystems::Clear), diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 1603f91219b38..bb540abcad2a3 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -1,6 +1,6 @@ use crate::{ sync_component::{SyncComponent, SyncComponentPlugin}, - sync_world::RenderEntity, + sync_world::SubEntity, Extract, ExtractSchedule, RenderApp, }; use bevy_app::{App, AppLabel, Plugin}; @@ -51,18 +51,20 @@ pub trait ExtractComponent: SyncComponent { /// entities. To do so, it sets up the [`ExtractSchedule`] step for the /// specified [`ExtractComponent`]. /// -/// It also registers [`SyncComponentPlugin`] to ensure the extracted components +/// It also registers [`SyncComponentPlugin`](`crate::sync_component::SyncComponentPlugin`) to ensure the extracted components /// are deleted if the main world components are removed. /// /// 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 [`ExtractComponentPlugin`]. -pub struct ExtractComponentPlugin { +pub struct ExtractComponentPlugin { only_extract_visible: bool, - marker: PhantomData (C, F)>, + marker: PhantomData (L, C, F)>, } -impl Default for ExtractComponentPlugin { +// pub type ExtractComponentPlugin = ExtractComponentPlugin; + +impl Default for ExtractComponentPlugin { fn default() -> Self { Self { only_extract_visible: false, @@ -71,7 +73,7 @@ impl Default for ExtractComponentPlugin { } } -impl ExtractComponentPlugin { +impl ExtractComponentPlugin { pub fn extract_visible() -> Self { Self { only_extract_visible: true, @@ -80,27 +82,30 @@ impl ExtractComponentPlugin { } } -impl, F: 'static + Send + Sync> Plugin - for ExtractComponentPlugin +impl< + L: AppLabel + Default + Clone + Copy + Eq, + C: ExtractComponent, + F: 'static + Send + Sync, + > Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { - app.add_plugins(SyncComponentPlugin::::default()); + app.add_plugins(SyncComponentPlugin::::default()); - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + if let Some(render_app) = app.get_sub_app_mut(L::default()) { if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible_components::); + render_app.add_systems(ExtractSchedule, extract_visible_components::); } else { - render_app.add_systems(ExtractSchedule, extract_components::); + render_app.add_systems(ExtractSchedule, extract_components::); } } } } /// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_components, F>( +fn extract_components, F>( mut commands: Commands, mut previous_len: Local, - query: Extract>, + query: Extract, C::QueryData), C::QueryFilter>>, ) { let mut values = Vec::with_capacity(*previous_len); for (entity, query_item) in &query { @@ -115,10 +120,10 @@ fn extract_components, F>( } /// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_visible_components, F>( +fn extract_visible_components, F>( mut commands: Commands, mut previous_len: Local, - query: Extract>, + query: Extract, &ViewVisibility, C::QueryData), C::QueryFilter>>, ) { let mut values = Vec::with_capacity(*previous_len); for (entity, view_visibility, query_item) in &query { diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index b177ffb465268..aa2b332968304 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -1,36 +1,57 @@ -use crate::{ - sync_world::{despawn_temporary_entities, entity_sync_system, SyncWorldPlugin}, - Render, RenderApp, RenderSystems, -}; -use bevy_app::{App, Plugin, SubApp}; +use core::marker::PhantomData; + +use crate::sync_world::{despawn_temporary_entities, entity_sync_system, SyncWorldPlugin}; +use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ resource::Resource, - schedule::{IntoScheduleConfigs, Schedule, ScheduleBuildSettings, ScheduleLabel, Schedules}, + schedule::{ + InternedScheduleLabel, InternedSystemSet, IntoScheduleConfigs, Schedule, + ScheduleBuildSettings, ScheduleLabel, Schedules, + }, world::{Mut, World}, }; use bevy_utils::default; -/// Plugin that sets up the [`RenderApp`] and handles extracting data from the +/// Plugin that sets up the [`RenderApp`](`crate::RenderApp`) and handles extracting data from the /// main world to the render world. -pub struct ExtractPlugin { +pub struct ExtractPlugin { /// Function that gets run at the beginning of each extraction. /// /// Gets the main world and render world as arguments (in that order). pub pre_extract: fn(&mut World, &mut World), + + marker: PhantomData, + + pub base_schedule: fn() -> Schedule, + pub schedule_label: InternedScheduleLabel, + + pub extract_set: InternedSystemSet, + pub despawn_set: InternedSystemSet, } -impl Default for ExtractPlugin { - fn default() -> Self { +impl ExtractPlugin { + pub fn new( + pre_extract: fn(&mut World, &mut World), + base_schedule: fn() -> Schedule, + schedule_label: InternedScheduleLabel, + extract_set: InternedSystemSet, + despawn_set: InternedSystemSet, + ) -> Self { Self { - pre_extract: |_, _| {}, + pre_extract, + marker: PhantomData, + base_schedule, + schedule_label, + extract_set, + despawn_set, } } } -impl Plugin for ExtractPlugin { +impl Plugin for ExtractPlugin { fn build(&self, app: &mut App) { - app.add_plugins(SyncWorldPlugin); + app.add_plugins(SyncWorldPlugin::::default()); app.init_resource::(); let mut render_app = SubApp::new(); @@ -45,16 +66,16 @@ impl Plugin for ExtractPlugin { extract_schedule.set_apply_final_deferred(false); render_app - .add_schedule(Render::base_schedule()) + .add_schedule((self.base_schedule)()) .add_schedule(extract_schedule) .allow_ambiguous_resource::() .add_systems( - Render, + self.schedule_label, ( // This set applies the commands from the extract schedule while the render schedule // is running in parallel with the main app. - apply_extract_commands.in_set(RenderSystems::ExtractCommands), - despawn_temporary_entities::.in_set(RenderSystems::PostCleanup), + apply_extract_commands.in_set(self.extract_set), + despawn_temporary_entities::.in_set(self.despawn_set), ), ); @@ -65,14 +86,14 @@ impl Plugin for ExtractPlugin { { #[cfg(feature = "trace")] let _stage_span = bevy_log::info_span!("entity_sync").entered(); - entity_sync_system::(main_world, render_world); + entity_sync_system::(main_world, render_world); } // run extract schedule extract(main_world, render_world); }); - app.insert_sub_app(RenderApp, render_app); + app.insert_sub_app(L::default(), render_app); } } @@ -135,9 +156,33 @@ mod test { extract_plugin::ExtractPlugin, sync_component::SyncComponent, sync_world::MainEntity, - Render, RenderApp, + RenderApp, }; + #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] + pub enum MyScheduleSystems { + ExtractCommands, + PostCleanup, + } + + #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] + pub struct MySchedule; + + impl MySchedule { + /// Sets up the base structure of the rendering [`Schedule`]. + /// + /// The sets defined in this enum are configured to run in order. + pub fn base_schedule() -> Schedule { + use MyScheduleSystems::*; + + let mut schedule = Schedule::new(Self); + + schedule.configure_sets((ExtractCommands, PostCleanup).chain()); + + schedule + } + } + #[derive(Component, Clone, Debug)] struct RenderComponent; @@ -171,7 +216,13 @@ mod test { fn extraction_works() { let mut app = App::new(); - app.add_plugins(ExtractPlugin::default()); + app.add_plugins(ExtractPlugin::::new( + |_, _| {}, + MySchedule::base_schedule, + MySchedule.intern(), + MyScheduleSystems::ExtractCommands.intern(), + MyScheduleSystems::PostCleanup.intern(), + )); app.add_plugins(ExtractComponentPlugin::::default()); app.add_plugins(ExtractComponentPlugin::::default()); app.add_systems(Startup, |mut commands: Commands| { @@ -182,7 +233,7 @@ mod test { // Normally RenderPlugin sets the RenderRecovery schedule as update, but for // testing we just use the Render schedule directly. - render_app.update_schedule = Some(Render.intern()); + render_app.update_schedule = Some(MySchedule.intern()); render_app.world_mut().add_observer( |event: On, mut commands: Commands| { diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index ca7556587c494..46079fcff3b8f 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -30,20 +30,27 @@ pub trait ExtractResource: Resource { /// 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 [`ExtractResourcePlugin`]. -pub struct ExtractResourcePlugin, F = ()>(PhantomData<(R, F)>); +pub struct ExtractResourcePlugin, F = (), L: AppLabel = RenderApp>( + PhantomData<(L, R, F)>, +); -impl, F> Default for ExtractResourcePlugin { +// pub type ExtractResourcePlugin = ExtractResourcePlugin; + +impl, F> Default for ExtractResourcePlugin { fn default() -> Self { Self(PhantomData) } } -impl, F: 'static + Send + Sync> Plugin - for ExtractResourcePlugin +impl< + L: AppLabel + Default, + R: ExtractResource, + F: 'static + Send + Sync, + > Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems(ExtractSchedule, extract_resource::); + if let Some(render_app) = app.get_sub_app_mut(L::default()) { + render_app.add_systems(ExtractSchedule, extract_resource::); } else { once!(bevy_log::error!( "Render app did not exist when trying to add `extract_resource` for <{}>.", @@ -54,7 +61,7 @@ impl, F: 'static + Send + } /// This system extracts the resource of the corresponding [`Resource`] type -pub fn extract_resource, F>( +pub fn extract_resource, F>( mut commands: Commands, main_resource: Extract>>, target_resource: Option>, diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 63e2da2bb5a89..b6a6de1c9cdee 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -76,7 +76,6 @@ pub mod prelude { view::Msaa, ExtractSchedule, }; } - pub use extract_param::Extract; pub use extract_plugin::{ExtractSchedule, MainWorld}; @@ -358,9 +357,13 @@ impl Plugin for RenderPlugin { if insert_future_resources(&self.render_creation, app.world_mut()) { // We only create the render world and set up extraction if we // have a rendering backend available. - app.add_plugins(ExtractPlugin { - pre_extract: error_handler::update_state, - }); + app.add_plugins(ExtractPlugin::::new( + error_handler::update_state, + Render::base_schedule, + Render.intern(), + RenderSystems::ExtractCommands.intern(), + RenderSystems::PostCleanup.intern(), + )); }; app.add_plugins(( diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index 6f0b2b1fa6b83..4440503bf7718 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -1,6 +1,7 @@ use async_channel::{Receiver, Sender}; -use bevy_app::{App, AppExit, AppLabel, Plugin, SubApp}; +use bevy_app::{App, AppExit, Plugin, SubApp}; +use bevy_derive::AppLabel; use bevy_ecs::{ resource::Resource, schedule::MainThreadExecutor, diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_render/src/sync_component.rs index 339c3fc8632a7..d50899425300c 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_render/src/sync_component.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ }; use crate::{ - sync_world::{EntityRecord, PendingSyncEntity, SyncToRenderWorld}, + sync_world::{EntityRecord, PendingSyncEntity, SyncToSubWorld}, RenderApp, }; @@ -21,14 +21,16 @@ use crate::{ /// /// # Implementation details /// -/// It adds [`SyncToRenderWorld`] as a required component to make the [`SyncWorldPlugin`] aware of the component, and +/// It adds [`SyncToSubWorld`] as a required component to make the [`SyncWorldPlugin`] aware of the component, and /// handles cleanup of the component in the render world when it is removed from an entity. /// /// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin /// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin -pub struct SyncComponentPlugin(PhantomData<(C, F)>); +pub struct SyncComponentPlugin(PhantomData<(L, C, F)>); -impl, F> Default for SyncComponentPlugin { +// pub type SyncComponentPlugin = SyncComponentPlugin; + +impl, F> Default for SyncComponentPlugin { fn default() -> Self { Self(PhantomData) } @@ -40,6 +42,8 @@ impl, F> Default for SyncComponentPlugin { /// 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. @@ -48,16 +52,19 @@ pub trait SyncComponent: Component { // type Target: Bundle = Self; } -impl, F: Send + Sync + 'static> Plugin - for SyncComponentPlugin +impl< + L: AppLabel + Default + Clone + Copy + Eq, + C: SyncComponent, + F: Send + Sync + 'static, + > Plugin for SyncComponentPlugin { fn build(&self, app: &mut App) { - app.register_required_components::(); + app.register_required_components::>(); app.world_mut() .register_component_hooks::() .on_remove(|mut world, context| { - let mut pending = world.resource_mut::>(); + let mut pending = world.resource_mut::>(); pending.push(EntityRecord::ComponentRemoved( context.entity, |mut entity| { diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index 66c2fa21884aa..dcb18fd3a2998 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -17,9 +17,9 @@ use bevy_ecs::{ }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -/// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world. +/// A plugin that synchronizes entities with [`SyncToSubWorld`] between the main world and the render world. /// -/// All entities with the [`SyncToRenderWorld`] component are kept in sync. It +/// All entities with the [`SyncToSubWorld`] component are kept in sync. It /// is automatically added as a required component by [`ExtractComponentPlugin`] /// and [`SyncComponentPlugin`], so it doesn't need to be added manually when /// spawning or as a required component when either of these plugins are used. @@ -33,13 +33,13 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// [`SyncWorldPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping /// between the main world and the render world. /// It does so by spawning and despawning entities in the render world, to match spawned and despawned entities in the main world. -/// The link between synced entities is maintained by the [`RenderEntity`] and [`MainEntity`] components. +/// The link between synced entities is maintained by the [`SubEntity`] and [`MainEntity`] components. /// -/// The [`RenderEntity`] contains the corresponding render world entity of a main world entity, while [`MainEntity`] contains +/// The [`SubEntity`] contains the corresponding render world entity of a main world entity, while [`MainEntity`] contains /// the corresponding main world entity of a render world entity. /// For convenience, [`QueryData`](bevy_ecs::query::QueryData) implementations are provided for both components: /// adding [`MainEntity`] to a query (without a `&`) will return the corresponding main world [`Entity`], -/// and adding [`RenderEntity`] will return the corresponding render world [`Entity`]. +/// and adding [`SubEntity`] will return the corresponding render world [`Entity`]. /// If you have access to the component itself, the underlying entities can be accessed by calling `.id()`. /// /// Synchronization is necessary preparation for extraction ([`ExtractSchedule`](crate::ExtractSchedule)), which copies over component data from the main @@ -59,8 +59,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// |---------------------------Main World------------------------------| /// | Entity | Component | /// |-------------------------------------------------------------------| -/// | ID: 1v1 | PointLight | RenderEntity(ID: 3V1) | SyncToRenderWorld | -/// | ID: 18v1 | PointLight | RenderEntity(ID: 5V1) | SyncToRenderWorld | +/// | ID: 1v1 | PointLight | SubEntity(ID: 3V1) | SyncToSubWorld | +/// | ID: 18v1 | PointLight | SubEntity(ID: 5V1) | SyncToSubWorld | /// |-------------------------------------------------------------------| /// /// |----------Render World-----------| @@ -73,8 +73,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// ``` /// /// Note that this effectively establishes a link between the main world entity and the render world entity. -/// Not every entity needs to be synchronized, however; only entities with the [`SyncToRenderWorld`] component are synced. -/// Adding [`SyncToRenderWorld`] to a main world component will establish such a link. +/// Not every entity needs to be synchronized, however; only entities with the [`SyncToSubWorld`] component are synced. +/// Adding [`SyncToSubWorld`] to a main world component will establish such a link. /// Once a synchronized main entity is despawned, its corresponding render entity will be automatically /// despawned in the next `sync`. /// @@ -90,23 +90,22 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin /// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin #[derive(Default)] -pub struct SyncWorldPlugin; +pub struct SyncWorldPlugin(PhantomData); -impl Plugin for SyncWorldPlugin { +impl Plugin for SyncWorldPlugin { fn build(&self, app: &mut bevy_app::App) { - app.init_resource::>(); + app.init_resource::>(); app.add_observer( - |add: On, - mut pending: ResMut>| { - pending.push(EntityRecord::Added(add.entity)); + |add: On>, mut pending: ResMut>| { + pending.push(EntityRecord::::Added(add.entity)); }, ); app.add_observer( - |remove: On, - mut pending: ResMut>, - query: Query<&RenderEntity>| { + |remove: On>, + mut pending: ResMut>, + query: Query<&SubEntity>| { if let Ok(e) = query.get(remove.entity) { - pending.push(EntityRecord::Removed(*e)); + pending.push(EntityRecord::::Removed(*e)); }; }, ); From fabe19de6e8abc1b57cb513fdd0e067365940fe6 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 29 May 2026 17:16:22 +0800 Subject: [PATCH 06/14] change hook to observer --- crates/bevy_render/src/sync_component.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_render/src/sync_component.rs index d50899425300c..6a6781ec6b0d1 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_render/src/sync_component.rs @@ -4,6 +4,9 @@ use bevy_app::{App, AppLabel, Plugin}; use bevy_ecs::{ bundle::{Bundle, NoBundleEffect}, component::Component, + lifecycle::Remove, + observer::On, + system::ResMut, }; use crate::{ @@ -61,16 +64,15 @@ impl< fn build(&self, app: &mut App) { app.register_required_components::>(); - app.world_mut() - .register_component_hooks::() - .on_remove(|mut world, context| { - let mut pending = world.resource_mut::>(); - pending.push(EntityRecord::ComponentRemoved( - context.entity, + app.add_observer( + |remove: On, mut pending: ResMut>| { + pending.push(EntityRecord::::ComponentRemoved( + remove.entity, |mut entity| { entity.remove::(); }, )); - }); + }, + ); } } From 899ee25caa44b85a1c650abe1468ef3f157ef78c Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 29 May 2026 17:01:46 +0800 Subject: [PATCH 07/14] Dual extraction --- crates/bevy_render/src/extract_plugin.rs | 224 +++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index aa2b332968304..8e25fb449fe44 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -303,4 +303,228 @@ mod test { .unwrap(); } } + + #[derive(AppLabel, Debug, Hash, PartialEq, Eq, Clone, Default, Copy)] + pub struct ExtractAppA; + + #[derive(AppLabel, Debug, Hash, PartialEq, Eq, Clone, Default, Copy)] + pub struct ExtractAppB; + + #[derive(Component, Clone, Debug)] + struct RenderComponentSeparateA; + + #[derive(Component, Clone, Debug)] + struct RenderComponentSeparateB; + + #[derive(Component, Clone, Debug)] + struct RenderComponentSeparateBoth; + + impl SyncComponent for RenderComponentSeparateA { + type Target = RenderComponentSeparateA; + } + + impl ExtractComponent for RenderComponentSeparateA { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = Self; + + fn extract_component( + _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>, + ) -> Option { + Some(RenderComponentSeparateA) + } + } + + impl SyncComponent for RenderComponentSeparateB { + type Target = RenderComponentSeparateB; + } + + impl ExtractComponent for RenderComponentSeparateB { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = Self; + + fn extract_component( + _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>, + ) -> Option { + Some(RenderComponentSeparateB) + } + } + + impl SyncComponent for RenderComponentSeparateBoth { + type Target = RenderComponentSeparateBoth; + } + + impl ExtractComponent for RenderComponentSeparateBoth { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = Self; + + fn extract_component( + _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>, + ) -> Option { + Some(RenderComponentSeparateBoth) + } + } + + impl SyncComponent for RenderComponentSeparateBoth { + type Target = RenderComponentSeparateBoth; + } + + impl ExtractComponent for RenderComponentSeparateBoth { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = Self; + + fn extract_component( + _item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>, + ) -> Option { + Some(RenderComponentSeparateBoth) + } + } + + #[test] + fn dual_extraction_works() { + let mut app = App::new(); + + app.add_plugins(ExtractPlugin::::new( + |_, _| {}, + MySchedule::base_schedule, + MySchedule.intern(), + MyScheduleSystems::ExtractCommands.intern(), + MyScheduleSystems::PostCleanup.intern(), + )); + app.add_plugins(ExtractPlugin::::new( + |_, _| {}, + MySchedule::base_schedule, + MySchedule.intern(), + MyScheduleSystems::ExtractCommands.intern(), + MyScheduleSystems::PostCleanup.intern(), + )); + + app.add_plugins(ExtractComponentPlugin::< + ExtractAppA, + RenderComponentSeparateA, + >::default()); + app.add_plugins(ExtractComponentPlugin::< + ExtractAppB, + RenderComponentSeparateB, + >::default()); + app.add_plugins(ExtractComponentPlugin::< + ExtractAppA, + RenderComponentSeparateBoth, + >::default()); + app.add_plugins(ExtractComponentPlugin::< + ExtractAppB, + RenderComponentSeparateBoth, + >::default()); + + app.add_systems(Startup, |mut commands: Commands| { + commands.spawn(( + RenderComponent, + RenderComponentSeparateA, + RenderComponentSeparateB, + RenderComponentSeparateBoth, + )); + }); + + let sub_app_a = app.get_sub_app_mut(ExtractAppA).unwrap(); + sub_app_a.update_schedule = Some(MySchedule.intern()); + + let sub_app_b = app.get_sub_app_mut(ExtractAppB).unwrap(); + sub_app_b.update_schedule = Some(MySchedule.intern()); + + app.update(); + + // Check that all components have been extracted + { + let sub_app_a = app.get_sub_app_mut(ExtractAppA).unwrap(); + sub_app_a + .world_mut() + .run_system_cached( + |entity: Single<( + &MainEntity, + Option<&RenderComponentSeparateA>, + Option<&RenderComponentSeparateB>, + Option<&RenderComponentSeparateBoth>, + )>| { + assert!(entity.1.is_some()); + assert!(entity.2.is_none()); + assert!(entity.3.is_some()); + }, + ) + .unwrap(); + } + + { + let sub_app_b = app.get_sub_app_mut(ExtractAppB).unwrap(); + sub_app_b + .world_mut() + .run_system_cached( + |entity: Single<( + &MainEntity, + Option<&RenderComponentSeparateA>, + Option<&RenderComponentSeparateB>, + Option<&RenderComponentSeparateBoth>, + )>| { + assert!(entity.1.is_none()); + assert!(entity.2.is_some()); + assert!(entity.3.is_some()); + }, + ) + .unwrap(); + } + + // Remove RenderComponentSeparateA + app.world_mut() + .run_system_cached( + |mut commands: Commands, query: Query>| { + for entity in query { + commands.entity(entity).remove::(); + } + }, + ) + .unwrap(); + + app.update(); + + // Check that the extracted components have been removed + { + let sub_app_a = app.get_sub_app_mut(ExtractAppA).unwrap(); + sub_app_a + .world_mut() + .run_system_cached( + |entity: Single<( + &MainEntity, + Option<&RenderComponentSeparateA>, + Option<&RenderComponentSeparateB>, + Option<&RenderComponentSeparateBoth>, + )>| { + assert!(entity.1.is_none()); + assert!(entity.2.is_none()); + assert!(entity.3.is_some()); + }, + ) + .unwrap(); + } + + { + let sub_app_b = app.get_sub_app_mut(ExtractAppB).unwrap(); + sub_app_b + .world_mut() + .run_system_cached( + |entity: Single<( + &MainEntity, + Option<&RenderComponentSeparateA>, + Option<&RenderComponentSeparateB>, + Option<&RenderComponentSeparateBoth>, + )>| { + assert!(entity.1.is_none()); + assert!(entity.2.is_some()); + assert!(entity.3.is_some()); + }, + ) + .unwrap(); + } + } } From fdca1f24e9bdee1d0e8370ad64c1463ffc3967fb Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Fri, 29 May 2026 15:50:52 +0800 Subject: [PATCH 08/14] Support #[extract_app(AppA,AppB)] in derive(ExtractComponent) --- .../macros/src/extract_component.rs | 73 ++++++++++++++----- crates/bevy_render/src/extract_plugin.rs | 15 ++++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/crates/bevy_render/macros/src/extract_component.rs b/crates/bevy_render/macros/src/extract_component.rs index 9577f8c954d5b..ec802fea534a0 100644 --- a/crates/bevy_render/macros/src/extract_component.rs +++ b/crates/bevy_render/macros/src/extract_component.rs @@ -1,7 +1,10 @@ use bevy_macro_utils::fq_std::{FQClone, FQOption}; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, parse_quote, DeriveInput, Path}; +use syn::{ + parse_macro_input, parse_quote, punctuated::Punctuated, DeriveInput, MacroDelimiter, Meta, + MetaList, Path, +}; pub fn derive_extract_component(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); @@ -20,15 +23,35 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let app_label = match ast.attrs.iter().find(|a| a.path().is_ident("extract_app")) { - Some(attr) => match attr.parse_args::() { - Ok(label) => label, - Err(e) => return e.to_compile_error().into(), - }, + let app_labels: Vec = match ast.attrs.iter().find(|a| a.path().is_ident("extract_app")) { + Some(attr) => { + if let Meta::List(meta_list) = &attr.meta { + if let MacroDelimiter::Paren(_) = meta_list.delimiter { + match parse_to_paths(meta_list) { + Ok(labels) => labels, + Err(e) => return e.into(), + } + } else { + return syn::Error::new_spanned( + &ast.ident, + "ExtractComponent requires #[extract_app(MyAppLabelA,MyAppLabelB)] to specify the target sub-app(s) in (parentheses)", + ) + .to_compile_error() + .into(); + } + } else { + return syn::Error::new_spanned( + &ast.ident, + "ExtractComponent requires #[extract_app(MyAppLabelA,MyAppLabelB)] to specify the target sub-app(s) as a list", + ) + .to_compile_error() + .into(); + } + } None => { return syn::Error::new_spanned( &ast.ident, - "ExtractComponent requires #[extract_app(MyAppLabel)] to specify the target sub-app", + "ExtractComponent requires #[extract_app(MyAppLabelA,MyAppLabelB)] to specify the target sub-app(s)", ) .to_compile_error() .into(); @@ -73,20 +96,32 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { } }; - TokenStream::from(quote! { - impl #impl_generics #bevy_render_path::sync_component::SyncComponent<#app_label> for #struct_name #type_generics #where_clause { - type Target = #sync_target; - } + app_labels.iter().map(|app_label| + TokenStream::from(quote! { + impl #impl_generics #bevy_render_path::sync_component::SyncComponent<#app_label> for #struct_name #type_generics #where_clause { + type Target = #sync_target; + } - impl #impl_generics #bevy_render_path::extract_component::ExtractComponent<#app_label> for #struct_name #type_generics #where_clause { - type QueryData = &'static Self; + impl #impl_generics #bevy_render_path::extract_component::ExtractComponent<#app_label> for #struct_name #type_generics #where_clause { + type QueryData = &'static Self; - type QueryFilter = #filter; - type Out = Self; + type QueryFilter = #filter; + type Out = Self; - fn extract_component(item: #bevy_ecs_path::query::QueryItem<'_, '_, Self::QueryData>) -> #FQOption { - #FQOption::Some(item.clone()) + fn extract_component(item: #bevy_ecs_path::query::QueryItem<'_, '_, Self::QueryData>) -> #FQOption { + #FQOption::Some(item.clone()) + } } - } - }) + }) + ).collect() +} + +fn parse_to_paths(meta_list: &MetaList) -> Result, proc_macro2::TokenStream> { + meta_list + .parse_args_with(|input: syn::parse::ParseStream| { + let result = Punctuated::::parse_terminated(input); + + result.map(|paths| paths.iter().cloned().collect::>()) + }) + .map_err(|e| e.to_compile_error()) } diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index 8e25fb449fe44..fff658eec51f5 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -319,6 +319,10 @@ mod test { #[derive(Component, Clone, Debug)] struct RenderComponentSeparateBoth; + #[derive(Component, Clone, Debug, ExtractComponent)] + #[extract_app(ExtractAppA, ExtractAppB)] + struct RenderComponentDual; + impl SyncComponent for RenderComponentSeparateA { type Target = RenderComponentSeparateA; } @@ -418,6 +422,8 @@ mod test { ExtractAppB, RenderComponentSeparateBoth, >::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); app.add_systems(Startup, |mut commands: Commands| { commands.spawn(( @@ -425,6 +431,7 @@ mod test { RenderComponentSeparateA, RenderComponentSeparateB, RenderComponentSeparateBoth, + RenderComponentDual, )); }); @@ -447,10 +454,12 @@ mod test { Option<&RenderComponentSeparateA>, Option<&RenderComponentSeparateB>, Option<&RenderComponentSeparateBoth>, + Option<&RenderComponentDual>, )>| { assert!(entity.1.is_some()); assert!(entity.2.is_none()); assert!(entity.3.is_some()); + assert!(entity.4.is_some()); }, ) .unwrap(); @@ -466,10 +475,12 @@ mod test { Option<&RenderComponentSeparateA>, Option<&RenderComponentSeparateB>, Option<&RenderComponentSeparateBoth>, + Option<&RenderComponentDual>, )>| { assert!(entity.1.is_none()); assert!(entity.2.is_some()); assert!(entity.3.is_some()); + assert!(entity.4.is_some()); }, ) .unwrap(); @@ -499,10 +510,12 @@ mod test { Option<&RenderComponentSeparateA>, Option<&RenderComponentSeparateB>, Option<&RenderComponentSeparateBoth>, + Option<&RenderComponentDual>, )>| { assert!(entity.1.is_none()); assert!(entity.2.is_none()); assert!(entity.3.is_some()); + assert!(entity.4.is_some()); }, ) .unwrap(); @@ -518,10 +531,12 @@ mod test { Option<&RenderComponentSeparateA>, Option<&RenderComponentSeparateB>, Option<&RenderComponentSeparateBoth>, + Option<&RenderComponentDual>, )>| { assert!(entity.1.is_none()); assert!(entity.2.is_some()); assert!(entity.3.is_some()); + assert!(entity.4.is_some()); }, ) .unwrap(); From 2a68d73ba131aa57abf95b7f7f67b9bb8f8f4123 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Tue, 7 Jul 2026 14:19:36 +0800 Subject: [PATCH 09/14] Merge migration guides --- .../migration-guides/extract-extract-a.md | 10 ---------- .../{extract-extract-b.md => extract-extract.md} | 13 ++++++++++--- 2 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 _release-content/migration-guides/extract-extract-a.md rename _release-content/migration-guides/{extract-extract-b.md => extract-extract.md} (63%) diff --git a/_release-content/migration-guides/extract-extract-a.md b/_release-content/migration-guides/extract-extract-a.md deleted file mode 100644 index c8a818d2f4a7c..0000000000000 --- a/_release-content/migration-guides/extract-extract-a.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Extract Extract -pull_requests: [] ---- - -Extraction used to be specific of Main World to Render World, but will now be generic - -- Use `TemporaryRenderEntity::default()` instead of `TemporaryRenderEntity` - -NOTE: more to come diff --git a/_release-content/migration-guides/extract-extract-b.md b/_release-content/migration-guides/extract-extract.md similarity index 63% rename from _release-content/migration-guides/extract-extract-b.md rename to _release-content/migration-guides/extract-extract.md index 3542421912a61..9dc7ff5a003dc 100644 --- a/_release-content/migration-guides/extract-extract-b.md +++ b/_release-content/migration-guides/extract-extract.md @@ -1,10 +1,11 @@ --- title: Extract Extract -pull_requests: [24420] +pull_requests: [24419, 24420, 24423] --- Extraction used to be specific of Main World to Render World, but will now be generic +- Use `TemporaryRenderEntity::default()` instead of `TemporaryRenderEntity` - When using extraction related traits e.g. `SyncComponent`, `ExtractComponent` and `ExtractResource`, you must specify the `AppLabel` for the target world. @@ -14,7 +15,6 @@ Before: impl SyncComponent for TemporalAntiAliasing { ... } #[derive(Component, ExtractComponent)] -#[extract_app(RenderApp)] pub struct Foo { ... } ``` @@ -28,4 +28,11 @@ impl SyncComponent for TemporalAntiAliasing { ... } pub struct Foo { ... } ``` -NOTE: more to come + +You can also extract a component from the main subapp to the render subapp, and to the audio subapp + +```rust,ignore +#[derive(Component, Clone, Debug, ExtractComponent)] +#[extract_app(RenderApp, AudioApp)] +struct SomeComponent; +``` From d318511cd07f6e0b1aed4f3838e0cfaf4ae81146 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Tue, 7 Jul 2026 14:38:56 +0800 Subject: [PATCH 10/14] ci --- .../migration-guides/extract-extract.md | 1 - crates/bevy_render/src/extract_plugin.rs | 18 +++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/_release-content/migration-guides/extract-extract.md b/_release-content/migration-guides/extract-extract.md index 9dc7ff5a003dc..c6186b5b68f9f 100644 --- a/_release-content/migration-guides/extract-extract.md +++ b/_release-content/migration-guides/extract-extract.md @@ -28,7 +28,6 @@ impl SyncComponent for TemporalAntiAliasing { ... } pub struct Foo { ... } ``` - You can also extract a component from the main subapp to the render subapp, and to the audio subapp ```rust,ignore diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index fff658eec51f5..ca49db6f697d6 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -148,7 +148,7 @@ pub fn extract(main_world: &mut World, render_world: &mut World) { #[cfg(test)] mod test { - use bevy_app::{App, Startup}; + use bevy_app::{App, AppLabel, Startup}; use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use crate::{ @@ -407,23 +407,27 @@ mod test { )); app.add_plugins(ExtractComponentPlugin::< - ExtractAppA, RenderComponentSeparateA, + (), + ExtractAppA, >::default()); app.add_plugins(ExtractComponentPlugin::< - ExtractAppB, RenderComponentSeparateB, + (), + ExtractAppB, >::default()); app.add_plugins(ExtractComponentPlugin::< - ExtractAppA, RenderComponentSeparateBoth, + (), + ExtractAppA, >::default()); app.add_plugins(ExtractComponentPlugin::< - ExtractAppB, RenderComponentSeparateBoth, + (), + ExtractAppB, >::default()); - app.add_plugins(ExtractComponentPlugin::::default()); - app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); app.add_systems(Startup, |mut commands: Commands| { commands.spawn(( From 07a016eaa4ea296bb8cd222991e9f4cf5b35055e Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 15 Jul 2026 22:40:08 +0800 Subject: [PATCH 11/14] Change to --- crates/bevy_core_pipeline/src/skybox/mod.rs | 2 +- crates/bevy_pbr/src/cluster/gpu.rs | 1 + crates/bevy_pbr/src/decal/clustered.rs | 2 +- crates/bevy_pbr/src/lib.rs | 12 +++---- crates/bevy_pbr/src/light_probe/generate.rs | 6 +++- crates/bevy_pbr/src/light_probe/mod.rs | 2 +- crates/bevy_pbr/src/volumetric_fog/mod.rs | 2 +- crates/bevy_render/src/camera.rs | 2 +- crates/bevy_render/src/extract_component.rs | 24 ++++++------- crates/bevy_render/src/extract_instances.rs | 40 ++++++++++----------- crates/bevy_render/src/extract_plugin.rs | 8 ++--- crates/bevy_render/src/extract_resource.rs | 16 ++++----- crates/bevy_render/src/sync_component.rs | 10 +++--- 13 files changed, 64 insertions(+), 63 deletions(-) diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index f29f375fbcfa1..fafa50f17f6d8 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -38,7 +38,7 @@ impl Plugin for SkyboxPlugin { embedded_asset!(app, "skybox.wgsl"); app.add_plugins(( - SyncComponentPlugin::::default(), + SyncComponentPlugin::::default(), UniformComponentPlugin::::default(), )); diff --git a/crates/bevy_pbr/src/cluster/gpu.rs b/crates/bevy_pbr/src/cluster/gpu.rs index 2cb5abc61dac5..4737eaec91796 100644 --- a/crates/bevy_pbr/src/cluster/gpu.rs +++ b/crates/bevy_pbr/src/cluster/gpu.rs @@ -130,6 +130,7 @@ impl Plugin for GpuClusteringPlugin { app.add_plugins(ExtractResourcePlugin::< GlobalClusterSettings, + RenderApp, GpuClusteringPlugin, >::default()); } diff --git a/crates/bevy_pbr/src/decal/clustered.rs b/crates/bevy_pbr/src/decal/clustered.rs index db6e7a4e7e39c..8b555555677d8 100644 --- a/crates/bevy_pbr/src/decal/clustered.rs +++ b/crates/bevy_pbr/src/decal/clustered.rs @@ -161,7 +161,7 @@ impl Plugin for ClusteredDecalPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "clustered.wgsl"); - app.add_plugins(SyncComponentPlugin::::default()); + app.add_plugins(SyncComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index a6cdad28ca896..1ca8bd4a3aa8d 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -225,7 +225,7 @@ impl Plugin for PbrPlugin { ScreenSpaceAmbientOcclusionPlugin, FogPlugin, ExtractResourcePlugin::::default(), - SyncComponentPlugin::::default(), + SyncComponentPlugin::::default(), LightmapPlugin, LightProbePlugin, GpuMeshPreprocessPlugin { @@ -239,11 +239,11 @@ impl Plugin for PbrPlugin { )) .add_plugins(( decal::ForwardDecalPlugin, - SyncComponentPlugin::::default(), - SyncComponentPlugin::::default(), - SyncComponentPlugin::::default(), - SyncComponentPlugin::::default(), - SyncComponentPlugin::::default(), + SyncComponentPlugin::::default(), + SyncComponentPlugin::::default(), + SyncComponentPlugin::::default(), + SyncComponentPlugin::::default(), + SyncComponentPlugin::::default(), )) .add_plugins(( ScatteringMediumPlugin, diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index 545d5437a0ffb..dc84f922718ec 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -128,7 +128,11 @@ impl Plugin for EnvironmentMapGenerationPlugin { embedded_asset!(app, "environment_filter.wgsl"); embedded_asset!(app, "copy.wgsl"); - app.add_plugins(SyncComponentPlugin::::default()) + app.add_plugins(SyncComponentPlugin::< + GeneratedEnvironmentMapLight, + RenderApp, + Self, + >::default()) .add_systems(Update, generate_environment_map_light); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 1a39e93810792..410e4be387e50 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -378,7 +378,7 @@ impl Plugin for LightProbePlugin { app.add_plugins(( EnvironmentMapGenerationPlugin, - ExtractInstancesPlugin::::new(), + ExtractInstancesPlugin::::new(), )); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index fc1f084c278ab..7584117be4909 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -70,7 +70,7 @@ impl Plugin for VolumetricFogPlugin { let plane_mesh = meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE).mesh()); let cube_mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh()); - app.add_plugins(SyncComponentPlugin::::default()); + app.add_plugins(SyncComponentPlugin::::default()); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 8ac5e672dabe8..3448923087a5a 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -103,7 +103,7 @@ impl Plugin for CameraPlugin { ExtractSchedule, ( extract_cameras - .after(extract_resource::), + .after(extract_resource::), clear_dirty_specializations.in_set(DirtySpecializationSystems::Clear), clear_dirty_wireframe_specializations .in_set(DirtySpecializationSystems::Clear), diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index bb540abcad2a3..1d5dab54d7d4e 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -57,14 +57,14 @@ pub trait ExtractComponent: SyncComponent { /// 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 [`ExtractComponentPlugin`]. -pub struct ExtractComponentPlugin { +pub struct ExtractComponentPlugin { only_extract_visible: bool, - marker: PhantomData (L, C, F)>, + marker: PhantomData (C, L, F)>, } -// pub type ExtractComponentPlugin = ExtractComponentPlugin; +// pub type ExtractComponentPlugin = ExtractComponentPlugin; -impl Default for ExtractComponentPlugin { +impl Default for ExtractComponentPlugin { fn default() -> Self { Self { only_extract_visible: false, @@ -73,7 +73,7 @@ impl Default for ExtractComponentPlugin { } } -impl ExtractComponentPlugin { +impl ExtractComponentPlugin { pub fn extract_visible() -> Self { Self { only_extract_visible: true, @@ -83,26 +83,26 @@ impl ExtractComponentPlugin { } impl< - L: AppLabel + Default + Clone + Copy + Eq, C: ExtractComponent, + L: AppLabel + Default + Clone + Copy + Eq, F: 'static + Send + Sync, - > Plugin for ExtractComponentPlugin + > Plugin for ExtractComponentPlugin { fn build(&self, app: &mut App) { - app.add_plugins(SyncComponentPlugin::::default()); + app.add_plugins(SyncComponentPlugin::::default()); if let Some(render_app) = app.get_sub_app_mut(L::default()) { if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible_components::); + render_app.add_systems(ExtractSchedule, extract_visible_components::); } else { - render_app.add_systems(ExtractSchedule, extract_components::); + render_app.add_systems(ExtractSchedule, extract_components::); } } } } /// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_components, F>( +fn extract_components, L: AppLabel + Clone + Copy + Eq, F>( mut commands: Commands, mut previous_len: Local, query: Extract, C::QueryData), C::QueryFilter>>, @@ -120,7 +120,7 @@ fn extract_components } /// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_visible_components, F>( +fn extract_visible_components, L: AppLabel + Clone + Copy + Eq, F>( mut commands: Commands, mut previous_len: Local, query: Extract, &ViewVisibility, C::QueryData), C::QueryFilter>>, diff --git a/crates/bevy_render/src/extract_instances.rs b/crates/bevy_render/src/extract_instances.rs index a259492f3ef02..b28dca98d098b 100644 --- a/crates/bevy_render/src/extract_instances.rs +++ b/crates/bevy_render/src/extract_instances.rs @@ -44,10 +44,10 @@ pub trait ExtractInstance: Send + Sync + Sized + 'static { /// Therefore it sets up the [`ExtractSchedule`] step for the specified /// [`ExtractedInstances`]. #[derive(Default)] -pub struct ExtractInstancesPlugin +pub struct ExtractInstancesPlugin where - L: AppLabel, EI: ExtractInstance, + L: AppLabel, { only_extract_visible: bool, marker: PhantomData (L, EI)>, @@ -55,25 +55,25 @@ where /// Stores all extract instances of a type in the sub world. #[derive(Resource, Deref, DerefMut)] -pub struct ExtractedInstances(#[deref] MainEntityHashMap, PhantomData) +pub struct ExtractedInstances(#[deref] MainEntityHashMap, PhantomData) where - L: AppLabel, - EI: ExtractInstance; + EI: ExtractInstance, + L: AppLabel; -impl Default for ExtractedInstances +impl Default for ExtractedInstances where - L: AppLabel, EI: ExtractInstance, + L: AppLabel, { fn default() -> Self { Self(Default::default(), PhantomData) } } -impl ExtractInstancesPlugin +impl ExtractInstancesPlugin where - L: AppLabel, EI: ExtractInstance, + L: AppLabel, { /// Creates a new [`ExtractInstancesPlugin`] that unconditionally extracts to /// the sub world, whether the entity is visible or not. @@ -94,29 +94,29 @@ where } } -impl Plugin for ExtractInstancesPlugin +impl Plugin for ExtractInstancesPlugin where - L: AppLabel + Default, EI: ExtractInstance, + L: AppLabel + Default, { fn build(&self, app: &mut App) { if let Some(sub_app) = app.get_sub_app_mut(L::default()) { - sub_app.init_resource::>(); + sub_app.init_resource::>(); if self.only_extract_visible { - sub_app.add_systems(ExtractSchedule, extract_visible::); + sub_app.add_systems(ExtractSchedule, extract_visible::); } else { - sub_app.add_systems(ExtractSchedule, extract_all::); + sub_app.add_systems(ExtractSchedule, extract_all::); } } } } -fn extract_all( - mut extracted_instances: ResMut>, +fn extract_all( + mut extracted_instances: ResMut>, query: Extract>, ) where - L: AppLabel, EI: ExtractInstance, + L: AppLabel, { extracted_instances.clear(); for (entity, other) in &query { @@ -126,12 +126,12 @@ fn extract_all( } } -fn extract_visible( - mut extracted_instances: ResMut>, +fn extract_visible( + mut extracted_instances: ResMut>, query: Extract>, ) where - L: AppLabel, EI: ExtractInstance, + L: AppLabel, { extracted_instances.clear(); for (entity, view_visibility, other) in &query { diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index ca49db6f697d6..8160e28e35f67 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -408,26 +408,22 @@ mod test { app.add_plugins(ExtractComponentPlugin::< RenderComponentSeparateA, - (), ExtractAppA, >::default()); app.add_plugins(ExtractComponentPlugin::< RenderComponentSeparateB, - (), ExtractAppB, >::default()); app.add_plugins(ExtractComponentPlugin::< RenderComponentSeparateBoth, - (), ExtractAppA, >::default()); app.add_plugins(ExtractComponentPlugin::< RenderComponentSeparateBoth, - (), ExtractAppB, >::default()); - app.add_plugins(ExtractComponentPlugin::::default()); - app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); app.add_systems(Startup, |mut commands: Commands| { commands.spawn(( diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index 46079fcff3b8f..019c07fbf46a0 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -30,27 +30,27 @@ pub trait ExtractResource: Resource { /// 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 [`ExtractResourcePlugin`]. -pub struct ExtractResourcePlugin, F = (), L: AppLabel = RenderApp>( - PhantomData<(L, R, F)>, +pub struct ExtractResourcePlugin, L: AppLabel = RenderApp, F = ()>( + PhantomData<(R, L, F)>, ); -// pub type ExtractResourcePlugin = ExtractResourcePlugin; +// pub type ExtractResourcePlugin = ExtractResourcePlugin; -impl, F> Default for ExtractResourcePlugin { +impl, L: AppLabel, F> Default for ExtractResourcePlugin { fn default() -> Self { Self(PhantomData) } } impl< - L: AppLabel + Default, R: ExtractResource, + L: AppLabel + Default, F: 'static + Send + Sync, - > Plugin for ExtractResourcePlugin + > Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { if let Some(render_app) = app.get_sub_app_mut(L::default()) { - render_app.add_systems(ExtractSchedule, extract_resource::); + render_app.add_systems(ExtractSchedule, extract_resource::); } else { once!(bevy_log::error!( "Render app did not exist when trying to add `extract_resource` for <{}>.", @@ -61,7 +61,7 @@ impl< } /// This system extracts the resource of the corresponding [`Resource`] type -pub fn extract_resource, F>( +pub fn extract_resource, L: AppLabel, F>( mut commands: Commands, main_resource: Extract>>, target_resource: Option>, diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_render/src/sync_component.rs index cd9a98beeeeda..7f84876d00c31 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_render/src/sync_component.rs @@ -31,11 +31,11 @@ use bevy_log::warn_once; /// /// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin /// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin -pub struct SyncComponentPlugin(PhantomData<(L, C, F)>); +pub struct SyncComponentPlugin(PhantomData<(C, L, F)>); -// pub type SyncComponentPlugin = SyncComponentPlugin; +// pub type SyncComponentPlugin = SyncComponentPlugin; -impl, F> Default for SyncComponentPlugin { +impl, L: AppLabel, F> Default for SyncComponentPlugin { fn default() -> Self { Self(PhantomData) } @@ -58,10 +58,10 @@ pub trait SyncComponent: Component { } impl< - L: AppLabel + Default + Clone + Copy + Eq, C: SyncComponent, + L: AppLabel + Default + Clone + Copy + Eq, F: Send + Sync + 'static, - > Plugin for SyncComponentPlugin + > Plugin for SyncComponentPlugin { fn build(&self, app: &mut App) { app.register_required_components::>(); From ce9dc3fd2a6dbba0f39c47ad83a9509cf06777eb Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 15 Jul 2026 22:41:40 +0800 Subject: [PATCH 12/14] Update _release-content/migration-guides/extract-extract.md Co-authored-by: Kevin Chen --- _release-content/migration-guides/extract-extract.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_release-content/migration-guides/extract-extract.md b/_release-content/migration-guides/extract-extract.md index c6186b5b68f9f..7ee65a4aae1a1 100644 --- a/_release-content/migration-guides/extract-extract.md +++ b/_release-content/migration-guides/extract-extract.md @@ -28,7 +28,7 @@ impl SyncComponent for TemporalAntiAliasing { ... } pub struct Foo { ... } ``` -You can also extract a component from the main subapp to the render subapp, and to the audio subapp +You can now extract a component from the main subapp to multiple subapps. To extract a component to multiple subapps, list them as arguments to `extract_app`: ```rust,ignore #[derive(Component, Clone, Debug, ExtractComponent)] From 39547afc8bd0425c44392786df89d7f2ed2978b0 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Wed, 15 Jul 2026 22:52:06 +0800 Subject: [PATCH 13/14] Remove --- crates/bevy_render/src/extract_plugin.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_render/src/extract_plugin.rs index 8160e28e35f67..72d553938bbcd 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_render/src/extract_plugin.rs @@ -427,7 +427,6 @@ mod test { app.add_systems(Startup, |mut commands: Commands| { commands.spawn(( - RenderComponent, RenderComponentSeparateA, RenderComponentSeparateB, RenderComponentSeparateBoth, From 25f8823bacc30fa55247e86aae9e29c9d738b740 Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Sun, 26 Jul 2026 23:12:54 +0800 Subject: [PATCH 14/14] Feedback --- crates/bevy_app/src/app.rs | 3 +- .../macros/src/extract_component.rs | 61 ++++++------------- crates/bevy_render/src/sync_world.rs | 6 +- crates/bevy_render/src/view/window/mod.rs | 6 +- 4 files changed, 26 insertions(+), 50 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 50112e27df620..0e13d9f5a6700 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -36,7 +36,8 @@ use std::{ }; bevy_ecs::define_label!( - /// A strongly-typed class of labels used to identify an [`App`]. + /// A strongly-typed class of labels used to uniquely identify an [`App`]. + /// An [`AppLabel`] should not be an enum. #[diagnostic::on_unimplemented( note = "consider annotating `{Self}` with `#[derive(AppLabel)]`" )] diff --git a/crates/bevy_render/macros/src/extract_component.rs b/crates/bevy_render/macros/src/extract_component.rs index ec802fea534a0..c9bae62641ba1 100644 --- a/crates/bevy_render/macros/src/extract_component.rs +++ b/crates/bevy_render/macros/src/extract_component.rs @@ -1,10 +1,7 @@ use bevy_macro_utils::fq_std::{FQClone, FQOption}; use proc_macro::TokenStream; use quote::quote; -use syn::{ - parse_macro_input, parse_quote, punctuated::Punctuated, DeriveInput, MacroDelimiter, Meta, - MetaList, Path, -}; +use syn::{parse_macro_input, parse_quote, punctuated::Punctuated, DeriveInput, Path}; pub fn derive_extract_component(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); @@ -23,40 +20,28 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let app_labels: Vec = match ast.attrs.iter().find(|a| a.path().is_ident("extract_app")) { - Some(attr) => { - if let Meta::List(meta_list) = &attr.meta { - if let MacroDelimiter::Paren(_) = meta_list.delimiter { - match parse_to_paths(meta_list) { - Ok(labels) => labels, - Err(e) => return e.into(), - } - } else { - return syn::Error::new_spanned( - &ast.ident, - "ExtractComponent requires #[extract_app(MyAppLabelA,MyAppLabelB)] to specify the target sub-app(s) in (parentheses)", - ) - .to_compile_error() - .into(); - } - } else { + let Some(attr) = ast.attrs.iter().find(|a| a.path().is_ident("extract_app")) else { + return syn::Error::new_spanned( + &ast.ident, + "ExtractComponent requires #[extract_app(MyAppLabelA, MyAppLabelB)] to specify the target sub-app(s)", + ) + .to_compile_error() + .into(); + }; + + let app_labels: Vec = + match attr.parse_args_with(Punctuated::::parse_terminated) { + Ok(labels) if labels.is_empty() => { return syn::Error::new_spanned( - &ast.ident, - "ExtractComponent requires #[extract_app(MyAppLabelA,MyAppLabelB)] to specify the target sub-app(s) as a list", + attr, + "#[extract_app] requires at least one AppLabel, e.g. #[extract_app(RenderApp)]", ) .to_compile_error() .into(); } - } - None => { - return syn::Error::new_spanned( - &ast.ident, - "ExtractComponent requires #[extract_app(MyAppLabelA,MyAppLabelB)] to specify the target sub-app(s)", - ) - .to_compile_error() - .into(); - } - }; + Ok(labels) => labels.into_iter().collect(), + Err(e) => return e.to_compile_error().into(), + }; let filter = if let Some(attr) = ast .attrs @@ -115,13 +100,3 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { }) ).collect() } - -fn parse_to_paths(meta_list: &MetaList) -> Result, proc_macro2::TokenStream> { - meta_list - .parse_args_with(|input: syn::parse::ParseStream| { - let result = Punctuated::::parse_terminated(input); - - result.map(|paths| paths.iter().cloned().collect::>()) - }) - .map_err(|e| e.to_compile_error()) -} diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index dcb18fd3a2998..d7da53c780d3b 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -122,8 +122,7 @@ impl Plugin for SyncWorldPlugin { /// /// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin /// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin -#[derive(Component, Copy, Clone, Debug, Default, Reflect)] -#[reflect(Component, Default, Clone)] +#[derive(Component, Copy, Clone, Debug, Default)] #[component(storage = "SparseSet")] pub struct SyncToSubWorld(PhantomData); @@ -132,9 +131,8 @@ pub type SyncToRenderWorld = SyncToSubWorld; /// Component added on the main world entities that are synced to the Sub World in order to keep track of the corresponding sub world entity. /// /// Can also be used as a newtype wrapper for sub world entities. -#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Reflect)] +#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq)] #[component(clone_behavior = Ignore)] -#[reflect(Component, Clone)] pub struct SubEntity(#[deref] Entity, PhantomData); pub type RenderEntity = SubEntity; diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index a2d64eb563f98..cd189169ee4f3 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -34,14 +34,16 @@ impl Plugin for WindowRenderPlugin { // a dependency to `bevy_window` { app.add_observer(|trigger: On, mut commands: Commands| { - commands.entity(trigger.entity).insert(SyncToRenderWorld); + commands + .entity(trigger.entity) + .insert(SyncToRenderWorld::default()); }); // The primary window gets added before this plugin so we can't rely on the observer let _ = app.world_mut().run_system_once( |mut commands: Commands, windows: Query>| { for entity in &windows { - commands.entity(entity).insert(SyncToRenderWorld); + commands.entity(entity).insert(SyncToRenderWorld::default()); } }, );