Implement WorldQuery for MainWorld and RenderWorld components (#15745)

# Objective

#15320 is a particularly painful breaking change, and the new
`RenderEntity` in particular is very noisy, with a lot of `let entity =
entity.id()` spam.

## Solution

Implement `WorldQuery`, `QueryData` and `ReadOnlyQueryData` for
`RenderEntity` and `WorldEntity`.

These work the same as the `Entity` impls from a user-facing
perspective: they simply return an owned (copied) `Entity` identifier.
This dramatically reduces noise and eases migration.

Under the hood, these impls defer to the implementations for `&T` for
everything other than the "call .id() for the user" bit, as they involve
read-only access to component data. Doing it this way (as opposed to
implementing a custom fetch, as tried in the first commit) dramatically
reduces the maintenance risk of complex unsafe code outside of
`bevy_ecs`.

To make this easier (and encourage users to do this themselves!), I've
made `ReadFetch` and `WriteFetch` slightly more public: they're no
longer `doc(hidden)`. This is a good change, since trying to vendor the
logic is much worse than just deferring to the existing tested impls.

## Testing

I've run a handful of rendering examples (breakout, alien_cake_addict,
auto_exposure, fog_volumes, box_shadow) and nothing broke.

## Follow-up

We should lint for the uses of `&RenderEntity` and `&MainEntity` in
queries: this is just less nice for no reason.

---------

Co-authored-by: Trashtalk217 <trashtalk217@gmail.com>
This commit is contained in:
Alice Cecile 2024-10-13 16:58:46 -04:00 committed by GitHub
parent d96a9d15f6
commit a7e9330af9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 291 additions and 87 deletions

View File

