Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retain Rendering World #14449

Closed
wants to merge 66 commits into from
Closed

Retain Rendering World #14449

wants to merge 66 commits into from

Conversation

re0312
Copy link
Contributor

@re0312 re0312 commented Jul 23, 2024

Objective

Solution

  • Transition to a strategy of maintaining entity-to-entity mapping between the main world and render world, abandoning the method of removing all entities every frame.
  • Use Observer to listen the components that tis entity needs to be synchronized to the render world.
  • Before executing the extract schedule, the entity_sync_system will be run first, which is responsible for maintaining the entity mapping between the main world and the rendering world

Bevy Schedule
image

Alternative(s)

we could using Resource to manage all rendering entity component data so that we don't have any entities at all.

Advantages

  • We can continue to use ECS in rendering, which can bring more performance advantages in many aspects(Retain redering world + update in place)
  • More consistent primitives, as Bevy is entirely based on ECS.

Drawbacks

  • Once a component has a dependency relationship on other main world entities, this relationship must also be synchronized to the rendering world, which undoubtedly increases the user's mental burden
  • Not all the entities are in sync with the rendering world, And that makes it pretty confusing to tell apart the entities in the main world from those in the rendering world when we're in the middle of rendering stuff. And considering our extensive use of EntityHashmap to handle rendering data. that's could made everything even more of a headache to deal with.

Performance

cargo run --release --example many_cubes --features bevy/trace_tracy
red is main
image

cargo run --release --example many_foxes --features bevy/trace_tracy
image

cargo run --release --example many_buttons --features bevy/trace_tracy

image

~30% loss
The majority of the performance loss from the spawn(~3ms) and despawn(~3ms) operations of ephemeral entity.

Next Steps

  1. Stop spawn temporary entities and depsawn it every frame , it could persist in render world
  2. Update in place for instance data instead re-insert it per frame

Migration Guide

Render world no longer clear all entities per frame , Instead, You must manually control the spawn and despawn of render entities properly.

Before this pr ,extract would copy the related main world entity and its data into render world. Then, the render world clear all render entities at the end of the frame to reserve enough entity space, ensuring that no main world entity ID would be occupied when next extract.

For the sake of * as entities, we turn to an entity-to-entity mapping strategy to sync between main world entity and render world entity, where each synchronized main entity has a RenderEntity component that holds an Entity ID pointer to its unique counterpart entity in the render world.

image

To establish a "Synchronous Relationship", you can add a SyncRenderWorld component to an entity, indicating that it needs to be synchronized to the render world. (Currently, its added by default in Camera2dBundle , Camera3dBundle, DirectionalLightBundle, SpotLightBundle, PointLightBundle, SpriteBundle, and SpriteSheetBundle.)

Once a synchronized main entity despawned ,its corresponding render entity will automatically be purged before next extract.

Temporary entities are part of entities in render world , bevy's renderer spawns numerous temporary entities for rendering per frame , which need to be cleared at the end of each frame.
To avoid duplicating spawn these entities each frame,you can add a TemporaryRenderEntity component to it. Bevy will then automatically clean up it at the end of each frame. But as best you should turn to persisit entity in render world instead of spawn and clear them every frame.

ExtractComponentPlugin now only extracts "Synchronized" entity to render world.

Co-Authored-By:Trashtalk217 Trashtalk217@gmail.com

@re0312
Copy link
Contributor Author

re0312 commented Jul 23, 2024

Added @Trashtalk217 as coauthor ,because this pr was highly inspired by his work #14252

@alice-i-cecile alice-i-cecile added this to the 0.15 milestone Jul 23, 2024
@alice-i-cecile alice-i-cecile added A-Rendering Drawing game state to the screen D-Complex Quite challenging from either a design or technical perspective. Ask for help! X-Controversial There is active debate or serious implications around merging this PR S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jul 23, 2024
@ecoskey
Copy link
Contributor

ecoskey commented Aug 28, 2024

I like the move to a single SyncRenderWorld component, and the possibility of using required components for this, especially now that that PR's been merged. It definitely simplifies the mental model and keeping track of what component actually marks an entity for syncing.

IMO there's still some things worth ironing out in a followup PR (like a removal/filter check in ExtractComponent), but I'm generally in approval now.

@wilk10
Copy link
Contributor

