-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Retain Rendering World #14449
Conversation
Added @Trashtalk217 as coauthor ,because this pr was highly inspired by his work #14252 |
I like the move to a single 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. |
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 ( |
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,
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 ).
That's my bad ,Camera2D is synced but not mentioned in the pr description.
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 | | ||
/// |--------------------------------------------------------------------| | ||
/// ``` | ||
/// |
There was a problem hiding this comment.
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.
Thanks for the reply! It's all clear now. I left a comment about specifying it in the documentation as well. |
There was a problem hiding this 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
/// | ||
/// 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. |
There was a problem hiding this comment.
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
/// | ||
/// ``` | ||
/// | ||
/// 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` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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` |
There was a problem hiding this comment.
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.
Co-authored-by: Trashtalk217 <trashtalk217@gmail.com> Co-authored-by: Anselmo Sampietro <ans.samp@gmail.com>
There was a problem hiding this 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())) |
There was a problem hiding this comment.
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.
I think I like the WorldQuery impl 🤔 |
Improve documentation
There was a problem hiding this 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/ |--------------------------------------------------------------------| |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
//are/ |--------------------------------------------------------------------| | |
/// |--------------------------------------------------------------------| |
There was a problem hiding this 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The extraction happens in it's own step, independently from synchronization. | |
/// The extraction happens in its own step, independently from synchronization. |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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!"); |
There was a problem hiding this comment.
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>| { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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?
There was a problem hiding this comment.
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.
One last nit from me: while it doesn't affect any of the in-engine uses of |
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. |
|
- 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>
- 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>
Objective
* as entities
because it needs to despawn all entities each frame to ensure consistent entity IDs between the main world and render world.Solution
Observer
to listen the components that tis entity needs to be synchronized to the render world.entity_sync_system
will be run first, which is responsible for maintaining the entity mapping between the main world and the rendering worldBevy Schedule
Alternative(s)
we could using Resource to manage all rendering entity component data so that we don't have any entities at all.
Advantages
Drawbacks
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
cargo run --release --example many_foxes --features bevy/trace_tracy
cargo run --release --example many_buttons --features bevy/trace_tracy
~30% loss
The majority of the performance loss from the spawn(~3ms) and despawn(~3ms) operations of ephemeral entity.
Next Steps
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 nextextract
.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 eachsynchronized
main entity has aRenderEntity
component that holds an Entity ID pointer to its unique counterpart entity in the render world.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 inCamera2dBundle
,Camera3dBundle
,DirectionalLightBundle
,SpotLightBundle
,PointLightBundle
,SpriteBundle
, andSpriteSheetBundle
.)Once a
synchronized
main entity despawned ,its corresponding render entity will automatically be purged before nextextract
.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