-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Synchronize removed components with the render world #15582
base: main
Are you sure you want to change the base?
Conversation
I'll add and update documentation if we decide this is the solution we want |
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.
Broadly in favour of this, but there's one big exception that would break this system. This is exceedingly rare, but it does need to be documented.
Say that we have two synced and extracted components A
and B
. That are located on the same entity.
| Main World | Render World |
| -----------------------------|----------------------------|
| e1: (A, B, RenderEntity(e2)) | e2: (A, B, MainEntity(e1)) |
If you now remove A
from e1
, this will remove both A
and B
in the render world.
| Main World | Render World |
| -----------------------------|---------------------------|
| e1: (B, RenderEntity(e2)) | e2: (MainEntity(e1)) |
This may not be what the user expects. And while this scenario may be very rare, it should be documented. You could solve this by counting the number of synced components a entity has (and storing it with SyncToRenderWorld), but that might be a step to far.
Also some documentation is still required for the sync_component
plugin.
crates/bevy_render/src/world_sync.rs
Outdated
ec.despawn(); | ||
}; | ||
} | ||
EntityRecord::ComponentRemoved(main_entity) => { | ||
// It's difficult to remove only the relevant component because component ids aren't stable across worlds, | ||
// so we just clear the entire 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.
This is fun and has consequences, but we're gonna figure that out.
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.
After thinking more I think it's what we need to do in any case. Components like SpotLight
aren't extracted directly but generate derived values, e.g. ExtractedPointLight
. The only reason SyncComponentPlugin
works in this case is because we clear everything. Because extract has to work when there's nothing present in the render world the first time it runs, I cannot think of any cases where this would break?
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 deferring on your judgement on this as I don't have much insight in rendering internals. As long as it's correctly documented (and maybe also include I reason why we have to do this, besides it being difficult, because it's strictly not impossible), this should be good.
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.
@IceSentry or another SME should perhaps sanity check this logic, I'm not an expert in the internals either but it makes sense to me :D
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.
Last couple nits, after this it's ready to go.
Co-authored-by: Trashtalk217 <[email protected]>
There are still a few open questions:
Also it looks like some of the examples broke when I merged main, I'll have to debug that later. |
Yes.
Yes. Document this on |
if let Some(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(); |
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 now this is probably fine since we don't really take advantage of the retained render world yet, but that feels like a major footgun for when we start to do that.
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.
With the current extract system it's mostly a (minor) performance footgun. The extract system needs to handle extracting to an empty entity when it first gets created. Unless that's very performance heavy (which it shouldn't be during extract) or you insert/remove components very often it shouldn't be a problem.
When we use the retained world more we can probably come up with something smarter, but that will probably involve reworking extraction as well to do diffs and having a way to track derived state. If we just removed the same component it wouldn't solve the issue, as e.g. PointLight
becomes ExtractedPointLight
.
This at least is a much better stopgap than just saying "you're not allowed to remove render components after spawning".
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 registersSyncToRenderWorld
as a required component forC
, and adds a component hook that will clear all components from the render world entity whenC
is removed. We add this plugin toExtractComponentPlugin
which fixes most instances of the problem. For custom extraction logic we can manually addSyncComponentPlugin
for that component.I haven't done so here, but I propose we rename
WorldSyncPlugin
toSyncWorldPlugin
so we start a naming convention like all theExtract
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 can split these out into a separate PR if needed.I found that using the
RenderEntity
wrapper instead ofEntity
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
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