Synchronize removed components with the render world (#15582)

# Objective

Fixes #15560
Fixes (most of) #15570

Currently a lot of examples (and presumably some user code) depend on
toggling certain render features by adding/removing a single component
to an entity, e.g. `SpotLight` to toggle a light. Because of the
retained render world this no longer works: Extract will add any new
components, but when it is removed the entity persists unchanged in the
render world.

## Solution

Add `SyncComponentPlugin<C: Component>` that registers
`SyncToRenderWorld` as a required component for `C`, and adds a
component hook that will clear all components from the render world
entity when `C` is removed. We add this plugin to
`ExtractComponentPlugin` which fixes most instances of the problem. For
custom extraction logic we can manually add `SyncComponentPlugin` for
that component.

We also rename `WorldSyncPlugin` to `SyncWorldPlugin` so we start a
naming convention like all the `Extract` plugins.

In this PR I also fixed a bunch of breakage related to the retained
render world, stemming from old code that assumed that `Entity` would be
the same in both worlds.

I found that using the `RenderEntity` wrapper instead of `Entity` in
data structures when referring to render world entities makes intent
much clearer, so I propose we make this an official pattern.

## Testing

Run examples like

```
cargo run --features pbr_multi_layer_material_textures --example clearcoat
cargo run --example volumetric_fog
```

and see that they work, and that toggles work correctly. But really we
should test every single example, as we might not even have caught all
the breakage yet.

---

## Migration Guide

The retained render world notes should be updated to explain this edge
case and `SyncComponentPlugin`

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Trashtalk217 <trashtalk217@gmail.com>
This commit is contained in:
Kristoffer Søholm 2024-10-09 00:23:17 +02:00 committed by GitHub
parent 45eff09213
commit 2d1b4939d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 151 additions and 84 deletions

View file

@ -2,7 +2,7 @@ use bevy_ecs::prelude::*;
use bevy_render::{
render_resource::{StorageBuffer, UniformBuffer},
renderer::{RenderDevice, RenderQueue},
world_sync::RenderEntity,
sync_world::RenderEntity,
Extract,
};
use bevy_utils::{Entry, HashMap};

View file

@ -6,7 +6,7 @@ use crate::{
};
use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::world_sync::SyncToRenderWorld;
use bevy_render::sync_world::SyncToRenderWorld;
use bevy_render::{
camera::{
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,

View file

@ -55,9 +55,9 @@ use bevy_render::{
TextureFormat, TextureUsages,
},
renderer::RenderDevice,
sync_world::RenderEntity,
texture::TextureCache,
view::{Msaa, ViewDepthTexture},
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};

View file

@ -11,8 +11,8 @@ use bevy_render::{
extract_component::ExtractComponent,
primitives::Frustum,
render_resource::{LoadOp, TextureUsages},
sync_world::SyncToRenderWorld,
view::{ColorGrading, Msaa, VisibleEntities},
world_sync::SyncToRenderWorld,
};
use bevy_transform::prelude::{GlobalTransform, Transform};
use serde::{Deserialize, Serialize};

View file

@ -89,9 +89,9 @@ use bevy_render::{
Texture, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
},
renderer::RenderDevice,
sync_world::RenderEntity,
texture::{BevyDefault, ColorAttachment, Image, TextureCache},
view::{ExtractedView, ViewDepthTexture, ViewTarget},
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{tracing::warn, HashMap};

View file

@ -46,12 +46,12 @@ use bevy_render::{
TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
},
renderer::{RenderContext, RenderDevice},
sync_world::RenderEntity,
texture::{BevyDefault, CachedTexture, TextureCache},
view::{
prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
ViewUniformOffset, ViewUniforms,
},
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{info_once, prelude::default, warn_once};

View file

@ -32,9 +32,9 @@ use bevy_render::{
TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
},
renderer::{RenderContext, RenderDevice},
sync_world::RenderEntity,
texture::{BevyDefault, CachedTexture, TextureCache},
view::{ExtractedView, Msaa, ViewTarget},
world_sync::RenderEntity,
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
};
use bevy_utils::tracing::warn;

View file

@ -105,7 +105,7 @@ use {
ShaderStages, ShaderType, VertexFormat,
},
renderer::RenderDevice,
world_sync::TemporaryRenderEntity,
sync_world::TemporaryRenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
},
bytemuck::cast_slice,