wilk10 commented Aug 29, 2024

Coming from a user point of view, I have read the PR and superficially followed the discussion on Discord, but I don't know all the nitty gritty.

I think there's something missing in the documentation about entities that are not synchronized. Are all entities that are not synced (Without<SyncRenderWorld>) temporary entities? Or are there three groups: synced, temporary and neither synced nor temporary? I guess the confusion comes from the asymmetry between Sprites being synced and Camera2d not being synced, while 3D Meshes are not synced, but Camera3d is. It's a bit confusing why that is the case. And what about 2D Meshes?

@re0312
Copy link
Contributor Author

re0312 commented Aug 29, 2024

Are all entities that are not synced (Without<SyncRenderWorld>) temporary entities?

No , Temporary entities are merely part of entities in render world . previously, bevy's renderer spawns numerous temporary entities for rendering per frame , which need to be cleared at the end of each frame. IMO, it should be unnecessary once retain render world landed,
However, to minimize the impact of this PR, I have retained this concept and left it to future pr.

Or are there three groups: synced, temporary and neither synced nor temporary?

There are only two groups of entities : synced and unsynced , A synced entity X is one in the main world that has a corresponding ID Y in the render world( X !=Y ).
for a synced main world entity it corresponding render entity ID is stored in the component RenderEntity.

Camera2d not being synced

That's my bad ,Camera2D is synced but not mentioned in the pr description.

while 3D Meshes are not synced, but Camera3d is. It's a bit confusing why that is the case. And what about 2D Meshes?

this is because the current 3D and 2D Mesh instances in render world are all organized within an EntityHashmap, there are no corresponding render entities at all . so it is unnecessary to sync them.

/// | | | Render wrold loop |
/// |--------------------------------------------------------------------|
/// ```
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we could cover the fact that not all entities that are extracted in the render world need to be kept in sync via SyncRenderWorld because they are handled differently (without entities) in the render world.

examples/3d/fog_volumes.rs Outdated Show resolved Hide resolved
@wilk10
Copy link
Contributor

wilk10 commented Aug 30, 2024

Thanks for the reply! It's all clear now. I left a comment about specifying it in the documentation as well.

Copy link
Contributor

@Trashtalk217 Trashtalk217 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked the documentation. It could use some work, but it's getting there.

crates/bevy_render/src/world_sync.rs Outdated Show resolved Hide resolved
///
/// Previously, `extract` will copy the related main world entity and its data into the render world , and then render world will clear all render entities at the end of frame to reserve enough entity space to ensure that no main world entity ID has been occupied during next `extract`.
///
/// With `* as entities`, we should not clear all entities in render world because some core metadata (e.g. [`Component`], [`Query`]) are also stored in the form of entity.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation should describe something as-is, not as a before and after. That's what patch notes are for. These previous two lines can be removed.

crates/bevy_render/src/world_sync.rs Outdated Show resolved Hide resolved
crates/bevy_render/src/world_sync.rs Outdated Show resolved Hide resolved
///
/// ```
///
/// To establish a "Synchronous Relationship", you can add a [`SyncRenderWorld`] component to an entity. Once a `synchronized` main entity is despawned ,its corresponding render entity will be automatically purged in the next `Sync`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// To establish a "Synchronous Relationship", you can add a [`SyncRenderWorld`] component to an entity. Once a `synchronized` main entity is despawned ,its corresponding render entity will be automatically purged in the next `Sync`
/// To establish a "Synchronous Relationship", you can add a [`SyncRenderWorld`] component to an entity. Once a synchronized main entity is despawned, its corresponding render entity will be automatically purged in the next `Sync`

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably have some example code of how people can sync their own components using this plugin.

re0312 and others added 2 commits September 1, 2024 03:40
Co-authored-by: Trashtalk217 <trashtalk217@gmail.com>
Co-authored-by: Anselmo Sampietro <ans.samp@gmail.com>
Copy link
Contributor

@bushrat011899 bushrat011899 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar enough with the rendering side of Bevy to feel comfortable approving this PR, but it does look good to my novice eyes. I don't see anything that stands out as particularly wrong (that hasn't already been pointed in e.g. documentation).

I do have a suggestion around implementing WorldQuery on RenderEntity directly, which reduces the noise in this PR quite a bit and gives the rendering people some slightly nicer ergonomics. I've tested this implementation and it works as expected, allowing RenderEntity to be a drop-in replacement for Entity.