@ -27,13 +27,13 @@ pub(super) struct ExtractedStateBuffers {
pub(super) fn extract_buffers( pub(super) fn extract_buffers(
mut commands: Commands, mut commands: Commands,
changed: Extract<Query<(&RenderEntity, &AutoExposure), Changed<AutoExposure>>>, changed: Extract<Query<(RenderEntity, &AutoExposure), Changed<AutoExposure>>>,
mut removed: Extract<RemovedComponents<AutoExposure>>, mut removed: Extract<RemovedComponents<AutoExposure>>,
) { ) {
commands.insert_resource(ExtractedStateBuffers { commands.insert_resource(ExtractedStateBuffers {
changed: changed changed: changed
.iter() .iter()
.map(|(entity, settings)| (entity.id(), settings.clone())) .map(|(entity, settings)| (entity, settings.clone()))
.collect(), .collect(),
removed: removed.read().collect(), removed: removed.read().collect(),
}); });

View File

@ -375,7 +375,7 @@ pub fn extract_core_2d_camera_phases(
mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>, mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
mut opaque_2d_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>, mut opaque_2d_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
mut alpha_mask_2d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>, mut alpha_mask_2d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
cameras_2d: Extract<Query<(&RenderEntity, &Camera), With<Camera2d>>>, cameras_2d: Extract<Query<(RenderEntity, &Camera), With<Camera2d>>>,
mut live_entities: Local<EntityHashSet>, mut live_entities: Local<EntityHashSet>,
) { ) {
live_entities.clear(); live_entities.clear();
@ -384,7 +384,6 @@ pub fn extract_core_2d_camera_phases(
if !camera.is_active { if !camera.is_active {
continue; continue;
} }
let entity = entity.id();
transparent_2d_phases.insert_or_clear(entity); transparent_2d_phases.insert_or_clear(entity);
opaque_2d_phases.insert_or_clear(entity); opaque_2d_phases.insert_or_clear(entity);
alpha_mask_2d_phases.insert_or_clear(entity); alpha_mask_2d_phases.insert_or_clear(entity);

View File

@ -528,17 +528,16 @@ pub fn extract_core_3d_camera_phases(
mut alpha_mask_3d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>, mut alpha_mask_3d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3d>>,
mut transmissive_3d_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>, mut transmissive_3d_phases: ResMut<ViewSortedRenderPhases<Transmissive3d>>,
mut transparent_3d_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>, mut transparent_3d_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
cameras_3d: Extract<Query<(&RenderEntity, &Camera), With<Camera3d>>>, cameras_3d: Extract<Query<(RenderEntity, &Camera), With<Camera3d>>>,
mut live_entities: Local<EntityHashSet>, mut live_entities: Local<EntityHashSet>,
) { ) {
live_entities.clear(); live_entities.clear();
for (render_entity, camera) in &cameras_3d { for (entity, camera) in &cameras_3d {
if !camera.is_active { if !camera.is_active {
continue; continue;
} }
let entity = render_entity.id();
opaque_3d_phases.insert_or_clear(entity); opaque_3d_phases.insert_or_clear(entity);
alpha_mask_3d_phases.insert_or_clear(entity); alpha_mask_3d_phases.insert_or_clear(entity);
transmissive_3d_phases.insert_or_clear(entity); transmissive_3d_phases.insert_or_clear(entity);
@ -563,7 +562,7 @@ pub fn extract_camera_prepass_phase(
cameras_3d: Extract< cameras_3d: Extract<
Query< Query<
( (
&RenderEntity, RenderEntity,
&Camera, &Camera,
Has<DepthPrepass>, Has<DepthPrepass>,
Has<NormalPrepass>, Has<NormalPrepass>,
@ -577,20 +576,13 @@ pub fn extract_camera_prepass_phase(
) { ) {
live_entities.clear(); live_entities.clear();
for ( for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
render_entity, cameras_3d.iter()
camera,
depth_prepass,
normal_prepass,
motion_vector_prepass,
deferred_prepass,
) in cameras_3d.iter()
{ {
if !camera.is_active { if !camera.is_active {
continue; continue;
} }
let entity = render_entity.id();
if depth_prepass || normal_prepass || motion_vector_prepass { if depth_prepass || normal_prepass || motion_vector_prepass {
opaque_3d_prepass_phases.insert_or_clear(entity); opaque_3d_prepass_phases.insert_or_clear(entity);
alpha_mask_3d_prepass_phases.insert_or_clear(entity); alpha_mask_3d_prepass_phases.insert_or_clear(entity);

View File

@ -810,7 +810,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
/// Extracts all [`DepthOfField`] components into the render world. /// Extracts all [`DepthOfField`] components into the render world.
fn extract_depth_of_field_settings( fn extract_depth_of_field_settings(
mut commands: Commands, mut commands: Commands,
mut query: Extract<Query<(&RenderEntity, &DepthOfField, &Projection)>>, mut query: Extract<Query<(RenderEntity, &DepthOfField, &Projection)>>,
) { ) {
if !DEPTH_TEXTURE_SAMPLING_SUPPORTED { if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
info_once!( info_once!(
@ -820,7 +820,6 @@ fn extract_depth_of_field_settings(
} }
for (entity, depth_of_field, projection) in query.iter_mut() { for (entity, depth_of_field, projection) in query.iter_mut() {
let entity = entity.id();
// Depth of field is nonsensical without a perspective projection. // Depth of field is nonsensical without a perspective projection.
let Projection::Perspective(ref perspective_projection) = *projection else { let Projection::Perspective(ref perspective_projection) = *projection else {
continue; continue;

View File

@ -358,7 +358,7 @@ impl SpecializedRenderPipeline for TaaPipeline {
fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) { fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
let mut cameras_3d = main_world.query_filtered::<( let mut cameras_3d = main_world.query_filtered::<(
&RenderEntity, RenderEntity,
&Camera, &Camera,
&Projection, &Projection,
&mut TemporalAntiAliasing, &mut TemporalAntiAliasing,
@ -375,7 +375,7 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld
let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_)); let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_));
if camera.is_active && has_perspective_projection { if camera.is_active && has_perspective_projection {
commands commands
.get_entity(entity.id()) .get_entity(entity)
.expect("Camera entity wasn't synced.") .expect("Camera entity wasn't synced.")
.insert(taa_settings.clone()); .insert(taa_settings.clone());
taa_settings.reset = false; taa_settings.reset = false;

View File

@ -1057,7 +1057,7 @@ unsafe impl QueryData for &Archetype {
/// SAFETY: access is read only /// SAFETY: access is read only
unsafe impl ReadOnlyQueryData for &Archetype {} unsafe impl ReadOnlyQueryData for &Archetype {}
#[doc(hidden)] /// The [`WorldQuery::Fetch`] type for `& T`.
pub struct ReadFetch<'w, T: Component> { pub struct ReadFetch<'w, T: Component> {
components: StorageSwitch< components: StorageSwitch<
T, T,
@ -1415,7 +1415,7 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> {
/// SAFETY: access is read only /// SAFETY: access is read only
unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {} unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {}
#[doc(hidden)] /// The [`WorldQuery::Fetch`] type for `&mut T`.
pub struct WriteFetch<'w, T: Component> { pub struct WriteFetch<'w, T: Component> {
components: StorageSwitch< components: StorageSwitch<
T, T,

View File

@ -526,8 +526,8 @@ pub(crate) fn clusterable_object_order(
/// Extracts clusters from the main world from the render world. /// Extracts clusters from the main world from the render world.
pub fn extract_clusters( pub fn extract_clusters(
mut commands: Commands, mut commands: Commands,
views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>, views: Extract<Query<(RenderEntity, &Clusters, &Camera)>>,
mapper: Extract<Query<&RenderEntity>>, mapper: Extract<Query<RenderEntity>>,
) { ) {
for (entity, clusters, camera) in &views { for (entity, clusters, camera) in &views {
if !camera.is_active { if !camera.is_active {
@ -548,14 +548,14 @@ pub fn extract_clusters(
for clusterable_entity in &cluster_objects.entities { for clusterable_entity in &cluster_objects.entities {
if let Ok(entity) = mapper.get(*clusterable_entity) { if let Ok(entity) = mapper.get(*clusterable_entity) {
data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity( data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
entity.id(), entity,
)); ));
} }
} }
} }
commands commands
.get_entity(entity.id()) .get_entity(entity)
.expect("Clusters entity wasn't synced.") .expect("Clusters entity wasn't synced.")
.insert(( .insert((
ExtractedClusterableObjects { data }, ExtractedClusterableObjects { data },

View File

@ -372,7 +372,7 @@ impl Plugin for LightProbePlugin {
/// Compared to the `ExtractComponentPlugin`, this implementation will create a default instance /// Compared to the `ExtractComponentPlugin`, this implementation will create a default instance
/// if one does not already exist. /// if one does not already exist.
fn gather_environment_map_uniform( fn gather_environment_map_uniform(
view_query: Extract<Query<(&RenderEntity, Option<&EnvironmentMapLight>), With<Camera3d>>>, view_query: Extract<Query<(RenderEntity, Option<&EnvironmentMapLight>), With<Camera3d>>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (view_entity, environment_map_light) in view_query.iter() { for (view_entity, environment_map_light) in view_query.iter() {
@ -386,7 +386,7 @@ fn gather_environment_map_uniform(
EnvironmentMapUniform::default() EnvironmentMapUniform::default()
}; };
commands commands
.get_entity(view_entity.id()) .get_entity(view_entity)
.expect("Environment map light entity wasn't synced.") .expect("Environment map light entity wasn't synced.")
.insert(environment_map_uniform); .insert(environment_map_uniform);
} }
@ -398,7 +398,7 @@ fn gather_light_probes<C>(
image_assets: Res<RenderAssets<GpuImage>>, image_assets: Res<RenderAssets<GpuImage>>,
light_probe_query: Extract<Query<(&GlobalTransform, &C), With<LightProbe>>>, light_probe_query: Extract<Query<(&GlobalTransform, &C), With<LightProbe>>>,
view_query: Extract< view_query: Extract<
Query<(&RenderEntity, &GlobalTransform, &Frustum, Option<&C>), With<Camera3d>>, Query<(RenderEntity, &GlobalTransform, &Frustum, Option<&C>), With<Camera3d>>,
>, >,
mut reflection_probes: Local<Vec<LightProbeInfo<C>>>, mut reflection_probes: Local<Vec<LightProbeInfo<C>>>,
mut view_reflection_probes: Local<Vec<LightProbeInfo<C>>>, mut view_reflection_probes: Local<Vec<LightProbeInfo<C>>>,
@ -437,16 +437,15 @@ fn gather_light_probes<C>(
// Gather up the light probes in the list. // Gather up the light probes in the list.
render_view_light_probes.maybe_gather_light_probes(&view_reflection_probes); render_view_light_probes.maybe_gather_light_probes(&view_reflection_probes);
let entity = view_entity.id();
// Record the per-view light probes. // Record the per-view light probes.
if render_view_light_probes.is_empty() { if render_view_light_probes.is_empty() {
commands commands
.get_entity(entity) .get_entity(view_entity)
.expect("View entity wasn't synced.") .expect("View entity wasn't synced.")
.remove::<RenderViewLightProbes<C>>(); .remove::<RenderViewLightProbes<C>>();
} else { } else {
commands commands
.get_entity(entity) .get_entity(view_entity)
.expect("View entity wasn't synced.") .expect("View entity wasn't synced.")
.insert(render_view_light_probes); .insert(render_view_light_probes);
} }

View File

@ -581,11 +581,10 @@ where
// Extract the render phases for the prepass // Extract the render phases for the prepass
pub fn extract_camera_previous_view_data( pub fn extract_camera_previous_view_data(
mut commands: Commands, mut commands: Commands,
cameras_3d: Extract<Query<(&RenderEntity, &Camera, Option<&PreviousViewData>), With<Camera3d>>>, cameras_3d: Extract<Query<(RenderEntity, &Camera, Option<&PreviousViewData>), With<Camera3d>>>,
) { ) {
for (entity, camera, maybe_previous_view_data) in cameras_3d.iter() { for (entity, camera, maybe_previous_view_data) in cameras_3d.iter() {
if camera.is_active { if camera.is_active {
let entity = entity.id();
let mut entity = commands let mut entity = commands
.get_entity(entity) .get_entity(entity)
.expect("Camera entity wasn't synced."); .expect("Camera entity wasn't synced.");

View File

@ -193,7 +193,7 @@ pub fn extract_lights(
global_point_lights: Extract<Res<GlobalVisibleClusterableObjects>>, global_point_lights: Extract<Res<GlobalVisibleClusterableObjects>>,
point_lights: Extract< point_lights: Extract<
Query<( Query<(
&RenderEntity, RenderEntity,
&PointLight, &PointLight,
&CubemapVisibleEntities, &CubemapVisibleEntities,
&GlobalTransform, &GlobalTransform,
@ -204,7 +204,7 @@ pub fn extract_lights(
>, >,
spot_lights: Extract< spot_lights: Extract<
Query<( Query<(
&RenderEntity, RenderEntity,
&SpotLight, &SpotLight,
&VisibleMeshEntities, &VisibleMeshEntities,
&GlobalTransform, &GlobalTransform,
@ -216,7 +216,7 @@ pub fn extract_lights(
directional_lights: Extract< directional_lights: Extract<
Query< Query<
( (
&RenderEntity, RenderEntity,
&DirectionalLight, &DirectionalLight,
&CascadesVisibleEntities, &CascadesVisibleEntities,
&Cascades, &Cascades,
@ -230,7 +230,7 @@ pub fn extract_lights(
Without<SpotLight>, Without<SpotLight>,
>, >,
>, >,
mapper: Extract<Query<&RenderEntity>>, mapper: Extract<Query<RenderEntity>>,
mut previous_point_lights_len: Local<usize>, mut previous_point_lights_len: Local<usize>,
mut previous_spot_lights_len: Local<usize>, mut previous_spot_lights_len: Local<usize>,
) { ) {
@ -298,7 +298,7 @@ pub fn extract_lights(
volumetric: volumetric_light.is_some(), volumetric: volumetric_light.is_some(),
}; };
point_lights_values.push(( point_lights_values.push((
render_entity.id(), render_entity,
( (
extracted_point_light, extracted_point_light,
render_cubemap_visible_entities, render_cubemap_visible_entities,
@ -331,7 +331,7 @@ pub fn extract_lights(
2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32; 2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32;
spot_lights_values.push(( spot_lights_values.push((
render_entity.id(), render_entity,
( (
ExtractedPointLight { ExtractedPointLight {
color: spot_light.color.into(), color: spot_light.color.into(),
@ -388,14 +388,14 @@ pub fn extract_lights(
let mut cascade_visible_entities = EntityHashMap::default(); let mut cascade_visible_entities = EntityHashMap::default();
for (e, v) in cascades.cascades.iter() { for (e, v) in cascades.cascades.iter() {
if let Ok(entity) = mapper.get(*e) { if let Ok(entity) = mapper.get(*e) {
extracted_cascades.insert(entity.id(), v.clone()); extracted_cascades.insert(entity, v.clone());
} else { } else {
break; break;
} }
} }
for (e, v) in frusta.frusta.iter() { for (e, v) in frusta.frusta.iter() {
if let Ok(entity) = mapper.get(*e) { if let Ok(entity) = mapper.get(*e) {
extracted_frusta.insert(entity.id(), v.clone()); extracted_frusta.insert(entity, v.clone());
} else { } else {
break; break;
} }
@ -403,7 +403,7 @@ pub fn extract_lights(
for (e, v) in visible_entities.entities.iter() { for (e, v) in visible_entities.entities.iter() {
if let Ok(entity) = mapper.get(*e) { if let Ok(entity) = mapper.get(*e) {
cascade_visible_entities.insert( cascade_visible_entities.insert(
entity.id(), entity,
v.iter() v.iter()
.map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v)) .map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v))
.collect(), .collect(),
@ -414,7 +414,7 @@ pub fn extract_lights(
} }
commands commands
.get_entity(entity.id()) .get_entity(entity)
.expect("Light entity wasn't synced.") .expect("Light entity wasn't synced.")
.insert(( .insert((
ExtractedDirectionalLight { ExtractedDirectionalLight {
@ -442,7 +442,7 @@ pub fn extract_lights(
fn create_render_visible_mesh_entities( fn create_render_visible_mesh_entities(
commands: &mut Commands, commands: &mut Commands,
mapper: &Extract<Query<&RenderEntity>>, mapper: &Extract<Query<RenderEntity>>,
visible_entities: &VisibleMeshEntities, visible_entities: &VisibleMeshEntities,
) -> RenderVisibleMeshEntities { ) -> RenderVisibleMeshEntities {
RenderVisibleMeshEntities { RenderVisibleMeshEntities {
@ -451,7 +451,6 @@ fn create_render_visible_mesh_entities(
.map(|e| { .map(|e| {
let render_entity = mapper let render_entity = mapper
.get(*e) .get(*e)
.map(RenderEntity::id)
.unwrap_or_else(|_| commands.spawn(TemporaryRenderEntity).id()); .unwrap_or_else(|_| commands.spawn(TemporaryRenderEntity).id());
(render_entity, MainEntity::from(*e)) (render_entity, MainEntity::from(*e))
}) })

View File

@ -522,7 +522,7 @@ fn extract_ssao_settings(
mut commands: Commands, mut commands: Commands,
cameras: Extract< cameras: Extract<
Query< Query<
(&RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa), (RenderEntity, &Camera, &ScreenSpaceAmbientOcclusion, &Msaa),
(With<Camera3d>, With<DepthPrepass>, With<NormalPrepass>), (With<Camera3d>, With<DepthPrepass>, With<NormalPrepass>),
>, >,
>, >,
@ -537,7 +537,7 @@ fn extract_ssao_settings(
} }
if camera.is_active { if camera.is_active {
commands commands
.get_entity(entity.id()) .get_entity(entity)
.expect("SSAO entity wasn't synced.") .expect("SSAO entity wasn't synced.")
.insert(ssao_settings.clone()); .insert(ssao_settings.clone());
} }

View File

@ -271,9 +271,9 @@ impl FromWorld for VolumetricFogPipeline {
/// from the main world to the render world. /// from the main world to the render world.
pub fn extract_volumetric_fog( pub fn extract_volumetric_fog(
mut commands: Commands, mut commands: Commands,
view_targets: Extract<Query<(&RenderEntity, &VolumetricFog)>>, view_targets: Extract<Query<(RenderEntity, &VolumetricFog)>>,
fog_volumes: Extract<Query<(&RenderEntity, &FogVolume, &GlobalTransform)>>, fog_volumes: Extract<Query<(RenderEntity, &FogVolume, &GlobalTransform)>>,
volumetric_lights: Extract<Query<(&RenderEntity, &VolumetricLight)>>, volumetric_lights: Extract<Query<(RenderEntity, &VolumetricLight)>>,
) { ) {
if volumetric_lights.is_empty() { if volumetric_lights.is_empty() {
return; return;
@ -281,14 +281,14 @@ pub fn extract_volumetric_fog(
for (entity, volumetric_fog) in view_targets.iter() { for (entity, volumetric_fog) in view_targets.iter() {
commands commands
.get_entity(entity.id()) .get_entity(entity)
.expect("Volumetric fog entity wasn't synced.") .expect("Volumetric fog entity wasn't synced.")
.insert(*volumetric_fog); .insert(*volumetric_fog);
} }
for (entity, fog_volume, fog_transform) in fog_volumes.iter() { for (entity, fog_volume, fog_transform) in fog_volumes.iter() {
commands commands
.get_entity(entity.id()) .get_entity(entity)
.expect("Fog volume entity wasn't synced.") .expect("Fog volume entity wasn't synced.")
.insert((*fog_volume).clone()) .insert((*fog_volume).clone())
.insert(*fog_transform); .insert(*fog_transform);
@ -296,7 +296,7 @@ pub fn extract_volumetric_fog(
for (entity, volumetric_light) in volumetric_lights.iter() { for (entity, volumetric_light) in volumetric_lights.iter() {
commands commands
.get_entity(entity.id()) .get_entity(entity)
.expect("Volumetric light entity wasn't synced.") .expect("Volumetric light entity wasn't synced.")
.insert(*volumetric_light); .insert(*volumetric_light);
} }

View File

@ -1019,7 +1019,7 @@ pub fn extract_cameras(
mut commands: Commands, mut commands: Commands,
query: Extract< query: Extract<
Query<( Query<(
&RenderEntity, RenderEntity,
&Camera, &Camera,
&CameraRenderGraph, &CameraRenderGraph,
&GlobalTransform, &GlobalTransform,
@ -1096,7 +1096,7 @@ pub fn extract_cameras(
}) })
.collect(), .collect(),
}; };
let mut commands = commands.entity(render_entity.id()); let mut commands = commands.entity(render_entity);
commands.insert(( commands.insert((
ExtractedCamera { ExtractedCamera {
target: camera.target.normalize(primary_window), target: camera.target.normalize(primary_window),

View File

@ -199,12 +199,12 @@ impl<C: ExtractComponent> Plugin for ExtractComponentPlugin<C> {
fn extract_components<C: ExtractComponent>( fn extract_components<C: ExtractComponent>(
mut commands: Commands, mut commands: Commands,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
query: Extract<Query<(&RenderEntity, C::QueryData), C::QueryFilter>>, query: Extract<Query<(RenderEntity, C::QueryData), C::QueryFilter>>,
) { ) {
let mut values = Vec::with_capacity(*previous_len); let mut values = Vec::with_capacity(*previous_len);
for (entity, query_item) in &query { for (entity, query_item) in &query {
if let Some(component) = C::extract_component(query_item) { if let Some(component) = C::extract_component(query_item) {
values.push((entity.id(), component)); values.push((entity, component));
} }
} }
*previous_len = values.len(); *previous_len = values.len();
@ -215,13 +215,13 @@ fn extract_components<C: ExtractComponent>(
fn extract_visible_components<C: ExtractComponent>( fn extract_visible_components<C: ExtractComponent>(
mut commands: Commands, mut commands: Commands,
mut previous_len: Local<usize>, mut previous_len: Local<usize>,
query: Extract<Query<(&RenderEntity, &ViewVisibility, C::QueryData), C::QueryFilter>>, query: Extract<Query<(RenderEntity, &ViewVisibility, C::QueryData), C::QueryFilter>>,
) { ) {
let mut values = Vec::with_capacity(*previous_len); let mut values = Vec::with_capacity(*previous_len);
for (entity, view_visibility, query_item) in &query { for (entity, view_visibility, query_item) in &query {
if view_visibility.get() { if view_visibility.get() {
if let Some(component) = C::extract_component(query_item) { if let Some(component) = C::extract_component(query_item) {
values.push((entity.id(), component)); values.push((entity, component));
} }
} }
} }

View File

@ -34,9 +34,9 @@ use core::ops::{Deref, DerefMut};
/// # #[derive(Component)] /// # #[derive(Component)]
/// // Do make sure to sync the cloud entities before extracting them. /// // Do make sure to sync the cloud entities before extracting them.
/// # struct Cloud; /// # struct Cloud;
/// fn extract_clouds(mut commands: Commands, clouds: Extract<Query<&RenderEntity, With<Cloud>>>) { /// fn extract_clouds(mut commands: Commands, clouds: Extract<Query<RenderEntity, With<Cloud>>>) {
/// for cloud in &clouds { /// for cloud in &clouds {
/// commands.entity(cloud.id()).insert(Cloud); /// commands.entity(cloud).insert(Cloud);
/// } /// }
/// } /// }
/// ``` /// ```

View File

@ -30,9 +30,13 @@ use bevy_utils::hashbrown;
/// between the main world and the render world. /// 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. /// It does so by spawning and despawning entities in the render world, to match spawned and despawned entities in the main world.
/// The link between synced entities is maintained by the [`RenderEntity`] and [`MainEntity`] components. /// The link between synced entities is maintained by the [`RenderEntity`] and [`MainEntity`] components.
///
/// The [`RenderEntity`] contains the corresponding render world entity of a main world entity, while [`MainEntity`] contains /// The [`RenderEntity`] contains the corresponding render world entity of a main world entity, while [`MainEntity`] contains
/// the corresponding main world entity of a render world entity. /// the corresponding main world entity of a render world entity.
/// The entities can be accessed by calling `.id()` on either component. /// For convenience, [`QueryData`](bevy_ecs::query::QueryData) implementations are provided for both components:
/// adding [`MainEntity`] to a query (without a `&`) will return the corresponding main world [`Entity`],
/// and adding [`RenderEntity`] will return the corresponding render world [`Entity`].
/// If you have access to the component itself, the underlying entities can be accessed by calling `.id()`.
/// ///
/// Synchronization is necessary preparation for extraction ([`ExtractSchedule`](crate::ExtractSchedule)), which copies over component data from the main /// Synchronization is necessary preparation for extraction ([`ExtractSchedule`](crate::ExtractSchedule)), which copies over component data from the main
/// to the render world for these entities. /// to the render world for these entities.
@ -243,6 +247,220 @@ pub(crate) fn despawn_temporary_render_entities(
} }
} }
/// This module exists to keep the complex unsafe code out of the main module.
///
/// The implementations for both [`MainEntity`] and [`RenderEntity`] should stay in sync,
/// and are based off of the `&T` implementation in `bevy_ecs`.
mod render_entities_world_query_impls {
use super::{MainEntity, RenderEntity};
use bevy_ecs::{
archetype::Archetype,
component::{ComponentId, Components, Tick},
entity::Entity,
query::{FilteredAccess, QueryData, ReadOnlyQueryData, WorldQuery},
storage::{Table, TableRow},
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
/// SAFETY: defers completely to `&RenderEntity` implementation,
/// and then only modifies the output safely.
unsafe impl WorldQuery for RenderEntity {
type Item<'w> = Entity;
type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>;
type State = <&'static RenderEntity as WorldQuery>::State;
fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity {
item
}
fn shrink_fetch<'wlong: 'wshort, 'wshort>(
fetch: Self::Fetch<'wlong>,
) -> Self::Fetch<'wshort> {
fetch
}
#[inline]
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
component_id: &ComponentId,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
unsafe {
<&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
}
}
const IS_DENSE: bool = <&'static RenderEntity as WorldQuery>::IS_DENSE;
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
component_id: &ComponentId,
archetype: &'w Archetype,
table: &'w Table,
) {
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
unsafe {
<&RenderEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
}
}
#[inline]
unsafe fn set_table<'w>(
fetch: &mut Self::Fetch<'w>,
&component_id: &ComponentId,
table: &'w Table,
) {
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
unsafe { <&RenderEntity as WorldQuery>::set_table(fetch, &component_id, table) }
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`.
let component =
unsafe { <&RenderEntity as WorldQuery>::fetch(fetch, entity, table_row) };
component.id()
}
fn update_component_access(
&component_id: &ComponentId,
access: &mut FilteredAccess<ComponentId>,
) {
<&RenderEntity as WorldQuery>::update_component_access(&component_id, access);
}
fn init_state(world: &mut World) -> ComponentId {
<&RenderEntity as WorldQuery>::init_state(world)
}
fn get_state(components: &Components) -> Option<Self::State> {
<&RenderEntity as WorldQuery>::get_state(components)
}
fn matches_component_set(
&state: &ComponentId,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
<&RenderEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
}
}
// SAFETY: Component access of Self::ReadOnly is a subset of Self.
// Self::ReadOnly matches exactly the same archetypes/tables as Self.
unsafe impl QueryData for RenderEntity {
type ReadOnly = RenderEntity;
}
// SAFETY: the underlying `Entity` is copied, and no mutable access is provided.
unsafe impl ReadOnlyQueryData for RenderEntity {}
/// SAFETY: defers completely to `&RenderEntity` implementation,
/// and then only modifies the output safely.
unsafe impl WorldQuery for MainEntity {
type Item<'w> = Entity;
type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>;
type State = <&'static MainEntity as WorldQuery>::State;
fn shrink<'wlong: 'wshort, 'wshort>(item: Entity) -> Entity {
item
}
fn shrink_fetch<'wlong: 'wshort, 'wshort>(
fetch: Self::Fetch<'wlong>,
) -> Self::Fetch<'wshort> {
fetch
}
#[inline]
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
component_id: &ComponentId,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
// SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
unsafe {
<&MainEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run)
}
}
const IS_DENSE: bool = <&'static MainEntity as WorldQuery>::IS_DENSE;
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
component_id: &ComponentId,
archetype: &'w Archetype,
table: &'w Table,
) {
// SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
unsafe {
<&MainEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table);
}
}
#[inline]
unsafe fn set_table<'w>(
fetch: &mut Self::Fetch<'w>,
&component_id: &ComponentId,
table: &'w Table,
) {
// SAFETY: defers to the `&T` implementation, with T set to `MainEntity`.
unsafe { <&MainEntity 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: defers to the `&T` implementation, with T set to `MainEntity`.
let component = unsafe { <&MainEntity as WorldQuery>::fetch(fetch, entity, table_row) };
component.id()
}
fn update_component_access(
&component_id: &ComponentId,
access: &mut FilteredAccess<ComponentId>,
) {
<&MainEntity as WorldQuery>::update_component_access(&component_id, access);
}
fn init_state(world: &mut World) -> ComponentId {
<&MainEntity as WorldQuery>::init_state(world)
}
fn get_state(components: &Components) -> Option<Self::State> {
<&MainEntity as WorldQuery>::get_state(components)
}
fn matches_component_set(
&state: &ComponentId,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
<&MainEntity as WorldQuery>::matches_component_set(&state, set_contains_id)
}
}
// SAFETY: Component access of Self::ReadOnly is a subset of Self.
// Self::ReadOnly matches exactly the same archetypes/tables as Self.
unsafe impl QueryData for MainEntity {
type ReadOnly = MainEntity;
}
// SAFETY: the underlying `Entity` is copied, and no mutable access is provided.
unsafe impl ReadOnlyQueryData for MainEntity {}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bevy_ecs::{ use bevy_ecs::{

View File

@ -374,7 +374,7 @@ pub fn extract_sprites(
sprite_query: Extract< sprite_query: Extract<
Query<( Query<(
Entity, Entity,
&RenderEntity, RenderEntity,
&ViewVisibility, &ViewVisibility,
&Sprite, &Sprite,
&GlobalTransform, &GlobalTransform,
@ -422,7 +422,7 @@ pub fn extract_sprites(
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
extracted_sprites.sprites.insert( extracted_sprites.sprites.insert(
(entity.id(), original_entity.into()), (entity, original_entity.into()),
ExtractedSprite { ExtractedSprite {
color: sprite.color.into(), color: sprite.color.into(),
transform: *transform, transform: *transform,

View File

@ -247,7 +247,7 @@ pub fn extract_shadows(
Option<&TargetCamera>, Option<&TargetCamera>,
)>, )>,
>, >,
mapping: Extract<Query<&RenderEntity>>, mapping: Extract<Query<RenderEntity>>,
) { ) {
for (entity, uinode, transform, view_visibility, box_shadow, clip, camera) in &box_shadow_query for (entity, uinode, transform, view_visibility, box_shadow, clip, camera) in &box_shadow_query
{ {
@ -256,7 +256,7 @@ pub fn extract_shadows(
continue; continue;
}; };
let Ok(&camera_entity) = mapping.get(camera_entity) else { let Ok(camera_entity) = mapping.get(camera_entity) else {
continue; continue;
}; };
@ -266,7 +266,7 @@ pub fn extract_shadows(
} }
let ui_logical_viewport_size = camera_query let ui_logical_viewport_size = camera_query
.get(camera_entity.id()) .get(camera_entity)
.ok() .ok()
.and_then(|(_, c)| c.logical_viewport_size()) .and_then(|(_, c)| c.logical_viewport_size())
.unwrap_or(Vec2::ZERO) .unwrap_or(Vec2::ZERO)
@ -321,7 +321,7 @@ pub fn extract_shadows(
max: shadow_size + 6. * blur_radius, max: shadow_size + 6. * blur_radius,
}, },
clip: clip.map(|clip| clip.clip), clip: clip.map(|clip| clip.clip),
camera_entity: camera_entity.id(), camera_entity,
radius, radius,
blur_radius, blur_radius,
size: shadow_size, size: shadow_size,

View File

@ -235,7 +235,7 @@ pub fn extract_uinode_background_colors(
&BackgroundColor, &BackgroundColor,
)>, )>,
>, >,
mapping: Extract<Query<&RenderEntity>>, mapping: Extract<Query<RenderEntity>>,
) { ) {
for (entity, uinode, transform, view_visibility, clip, camera, background_color) in for (entity, uinode, transform, view_visibility, clip, camera, background_color) in
&uinode_query &uinode_query
@ -245,7 +245,7 @@ pub fn extract_uinode_background_colors(
continue; continue;
}; };
let Ok(&render_camera_entity) = mapping.get(camera_entity) else { let Ok(render_camera_entity) = mapping.get(camera_entity) else {
continue; continue;
}; };
@ -265,7 +265,7 @@ pub fn extract_uinode_background_colors(
}, },
clip: clip.map(|clip| clip.clip), clip: clip.map(|clip| clip.clip),
image: AssetId::default(), image: AssetId::default(),
camera_entity: render_camera_entity.id(), camera_entity: render_camera_entity,
item: ExtractedUiItem::Node { item: ExtractedUiItem::Node {
atlas_scaling: None, atlas_scaling: None,
transform: transform.compute_matrix(), transform: transform.compute_matrix(),
@ -302,7 +302,7 @@ pub fn extract_uinode_images(
Without<ImageScaleMode>, Without<ImageScaleMode>,
>, >,
>, >,
mapping: Extract<Query<&RenderEntity>>, mapping: Extract<Query<RenderEntity>>,
) { ) {
for (entity, uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query { for (entity, uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query {
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
@ -357,7 +357,7 @@ pub fn extract_uinode_images(
rect, rect,
clip: clip.map(|clip| clip.clip), clip: clip.map(|clip| clip.clip),
image: image.texture.id(), image: image.texture.id(),
camera_entity: render_camera_entity.id(), camera_entity: render_camera_entity,
item: ExtractedUiItem::Node { item: ExtractedUiItem::Node {
atlas_scaling, atlas_scaling,
transform: transform.compute_matrix(), transform: transform.compute_matrix(),
@ -388,7 +388,7 @@ pub fn extract_uinode_borders(
AnyOf<(&BorderColor, &Outline)>, AnyOf<(&BorderColor, &Outline)>,
)>, )>,
>, >,
mapping: Extract<Query<&RenderEntity>>, mapping: Extract<Query<RenderEntity>>,
) { ) {
let image = AssetId::<Image>::default(); let image = AssetId::<Image>::default();
@ -409,7 +409,7 @@ pub fn extract_uinode_borders(
continue; continue;
}; };
let Ok(&render_camera_entity) = mapping.get(camera_entity) else { let Ok(render_camera_entity) = mapping.get(camera_entity) else {
continue; continue;
}; };
@ -435,7 +435,7 @@ pub fn extract_uinode_borders(
}, },
image, image,
clip: maybe_clip.map(|clip| clip.clip), clip: maybe_clip.map(|clip| clip.clip),
camera_entity: render_camera_entity.id(), camera_entity: render_camera_entity,
item: ExtractedUiItem::Node { item: ExtractedUiItem::Node {
atlas_scaling: None, atlas_scaling: None,
transform: global_transform.compute_matrix(), transform: global_transform.compute_matrix(),
@ -464,7 +464,7 @@ pub fn extract_uinode_borders(
}, },
image, image,
clip: maybe_clip.map(|clip| clip.clip), clip: maybe_clip.map(|clip| clip.clip),
camera_entity: render_camera_entity.id(), camera_entity: render_camera_entity,
item: ExtractedUiItem::Node { item: ExtractedUiItem::Node {
transform: global_transform.compute_matrix(), transform: global_transform.compute_matrix(),
atlas_scaling: None, atlas_scaling: None,
@ -503,7 +503,7 @@ pub fn extract_default_ui_camera_view(
query: Extract< query: Extract<
Query< Query<
( (
&RenderEntity, RenderEntity,
&Camera, &Camera,
Option<&UiAntiAlias>, Option<&UiAntiAlias>,
Option<&UiBoxShadowSamples>, Option<&UiBoxShadowSamples>,
@ -534,7 +534,6 @@ pub fn extract_default_ui_camera_view(
camera.physical_viewport_rect(), camera.physical_viewport_rect(),
camera.physical_viewport_size(), camera.physical_viewport_size(),
) { ) {
let entity = entity.id();
// use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection // use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection
let projection_matrix = Mat4::orthographic_rh( let projection_matrix = Mat4::orthographic_rh(
0.0, 0.0,

View File

@ -376,7 +376,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
Without<BackgroundColor>, Without<BackgroundColor>,
>, >,
>, >,
render_entity_lookup: Extract<Query<&RenderEntity>>, render_entity_lookup: Extract<Query<RenderEntity>>,
) { ) {
// If there is only one camera, we use it as default // If there is only one camera, we use it as default
let default_single_camera = default_ui_camera.get(); let default_single_camera = default_ui_camera.get();
@ -386,7 +386,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
continue; continue;
}; };
let Ok(&camera_entity) = render_entity_lookup.get(camera_entity) else { let Ok(camera_entity) = render_entity_lookup.get(camera_entity) else {
continue; continue;
}; };
@ -419,7 +419,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
}, },
border, border,
clip: clip.map(|clip| clip.clip), clip: clip.map(|clip| clip.clip),
camera_entity: camera_entity.id(), camera_entity,
main_entity: entity.into(), main_entity: entity.into(),
}, },
); );

View File

@ -261,7 +261,7 @@ pub fn extract_ui_texture_slices(
Option<&TextureAtlas>, Option<&TextureAtlas>,
)>, )>,
>, >,
mapping: Extract<Query<&RenderEntity>>, mapping: Extract<Query<RenderEntity>>,
) { ) {
for ( for (
entity, entity,
@ -280,7 +280,7 @@ pub fn extract_ui_texture_slices(
continue; continue;
}; };
let Ok(&camera_entity) = mapping.get(camera_entity) else { let Ok(camera_entity) = mapping.get(camera_entity) else {
continue; continue;
}; };
@ -319,7 +319,7 @@ pub fn extract_ui_texture_slices(
}, },
clip: clip.map(|clip| clip.clip), clip: clip.map(|clip| clip.clip),
image: image.texture.id(), image: image.texture.id(),
camera_entity: camera_entity.id(), camera_entity,
image_scale_mode: image_scale_mode.clone(), image_scale_mode: image_scale_mode.clone(),
atlas_rect, atlas_rect,
flip_x: image.flip_x, flip_x: image.flip_x,