View file

@ -15,8 +15,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
mesh::Mesh3d,
primitives::{CascadesFrusta, CubemapFrusta, Frustum},
sync_world::SyncToRenderWorld,
view::{InheritedVisibility, ViewVisibility, Visibility},
world_sync::SyncToRenderWorld,
};
use bevy_transform::components::{GlobalTransform, Transform};

View file

@ -20,7 +20,7 @@ use bevy_render::{
UniformBuffer,
},
renderer::{RenderDevice, RenderQueue},
world_sync::RenderEntity,
sync_world::RenderEntity,
Extract,
};
use bevy_utils::{hashbrown::HashSet, tracing::warn};

View file

@ -127,6 +127,7 @@ use bevy_render::{
render_asset::prepare_assets,
render_graph::RenderGraph,
render_resource::Shader,
sync_component::SyncComponentPlugin,
texture::{GpuImage, Image},
view::{check_visibility, VisibilitySystems},
ExtractSchedule, Render, RenderApp, RenderSet,
@ -314,7 +315,6 @@ impl Plugin for PbrPlugin {
.register_type::<PointLight>()
.register_type::<PointLightShadowMap>()
.register_type::<SpotLight>()
.register_type::<DistanceFog>()
.register_type::<ShadowFilteringMethod>()
.init_resource::<AmbientLight>()
.init_resource::<GlobalVisibleClusterableObjects>()
@ -346,6 +346,11 @@ impl Plugin for PbrPlugin {
VolumetricFogPlugin,
ScreenSpaceReflectionsPlugin,
))
.add_plugins((
SyncComponentPlugin::<DirectionalLight>::default(),
SyncComponentPlugin::<PointLight>::default(),
SyncComponentPlugin::<SpotLight>::default(),
))
.configure_sets(
PostUpdate,
(

View file

@ -1,4 +1,4 @@
use bevy_render::{view::Visibility, world_sync::SyncToRenderWorld};
use bevy_render::view::Visibility;
use super::*;
@ -57,8 +57,7 @@ use super::*;
CascadeShadowConfig,
CascadesVisibleEntities,
Transform,
Visibility,
SyncToRenderWorld
Visibility
)]
pub struct DirectionalLight {
/// The color of the light.

View file

@ -1,4 +1,4 @@
use bevy_render::{view::Visibility, world_sync::SyncToRenderWorld};
use bevy_render::view::Visibility;
use super::*;
@ -21,13 +21,7 @@ use super::*;
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting)
#[derive(Component, Debug, Clone, Copy, Reflect)]
#[reflect(Component, Default, Debug)]
#[require(
CubemapFrusta,
CubemapVisibleEntities,
Transform,
Visibility,
SyncToRenderWorld
)]
#[require(CubemapFrusta, CubemapVisibleEntities, Transform, Visibility)]
pub struct PointLight {
/// The color of this light source.
pub color: Color,

View file

@ -1,4 +1,4 @@
use bevy_render::{view::Visibility, world_sync::SyncToRenderWorld};
use bevy_render::view::Visibility;
use super::*;
@ -9,7 +9,7 @@ use super::*;
/// the transform, and can be specified with [`Transform::looking_at`](Transform::looking_at).
#[derive(Component, Debug, Clone, Copy, Reflect)]
#[reflect(Component, Default, Debug)]
#[require(Frustum, VisibleMeshEntities, Transform, Visibility, SyncToRenderWorld)]
#[require(Frustum, VisibleMeshEntities, Transform, Visibility)]
pub struct SpotLight {
/// The color of the light.
///

View file

@ -21,9 +21,9 @@ use bevy_render::{
render_resource::{DynamicUniformBuffer, Sampler, Shader, ShaderType, TextureView},
renderer::{RenderDevice, RenderQueue},
settings::WgpuFeatures,
sync_world::RenderEntity,
texture::{FallbackImage, GpuImage, Image},
view::ExtractedView,
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::{components::Transform, prelude::GlobalTransform};

View file

@ -3,7 +3,7 @@ mod prepass_bindings;
use bevy_render::{
mesh::{Mesh3d, MeshVertexBufferLayoutRef, RenderMesh},
render_resource::binding_types::uniform_buffer,
world_sync::RenderEntity,
sync_world::RenderEntity,
};
pub use prepass_bindings::*;

View file

@ -8,7 +8,7 @@ use bevy_ecs::{
system::lifetimeless::Read,
};
use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_render::world_sync::RenderEntity;
use bevy_render::sync_world::RenderEntity;
use bevy_render::{
diagnostic::RecordDiagnostics,
mesh::RenderMesh,

View file

@ -30,9 +30,9 @@ use bevy_render::{
*,
},
renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue},
sync_world::RenderEntity,
texture::{CachedTexture, TextureCache},
view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms},
world_sync::RenderEntity,
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_utils::{

View file

@ -51,6 +51,7 @@ use bevy_render::{
mesh::{Mesh, Meshable},
render_graph::{RenderGraphApp, ViewNodeRunner},
render_resource::{Shader, SpecializedRenderPipelines},
sync_component::SyncComponentPlugin,
texture::Image,
view::{InheritedVisibility, ViewVisibility, Visibility},
ExtractSchedule, Render, RenderApp, RenderSet,
@ -231,6 +232,8 @@ impl Plugin for VolumetricFogPlugin {
app.register_type::<VolumetricFog>()
.register_type::<VolumetricLight>();
app.add_plugins(SyncComponentPlugin::<FogVolume>::default());
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

View file

@ -36,9 +36,9 @@ use bevy_render::{
TextureSampleType, TextureUsages, VertexState,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
sync_world::RenderEntity,
texture::{BevyDefault as _, GpuImage, Image},
view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniformOffset},
world_sync::RenderEntity,
Extract,
};
use bevy_transform::components::GlobalTransform;

View file

@ -6,12 +6,12 @@ use crate::{
render_asset::RenderAssets,
render_graph::{InternedRenderSubGraph, RenderSubGraph},
render_resource::TextureView,
sync_world::{RenderEntity, SyncToRenderWorld},
texture::GpuImage,
view::{
ColorGrading, ExtractedView, ExtractedWindows, GpuCulling, Msaa, RenderLayers, Visibility,
VisibleEntities,
},
world_sync::{RenderEntity, SyncToRenderWorld},
Extract,
};
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};

View file

@ -1,8 +1,9 @@
use crate::{
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue},
sync_component::SyncComponentPlugin,
sync_world::RenderEntity,
view::ViewVisibility,
world_sync::{RenderEntity, SyncToRenderWorld},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin};
@ -12,7 +13,6 @@ use bevy_ecs::{
prelude::*,
query::{QueryFilter, QueryItem, ReadOnlyQueryData},
system::lifetimeless::Read,
world::OnAdd,
};
use core::{marker::PhantomData, ops::Deref};
@ -160,15 +160,6 @@ fn prepare_uniform_components<C>(
/// This plugin extracts the components into the render world for synced entities.
///
/// To do so, it sets up the [`ExtractSchedule`] step for the specified [`ExtractComponent`].
///
/// # Warning
///
/// Be careful when removing the [`ExtractComponent`] from an entity. When an [`ExtractComponent`]
/// is added to an entity, that entity is automatically synced with the render world (see also
/// [`WorldSyncPlugin`](crate::world_sync::WorldSyncPlugin)). When removing the entity in the main
/// world, the synced entity also gets removed. However, if only the [`ExtractComponent`] is removed
/// this *doesn't* happen, and the synced entity stays around with the old extracted data.
/// We recommend despawning the entire entity, instead of only removing [`ExtractComponent`].
pub struct ExtractComponentPlugin<C, F = ()> {
only_extract_visible: bool,
marker: PhantomData<fn() -> (C, F)>,
@ -194,10 +185,8 @@ impl<C, F> ExtractComponentPlugin<C, F> {
impl<C: ExtractComponent> Plugin for ExtractComponentPlugin<C> {
fn build(&self, app: &mut App) {
// TODO: use required components
app.observe(|trigger: Trigger<OnAdd, C>, mut commands: Commands| {
commands.entity(trigger.entity()).insert(SyncToRenderWorld);
});
app.add_plugins(SyncComponentPlugin::<C>::default());
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
if self.only_extract_visible {
render_app.add_systems(ExtractSchedule, extract_visible_components::<C>);
@ -219,7 +208,7 @@ impl<T: Asset> ExtractComponent for Handle<T> {
}
}
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`SyncToRenderWorld`].
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToRenderWorld`].
fn extract_components<C: ExtractComponent>(
mut commands: Commands,
mut previous_len: Local<usize>,
@ -235,7 +224,7 @@ fn extract_components<C: ExtractComponent>(
commands.insert_or_spawn_batch(values);
}
/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`SyncToRenderWorld`].
/// 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<C: ExtractComponent>(
mut commands: Commands,
mut previous_len: Local<usize>,

View file

@ -30,7 +30,7 @@ use core::ops::{Deref, DerefMut};
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_render::Extract;
/// use bevy_render::world_sync::RenderEntity;
/// use bevy_render::sync_world::RenderEntity;
/// # #[derive(Component)]
/// // Do make sure to sync the cloud entities before extracting them.
/// # struct Cloud;

View file

@ -5,6 +5,7 @@ use crate::{
render_resource::{Buffer, BufferUsages, Extent3d, ImageDataLayout, Texture, TextureFormat},
renderer::{render_system, RenderDevice},
storage::{GpuShaderStorageBuffer, ShaderStorageBuffer},
sync_world::MainEntity,
texture::{GpuImage, TextureFormatPixelInfo},
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
};
@ -232,7 +233,7 @@ fn prepare_buffers(
mut buffer_pool: ResMut<GpuReadbackBufferPool>,
gpu_images: Res<RenderAssets<GpuImage>>,
ssbos: Res<RenderAssets<GpuShaderStorageBuffer>>,
handles: Query<(Entity, &Readback)>,
handles: Query<(&MainEntity, &Readback)>,
) {
for (entity, readback) in handles.iter() {
match readback {
@ -254,7 +255,7 @@ fn prepare_buffers(
);
let (tx, rx) = async_channel::bounded(1);
readbacks.requested.push(GpuReadback {
entity,
entity: entity.id(),
src: ReadbackSource::Texture {
texture: gpu_image.texture.clone(),
layout,
@ -272,7 +273,7 @@ fn prepare_buffers(
let buffer = buffer_pool.get(&render_device, size);
let (tx, rx) = async_channel::bounded(1);
readbacks.requested.push(GpuReadback {
entity,
entity: entity.id(),
src: ReadbackSource::Buffer {
src_start: 0,
dst_start: 0,

View file

@ -38,9 +38,10 @@ pub mod renderer;
pub mod settings;
mod spatial_bundle;
pub mod storage;
pub mod sync_component;
pub mod sync_world;
pub mod texture;
pub mod view;
pub mod world_sync;
/// The render prelude.
///
@ -77,8 +78,8 @@ use extract_resource::ExtractResourcePlugin;
use globals::GlobalsPlugin;
use render_asset::RenderAssetBytesPerFrame;
use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
use world_sync::{
despawn_temporary_render_entities, entity_sync_system, SyncToRenderWorld, WorldSyncPlugin,
use sync_world::{
despawn_temporary_render_entities, entity_sync_system, SyncToRenderWorld, SyncWorldPlugin,
};
use crate::gpu_readback::GpuReadbackPlugin;
@ -371,7 +372,7 @@ impl Plugin for RenderPlugin {
GlobalsPlugin,
MorphPlugin,
BatchingPlugin,
WorldSyncPlugin,
SyncWorldPlugin,
StoragePlugin,
GpuReadbackPlugin::default(),
));

View file

@ -81,7 +81,7 @@ impl Drop for RenderAppChannels {
/// The plugin is dependent on the [`RenderApp`] added by [`crate::RenderPlugin`] and so must
/// be added after that plugin. If it is not added after, the plugin will do nothing.
///
/// A single frame of execution looks something like below
/// A single frame of execution looks something like below
///
/// ```text
/// |---------------------------------------------------------------------------|
@ -92,7 +92,7 @@ impl Drop for RenderAppChannels {
/// ```
///
/// - `sync` is the step where the entity-entity mapping between the main and render world is updated.
/// This is run on the main app's thread. For more information checkout [`WorldSyncPlugin`].
/// This is run on the main app's thread. For more information checkout [`SyncWorldPlugin`].
/// - `extract` is the step where data is copied from the main world to the render world.
/// This is run on the main app's thread.
/// - On the render thread, we first apply the `extract commands`. This is not run during extract, so the
@ -104,7 +104,7 @@ impl Drop for RenderAppChannels {
/// - And finally the `main app schedule` is run.
/// - Once both the `main app schedule` and the `render schedule` are finished running, `extract` is run again.
///
/// [`WorldSyncPlugin`]: crate::world_sync::WorldSyncPlugin
/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin
#[derive(Default)]
pub struct PipelinedRenderingPlugin;

View file

@ -0,0 +1,42 @@
use core::marker::PhantomData;
use bevy_app::{App, Plugin};
use bevy_ecs::component::Component;
use crate::sync_world::{EntityRecord, PendingSyncEntity, SyncToRenderWorld};
/// Plugin that registers a component for automatic sync to the render world. See [`SyncWorldPlugin`] for more information.
///
/// This plugin is automatically added by [`ExtractComponentPlugin`], and only needs to be added for manual extraction implementations.
///
/// # Implementation details
///
/// It adds [`SyncToRenderWorld`] 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.
///
/// # Warning
/// When the component is removed from the main world entity, all components are removed from the entity in the render world.
/// This is done in order to handle components with custom extraction logic and derived state.
///
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin
pub struct SyncComponentPlugin<C: Component>(PhantomData<C>);
impl<C: Component> Default for SyncComponentPlugin<C> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<C: Component> Plugin for SyncComponentPlugin<C> {
fn build(&self, app: &mut App) {
app.register_required_components::<C, SyncToRenderWorld>();
app.world_mut().register_component_hooks::<C>().on_remove(
|mut world, entity, _component_id| {
let mut pending = world.resource_mut::<PendingSyncEntity>();
pending.push(EntityRecord::ComponentRemoved(entity));
},
);
}
}

View file

@ -13,11 +13,18 @@ use bevy_reflect::Reflect;
/// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world.
///
/// All entities with the [`SyncToRenderWorld`] 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.
///
/// # Implementation
///
/// Bevy's renderer is architected independently from the main app.
/// It operates in its own separate ECS [`World`], so the renderer logic can run in parallel with the main world logic.
/// This is called "Pipelined Rendering", see [`PipelinedRenderingPlugin`] for more information.
///
/// [`WorldSyncPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping
/// [`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.
@ -66,14 +73,16 @@ use bevy_reflect::Reflect;
/// The render world probably cares about a `Position` component, but not a `Velocity` component.
/// The extraction happens in its own step, independently from, and after synchronization.
///
/// Moreover, [`WorldSyncPlugin`] only synchronizes *entities*. [`RenderAsset`](crate::render_asset::RenderAsset)s like meshes and textures are handled
/// Moreover, [`SyncWorldPlugin`] only synchronizes *entities*. [`RenderAsset`](crate::render_asset::RenderAsset)s like meshes and textures are handled
/// differently.
///
/// [`PipelinedRenderingPlugin`]: crate::pipelined_rendering::PipelinedRenderingPlugin
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
#[derive(Default)]
pub struct WorldSyncPlugin;
pub struct SyncWorldPlugin;
impl Plugin for WorldSyncPlugin {
impl Plugin for SyncWorldPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.init_resource::<PendingSyncEntity>();
app.observe(
@ -86,22 +95,30 @@ impl Plugin for WorldSyncPlugin {
mut pending: ResMut<PendingSyncEntity>,
query: Query<&RenderEntity>| {
if let Ok(e) = query.get(trigger.entity()) {
pending.push(EntityRecord::Removed(e.id()));
pending.push(EntityRecord::Removed(*e));
};
},
);
}
}
/// 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 render world.
///
/// This component is automatically added as a required component by [`ExtractComponentPlugin`] and [`SyncComponentPlugin`].
/// For more information see [`SyncWorldPlugin`].
///
/// NOTE: This component should persist throughout the entity's entire lifecycle.
/// If this component is removed from its entity, the entity will be despawned.
///
/// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin
/// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin
#[derive(Component, Clone, Debug, Default, Reflect)]
#[reflect[Component]]
#[component(storage = "SparseSet")]
pub struct SyncToRenderWorld;
/// 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 Render World in order to keep track of the corresponding render world entity.
///
/// Can also be used as a newtype wrapper for render world entities.
#[derive(Component, Deref, Clone, Debug, Copy)]
pub struct RenderEntity(Entity);
impl RenderEntity {
@ -111,7 +128,9 @@ impl RenderEntity {
}
}
/// Component added on the render world entities to keep track of the corresponding main world entity
/// Component added on the render 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, Clone, Debug)]
pub struct MainEntity(Entity);
impl MainEntity {
@ -127,13 +146,17 @@ impl MainEntity {
pub struct TemporaryRenderEntity;
/// A record enum to what entities with [`SyncToRenderWorld`] 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
/// 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(Entity),
Removed(RenderEntity),
/// When a component is removed from an entity, notify the render world so that the corresponding component can be
/// removed. This contains the main world entity.
ComponentRemoved(Entity),
}
// Entity Record in MainWorld pending to Sync
@ -148,8 +171,8 @@ pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut Worl
for record in pending.drain(..) {
match record {
EntityRecord::Added(e) => {
if let Ok(mut entity) = world.get_entity_mut(e) {
match entity.entry::<RenderEntity>() {
if let Ok(mut main_entity) = world.get_entity_mut(e) {
match main_entity.entry::<RenderEntity>() {
bevy_ecs::world::Entry::Occupied(_) => {
panic!("Attempting to synchronize an entity that has already been synchronized!");
}
@ -161,11 +184,24 @@ pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut Worl
};
}
}
EntityRecord::Removed(e) => {
if let Ok(ec) = render_world.get_entity_mut(e) {
EntityRecord::Removed(render_entity) => {
if let Ok(ec) = render_world.get_entity_mut(render_entity.id()) {
ec.despawn();
};
}
EntityRecord::ComponentRemoved(main_entity) => {
let Some(mut render_entity) = world.get_mut::<RenderEntity>(main_entity) else {
continue;
};
if let Ok(render_world_entity) = render_world.get_entity_mut(render_entity.id()) {
// In order to handle components that extract to derived components, we clear the entity
// and let the extraction system re-add the components.
render_world_entity.despawn();
let id = render_world.spawn(MainEntity(main_entity)).id();
render_entity.0 = id;
}
},
}
}
});
@ -207,7 +243,7 @@ mod tests {
struct RenderDataComponent;
#[test]
fn world_sync() {
fn sync_world() {
let mut main_world = World::new();
let mut render_world = World::new();
main_world.init_resource::<PendingSyncEntity>();
@ -222,7 +258,7 @@ mod tests {
mut pending: ResMut<PendingSyncEntity>,
query: Query<&RenderEntity>| {
if let Ok(e) = query.get(trigger.entity()) {
pending.push(EntityRecord::Removed(e.id()));
pending.push(EntityRecord::Removed(*e));
};
},
);

View file

@ -2,9 +2,9 @@ use crate::Sprite;
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render::{
sync_world::SyncToRenderWorld,
texture::Image,
view::{InheritedVisibility, ViewVisibility, Visibility},
world_sync::SyncToRenderWorld,
};
use bevy_transform::components::{GlobalTransform, Transform};

View file

@ -31,6 +31,7 @@ use bevy_render::{
*,
},
renderer::{RenderDevice, RenderQueue},
sync_world::{RenderEntity, TemporaryRenderEntity},
texture::{
BevyDefault, DefaultImageSampler, FallbackImage, GpuImage, Image, ImageSampler,
TextureFormatPixelInfo,
@ -39,7 +40,6 @@ use bevy_render::{
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
ViewVisibility, VisibleEntities,
},
world_sync::{RenderEntity, TemporaryRenderEntity},
Extract,
};
use bevy_transform::components::GlobalTransform;

View file

@ -15,7 +15,7 @@ use bevy_ecs::{
system::{Commands, Local, Query, Res, ResMut},
};
use bevy_math::Vec2;
use bevy_render::world_sync::TemporaryRenderEntity;
use bevy_render::sync_world::TemporaryRenderEntity;
use bevy_render::{
primitives::Aabb,
texture::Image,

View file

@ -19,9 +19,9 @@ use bevy_render::{
render_phase::*,
render_resource::{binding_types::uniform_buffer, *},
renderer::{RenderDevice, RenderQueue},
sync_world::{RenderEntity, TemporaryRenderEntity},
texture::BevyDefault,
view::*,
world_sync::{RenderEntity, TemporaryRenderEntity},
Extract, ExtractSchedule, Render, RenderSet,
};
use bevy_transform::prelude::GlobalTransform;

View file

@ -32,9 +32,9 @@ use bevy_render::{
};
use bevy_render::{
render_phase::{PhaseItem, PhaseItemExtraIndex},
sync_world::{RenderEntity, TemporaryRenderEntity},
texture::GpuImage,
view::ViewVisibility,
world_sync::{RenderEntity, TemporaryRenderEntity},
ExtractSchedule, Render,
};
use bevy_sprite::TextureAtlasLayout;

View file

@ -18,9 +18,9 @@ use bevy_render::{
render_phase::*,
render_resource::{binding_types::uniform_buffer, *},
renderer::{RenderDevice, RenderQueue},
sync_world::{RenderEntity, TemporaryRenderEntity},
texture::BevyDefault,
view::*,
world_sync::{RenderEntity, TemporaryRenderEntity},
Extract, ExtractSchedule, Render, RenderSet,
};
use bevy_transform::prelude::GlobalTransform;

View file

@ -16,9 +16,9 @@ use bevy_render::{
render_phase::*,
render_resource::{binding_types::uniform_buffer, *},
renderer::{RenderDevice, RenderQueue},
sync_world::{RenderEntity, TemporaryRenderEntity},
texture::{BevyDefault, GpuImage, Image, TRANSPARENT_IMAGE_HANDLE},
view::*,
world_sync::{RenderEntity, TemporaryRenderEntity},
Extract, ExtractSchedule, Render, RenderSet,
};
use bevy_sprite::{

View file

@ -9,7 +9,6 @@ use bevy::{
math::vec3,
pbr::{FogVolume, VolumetricFog, VolumetricLight},
prelude::*,
render::world_sync::SyncToRenderWorld,
};
/// Entry point.
@ -44,9 +43,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// up.
scattering: 1.0,
..default()
})
// indicates that this fog volume needs to be Synchronized to the render world
.insert(SyncToRenderWorld);
});
// Spawn a bright directional light that illuminates the fog well.
commands.spawn((