mut removed: Extract<RemovedComponents<AutoExposureSettings>>,
) {
commands.insert_resource(ExtractedStateBuffers {
changed: changed
.iter()
.map(|(entity, settings)| (entity, settings.clone()))
.map(|(entity, settings)| (entity.id(), settings.clone()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's quite a lot of noise in this PR due to the need to insert these RenderEntity::id calls. This might be a controversial suggestion, but we can implement WorldQuery for RenderEntity so it still behaves like a Component, but also returns the inner Entity when queried directly.

unsafe impl WorldQuery for RenderEntity { ... }
/// SAFETY:
/// This implementation delegates to the existing implementation for &RenderEntity
unsafe impl WorldQuery for RenderEntity {
  type Item<'w> = Entity;
  type Fetch<'w> = ReadFetch<'w, RenderEntity>;
  type State = ComponentId;

  fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity {
      item
  }

  fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
      <&Self as WorldQuery>::shrink_fetch(fetch)
  }

  #[inline]
  unsafe fn init_fetch<'w>(
      world: UnsafeWorldCell<'w>,
      component_id: &ComponentId,
      last_run: Tick,
      this_run: Tick,
  ) -> ReadFetch<'w, RenderEntity> {
      // SAFETY: This implementation delegates to the existing implementation for &RenderEntity
      unsafe {
          <&Self as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
      }
  }

  const IS_DENSE: bool = <&Self as WorldQuery>::IS_DENSE;

  #[inline]
  unsafe fn set_archetype<'w>(
      fetch: &mut ReadFetch<'w, RenderEntity>,
      component_id: &ComponentId,
      archetype: &'w Archetype,
      table: &'w Table,
  ) {
      // SAFETY: This implementation delegates to the existing implementation for &RenderEntity
      unsafe {
          <&Self as WorldQuery>::set_archetype(fetch, component_id, archetype, table)
      }
  }

  #[inline]
  unsafe fn set_table<'w>(
      fetch: &mut ReadFetch<'w, RenderEntity>,
      component_id: &ComponentId,
      table: &'w Table,
  ) {
      // SAFETY: This implementation delegates to the existing implementation for &RenderEntity
      unsafe {
          <&Self as WorldQuery>::set_table(fetch, component_id, table)
      }
  }

  #[inline(always)]
  unsafe fn fetch<'w>(
      fetch: &mut Self::Fetch<'w>,
      entity: Entity,
      table_row: TableRow,
  ) -> Self::Item<'w> {
      // SAFETY: This implementation delegates to the existing implementation for &RenderEntity
      unsafe {
          <&Self as WorldQuery>::fetch(fetch, entity, table_row).id()
      }
  }

  fn update_component_access(
      component_id: &ComponentId,
      access: &mut FilteredAccess<ComponentId>,
  ) {
      <&Self as WorldQuery>::update_component_access(component_id, access)
  }

  fn init_state(world: &mut World) -> ComponentId {
      <&Self as WorldQuery>::init_state(world)
  }

  fn get_state(components: &Components) -> Option<Self::State> {
      <&Self as WorldQuery>::get_state(components)
  }

  fn matches_component_set(
      state: &ComponentId,
      set_contains_id: &impl Fn(ComponentId) -> bool,
  ) -> bool {
      <&Self as WorldQuery>::matches_component_set(state, set_contains_id)
  }
}

/// SAFETY: `Self` is the same as `Self::ReadOnly`
unsafe impl QueryData for RenderEntity {
  type ReadOnly = Self;
}

/// SAFETY: access is read only
unsafe impl ReadOnlyQueryData for RenderEntity {}

This would then allow for render systems to query for RenderEntity and directly receive the inner Entity, reducing the noise in this PR. Crucially, this doesn't conflict with the existing derive(Component), so you can still query for &RenderEntity, the mutable version, use it in filters, etc.

@alice-i-cecile
Copy link
Member

I think I like the WorldQuery impl 🤔

Copy link
Contributor

@NthTensor NthTensor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this generally meets or exceeds my requirements for docs. Thanks for working on that.

I can't attest any of the code at this point, maybe i'll find time later for a review.

/// | | | Main world loop |
/// | sync | extract |---------------------------------------------------|
/// | | | Render world loop |
//are/ |--------------------------------------------------------------------|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like a typo

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//are/ |--------------------------------------------------------------------|
/// |--------------------------------------------------------------------|

Copy link
Contributor

@wilk10 wilk10 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I mainly focused on the documentation and commented on a few tiny doc adjustments.
Thanks for previously including the part about assets not being synced, it's very clear now.

///
/// ```
///
/// Note that not this effectively establishes a link between the main world entity and the render world entity.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Note that not this effectively establishes a link between the main world entity and the render world entity.
/// Note that this effectively establishes a link between the main world entity and the render world entity.

/// Note that not 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 [`SyncRenderWorld`] are synced.
/// Adding [`SyncRenderWorld`] to a main world component will establish such a link.
/// Once a synchronized main entity is despawned, it's corresponding render entity will be automatically
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Once a synchronized main entity is despawned, it's corresponding render entity will be automatically
/// Once a synchronized main entity is despawned, its corresponding render entity will be automatically

/// The sync step does not handle the transfer of component data between worlds,
/// since it's often not necessary to transfer over all the components of a main world entity.
/// The render world probably cares about a `Position` component, but not a `Velocity` component.
/// The extraction happens in it's own step, independently from synchronization.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The extraction happens in it's own step, independently from synchronization.
/// The extraction happens in its own step, independently from synchronization.

Copy link
Contributor

@JMS55 JMS55 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a bunch of comments, but awesome work overall! This is much clearer and more intuitive than the previous version, and I'm happy with how this looks and the level of docs.

WorldQuery impl would be nice, but we can leave it to a followup (open an issue please!).

My only major concern is needing SyncRenderWorld on intermediate spatial entities - this seems like a huge footgun. Not sure how to solve it...

I'd also vote in favor of renaming SyncRenderWorld to SyncWithRenderWorld or SyncToRenderWorld to be even more explicit, but I don't feel too strongly on it, the name is acceptable as-is.

@@ -37,6 +38,8 @@ pub struct Camera2dBundle {
pub deband_dither: DebandDither,
pub main_texture_usages: CameraMainTextureUsages,
pub msaa: Msaa,
/// Marker component that indicates that its entity needs to be Synchronized to the render world
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For here and all other comments, please do not capitalize "Synchronized" in the middle of the sentence.


for (entity, camera, camera_projection, mut taa_settings) in
cameras_3d.iter_mut(&mut main_world)
{
let entity = entity.id();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit: entity is only used once here, I'd rather not create a new variable, and just use entity.id() directly in commands.get_or_spawn()


for (entity, camera, camera_projection, mut taa_settings) in
cameras_3d.iter_mut(&mut main_world)
{
let entity = entity.id();
let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_));
if camera.is_active && has_perspective_projection {
commands.get_or_spawn(entity).insert(taa_settings.clone());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets see if I understand this correctly: this could be just get_mut() here instead of get_or_spawn(), as the entity should already exist as camera bundles have SyncRenderWorld, correct?

(That said I do think we should leave it as get_or_spawn() in case users aren't using the bundle, this comment is just to see if I understand this PR correctly or not)

data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
*clusterable_entity,
));
if let Ok(entity) = mapper.get(*clusterable_entity) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we modify the clusterizer system to store RenderEntity on Clusters directly, instead of using another query here?

@@ -420,6 +420,9 @@ impl Plugin for PbrPlugin {
)
.init_resource::<LightMeta>();

render_app.world_mut().observe(add_light_view_entities);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these two lines doing here ?

if let Some(mut entity) = world.get_entity_mut(e) {
match entity.entry::<RenderEntity>() {
bevy_ecs::world::Entry::Occupied(_) => {
warn!("Attempting to synchronize an entity that has already been synchronized!");
Copy link
Contributor

@JMS55 JMS55 Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warn -> error? This should never happen, right?

}

pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut World) {
main_world.resource_scope(|world, mut pending: Mut<PendingSyncEntity>| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function could use some splitting up into closures or smaller functions to reduce the nesting 😬

}
EntityRecord::Removed(e) => {
if let Some(ec) = render_world.get_entity_mut(e) {
ec.despawn_recursive();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is recursive despawn what we want here?


local.extend(query.iter());

// ensure next frame allocation keeps order
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// ensure next frame allocation keeps order
// Ensure next frame allocation keeps order

Also, can you expand the comment a bit more? What does this mean, and why are we doing it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also going to pass on this file for now, but we'll need to review it at some point soon.

@ecoskey
Copy link
Contributor

ecoskey commented Sep 12, 2024

One last nit from me: while it doesn't affect any of the in-engine uses of ExtractComponentPlugin afaik, deletions of components in the main world won't propagate to their render world counterparts. I'd like to at least see a notice that ExtractComponentPlugin has this caveat, and recommend to only use it with transient entities or those whose components are not expected to be removed (aside from when the entity is despawned)

@JMS55
Copy link
Contributor

JMS55 commented Sep 12, 2024

Something else I forgot to mention, I'd like to mention in the code somewhere that we have to make sure that RenderEntity IDs are consistent across frames, i.e. if entity A has render entity B in one frame, then that same mapping will exist in all subsequent frames. A test for it would be awesome as well if you feel up to it.

@alice-i-cecile
Copy link
Member

SyncToRenderWorld gets my vote: extremely clear.

@JMS55 JMS55 closed this Sep 27, 2024
github-merge-queue bot pushed a commit that referenced this pull request Sep 30, 2024
- Adopted from #14449
- Still fixes #12144.

## Migration Guide

The retained render world is a complex change: migrating might take one
of a few different forms depending on the patterns you're using.

For every example, we specify in which world the code is run. Most of
the changes affect render world code, so for the average Bevy user who's
using Bevy's high-level rendering APIs, these changes are unlikely to
affect your code.

### Spawning entities in the render world

Previously, if you spawned an entity with `world.spawn(...)`,
`commands.spawn(...)` or some other method in the rendering world, it
would be despawned at the end of each frame. In 0.15, this is no longer
the case and so your old code could leak entities. This can be mitigated
by either re-architecting your code to no longer continuously spawn
entities (like you're used to in the main world), or by adding the
`bevy_render::world_sync::TemporaryRenderEntity` component to the entity
you're spawning. Entities tagged with `TemporaryRenderEntity` will be
removed at the end of each frame (like before).

### Extract components with `ExtractComponentPlugin`

```
// main world
app.add_plugins(ExtractComponentPlugin::<ComponentToExtract>::default());
```

`ExtractComponentPlugin` has been changed to only work with synced
entities. Entities are automatically synced if `ComponentToExtract` is
added to them. However, entities are not "unsynced" if any given
`ComponentToExtract` is removed, because an entity may have multiple
components to extract. This would cause the other components to no
longer get extracted because the entity is not synced.

So be careful when only removing extracted components from entities in
the render world, because it might leave an entity behind in the render
world. The solution here is to avoid only removing extracted components
and instead despawn the entire entity.

### Manual extraction using `Extract<Query<(Entity, ...)>>`

```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(Entity, &Clusters, &Camera)>>,
) {
    for (entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(entity).insert(...);
    }
}
```
One of the primary consequences of the retained rendering world is that
there's no longer a one-to-one mapping from entity IDs in the main world
to entity IDs in the render world. Unlike in Bevy 0.14, Entity 42 in the
main world doesn't necessarily map to entity 42 in the render world.

Previous code which called `get_or_spawn(main_world_entity)` in the
render world (`Extract<Query<(Entity, ...)>>` returns main world
entities). Instead, you should use `&RenderEntity` and
`render_entity.id()` to get the correct entity in the render world. Note
that this entity does need to be synced first in order to have a
`RenderEntity`.

When performing manual abstraction, this won't happen automatically
(like with `ExtractComponentPlugin`) so add a `SyncToRenderWorld` marker
component to the entities you want to extract.

This results in the following code:
```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>,
) {
    for (render_entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(render_entity.id()).insert(...);
    }
}

// in main world, when spawning
world.spawn(Clusters::default(), Camera::default(), SyncToRenderWorld)
```

### Looking up `Entity` ids in the render world

As previously stated, there's now no correspondence between main world
and render world `Entity` identifiers.

Querying for `Entity` in the render world will return the `Entity` id in
the render world: query for `MainEntity` (and use its `id()` method) to
get the corresponding entity in the main world.

This is also a good way to tell the difference between synced and
unsynced entities in the render world, because unsynced entities won't
have a `MainEntity` component.

---------

Co-authored-by: re0312 <re0312@outlook.com>
Co-authored-by: re0312 <45868716+re0312@users.noreply.github.com>
Co-authored-by: Periwink <charlesbour@gmail.com>
Co-authored-by: Anselmo Sampietro <ans.samp@gmail.com>
Co-authored-by: Emerson Coskey <56370779+ecoskey@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
robtfm pushed a commit to robtfm/bevy that referenced this pull request Oct 4, 2024
- Adopted from bevyengine#14449
- Still fixes bevyengine#12144.

## Migration Guide

The retained render world is a complex change: migrating might take one
of a few different forms depending on the patterns you're using.

For every example, we specify in which world the code is run. Most of
the changes affect render world code, so for the average Bevy user who's
using Bevy's high-level rendering APIs, these changes are unlikely to
affect your code.

### Spawning entities in the render world

Previously, if you spawned an entity with `world.spawn(...)`,
`commands.spawn(...)` or some other method in the rendering world, it
would be despawned at the end of each frame. In 0.15, this is no longer
the case and so your old code could leak entities. This can be mitigated
by either re-architecting your code to no longer continuously spawn
entities (like you're used to in the main world), or by adding the
`bevy_render::world_sync::TemporaryRenderEntity` component to the entity
you're spawning. Entities tagged with `TemporaryRenderEntity` will be
removed at the end of each frame (like before).

### Extract components with `ExtractComponentPlugin`

```
// main world
app.add_plugins(ExtractComponentPlugin::<ComponentToExtract>::default());
```

`ExtractComponentPlugin` has been changed to only work with synced
entities. Entities are automatically synced if `ComponentToExtract` is
added to them. However, entities are not "unsynced" if any given
`ComponentToExtract` is removed, because an entity may have multiple
components to extract. This would cause the other components to no
longer get extracted because the entity is not synced.

So be careful when only removing extracted components from entities in
the render world, because it might leave an entity behind in the render
world. The solution here is to avoid only removing extracted components
and instead despawn the entire entity.

### Manual extraction using `Extract<Query<(Entity, ...)>>`

```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(Entity, &Clusters, &Camera)>>,
) {
    for (entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(entity).insert(...);
    }
}
```
One of the primary consequences of the retained rendering world is that
there's no longer a one-to-one mapping from entity IDs in the main world
to entity IDs in the render world. Unlike in Bevy 0.14, Entity 42 in the
main world doesn't necessarily map to entity 42 in the render world.

Previous code which called `get_or_spawn(main_world_entity)` in the
render world (`Extract<Query<(Entity, ...)>>` returns main world
entities). Instead, you should use `&RenderEntity` and
`render_entity.id()` to get the correct entity in the render world. Note
that this entity does need to be synced first in order to have a
`RenderEntity`.

When performing manual abstraction, this won't happen automatically
(like with `ExtractComponentPlugin`) so add a `SyncToRenderWorld` marker
component to the entities you want to extract.

This results in the following code:
```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>,
) {
    for (render_entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(render_entity.id()).insert(...);
    }
}

// in main world, when spawning
world.spawn(Clusters::default(), Camera::default(), SyncToRenderWorld)
```

### Looking up `Entity` ids in the render world

As previously stated, there's now no correspondence between main world
and render world `Entity` identifiers.

Querying for `Entity` in the render world will return the `Entity` id in
the render world: query for `MainEntity` (and use its `id()` method) to
get the corresponding entity in the main world.

This is also a good way to tell the difference between synced and
unsynced entities in the render world, because unsynced entities won't
have a `MainEntity` component.

---------

Co-authored-by: re0312 <re0312@outlook.com>
Co-authored-by: re0312 <45868716+re0312@users.noreply.github.com>
Co-authored-by: Periwink <charlesbour@gmail.com>
Co-authored-by: Anselmo Sampietro <ans.samp@gmail.com>
Co-authored-by: Emerson Coskey <56370779+ecoskey@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Performance A change motivated by improving speed, memory usage or compile times D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Needs-Release-Note Work that should be called out in the blog due to impact S-Needs-SME Decision or review from an SME is required X-Controversial There is active debate or serious implications around merging this PR
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Render world blocking certain ecs developments