Skip to content

Commit 7b2cf98

Browse files
DJMcNabcart
andcommitted
Make RenderStage::Extract run on the render world (#4402)
# Objective - Currently, the `Extract` `RenderStage` is executed on the main world, with the render world available as a resource. - However, when needing access to resources in the render world (e.g. to mutate them), the only way to do so was to get exclusive access to the whole `RenderWorld` resource. - This meant that effectively only one extract which wrote to resources could run at a time. - We didn't previously make `Extract`ing writing to the world a non-happy path, even though we want to discourage that. ## Solution - Move the extract stage to run on the render world. - Add the main world as a `MainWorld` resource. - Add an `Extract` `SystemParam` as a convenience to access a (read only) `SystemParam` in the main world during `Extract`. ## Future work It should be possible to avoid needing to use `get_or_spawn` for the render commands, since now the `Commands`' `Entities` matches up with the world being executed on. We need to determine how this interacts with #3519 It's theoretically possible to remove the need for the `value` method on `Extract`. However, that requires slightly changing the `SystemParam` interface, which would make it more complicated. That would probably mess up the `SystemState` api too. ## Todo I still need to add doc comments to `Extract`. --- ## Changelog ### Changed - The `Extract` `RenderStage` now runs on the render world (instead of the main world as before). You must use the `Extract` `SystemParam` to access the main world during the extract phase. Resources on the render world can now be accessed using `ResMut` during extract. ### Removed - `Commands::spawn_and_forget`. Use `Commands::get_or_spawn(e).insert_bundle(bundle)` instead ## Migration Guide The `Extract` `RenderStage` now runs on the render world (instead of the main world as before). You must use the `Extract` `SystemParam` to access the main world during the extract phase. `Extract` takes a single type parameter, which is any system parameter (such as `Res`, `Query` etc.). It will extract this from the main world, and returns the result of this extraction when `value` is called on it. For example, if previously your extract system looked like: ```rust fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) { for cloud in clouds.iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` the new version would be: ```rust fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) { for cloud in clouds.value().iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` The diff is: ```diff --- a/src/clouds.rs +++ b/src/clouds.rs @@ -1,5 +1,5 @@ -fn extract_clouds(mut commands: Commands, clouds: Query<Entity, With<Cloud>>) { - for cloud in clouds.iter() { +fn extract_clouds(mut commands: Commands, mut clouds: Extract<Query<Entity, With<Cloud>>>) { + for cloud in clouds.value().iter() { commands.get_or_spawn(cloud).insert(Cloud); } } ``` You can now also access resources from the render world using the normal system parameters during `Extract`: ```rust fn extract_assets(mut render_assets: ResMut<MyAssets>, source_assets: Extract<Res<MyAssets>>) { *render_assets = source_assets.clone(); } ``` Please note that all existing extract systems need to be updated to match this new style; even if they currently compile they will not run as expected. A warning will be emitted on a best-effort basis if this is not met. Co-authored-by: Carter Anderson <[email protected]>
1 parent e6faf99 commit 7b2cf98

File tree

22 files changed

+377
-194
lines changed

22 files changed

+377
-194
lines changed

crates/bevy_core_pipeline/src/core_2d/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use bevy_render::{
2525
DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase,
2626
},
2727
render_resource::CachedRenderPipelineId,
28-
RenderApp, RenderStage,
28+
Extract, RenderApp, RenderStage,
2929
};
3030
use bevy_utils::FloatOrd;
3131
use std::ops::Range;
@@ -123,7 +123,7 @@ impl BatchedPhaseItem for Transparent2d {
123123

124124
pub fn extract_core_2d_camera_phases(
125125
mut commands: Commands,
126-
cameras_2d: Query<(Entity, &Camera), With<Camera2d>>,
126+
cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
127127
) {
128128
for (entity, camera) in cameras_2d.iter() {
129129
if camera.is_active {

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use bevy_render::{
3434
renderer::RenderDevice,
3535
texture::TextureCache,
3636
view::ViewDepthTexture,
37-
RenderApp, RenderStage,
37+
Extract, RenderApp, RenderStage,
3838
};
3939
use bevy_utils::{FloatOrd, HashMap};
4040

@@ -208,7 +208,7 @@ impl CachedRenderPipelinePhaseItem for Transparent3d {
208208

209209
pub fn extract_core_3d_camera_phases(
210210
mut commands: Commands,
211-
cameras_3d: Query<(Entity, &Camera), With<Camera3d>>,
211+
cameras_3d: Extract<Query<(Entity, &Camera), With<Camera3d>>>,
212212
) {
213213
for (entity, camera) in cameras_3d.iter() {
214214
if camera.is_active {

crates/bevy_ecs/src/schedule/stage.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use crate::{
1212
},
1313
world::{World, WorldId},
1414
};
15-
use bevy_utils::{tracing::info, HashMap, HashSet};
15+
use bevy_utils::{
16+
tracing::{info, warn},
17+
HashMap, HashSet,
18+
};
1619
use downcast_rs::{impl_downcast, Downcast};
1720
use fixedbitset::FixedBitSet;
1821
use std::fmt::Debug;
@@ -88,6 +91,7 @@ pub struct SystemStage {
8891
last_tick_check: u32,
8992
/// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied.
9093
apply_buffers: bool,
94+
must_read_resource: Option<ComponentId>,
9195
}
9296

9397
impl SystemStage {
@@ -110,6 +114,7 @@ impl SystemStage {
110114
uninitialized_at_end: vec![],
111115
last_tick_check: Default::default(),
112116
apply_buffers: true,
117+
must_read_resource: None,
113118
}
114119
}
115120

@@ -139,6 +144,10 @@ impl SystemStage {
139144
self.executor = executor;
140145
}
141146

147+
pub fn set_must_read_resource(&mut self, resource_id: ComponentId) {
148+
self.must_read_resource = Some(resource_id);
149+
}
150+
142151
#[must_use]
143152
pub fn with_system<Params>(mut self, system: impl IntoSystemDescriptor<Params>) -> Self {
144153
self.add_system(system);
@@ -563,6 +572,20 @@ impl SystemStage {
563572
}
564573
}
565574

575+
fn check_uses_resource(&self, resource_id: ComponentId, world: &World) {
576+
debug_assert!(!self.systems_modified);
577+
for system in &self.parallel {
578+
let access = system.component_access().unwrap();
579+
if !access.has_read(resource_id) {
580+
let component_name = world.components().get_info(resource_id).unwrap().name();
581+
warn!(
582+
"System {} doesn't access resource {component_name}, despite being required to",
583+
system.name()
584+
);
585+
}
586+
}
587+
}
588+
566589
/// All system and component change ticks are scanned once the world counter has incremented
567590
/// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD)
568591
/// times since the previous `check_tick` scan.
@@ -782,6 +805,9 @@ impl Stage for SystemStage {
782805
if world.contains_resource::<ReportExecutionOrderAmbiguities>() {
783806
self.report_ambiguities(world);
784807
}
808+
if let Some(resource_id) = self.must_read_resource {
809+
self.check_uses_resource(resource_id, world);
810+
}
785811
} else if self.executor_modified {
786812
self.executor.rebuild_cached_data(&self.parallel);
787813
self.executor_modified = false;

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,6 @@ impl<'w, 's> Commands<'w, 's> {
145145
}
146146
}
147147

148-
/// Spawns a [`Bundle`] without pre-allocating an [`Entity`]. The [`Entity`] will be allocated
149-
/// when this [`Command`] is applied.
150-
pub fn spawn_and_forget(&mut self, bundle: impl Bundle) {
151-
self.queue.push(Spawn { bundle });
152-
}
153-
154148
/// Creates a new entity with the components contained in `bundle`.
155149
///
156150
/// This returns an [`EntityCommands`] builder, which enables inserting more components and

crates/bevy_pbr/src/material.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use bevy_render::{
3434
renderer::RenderDevice,
3535
texture::FallbackImage,
3636
view::{ExtractedView, Msaa, VisibleEntities},
37-
RenderApp, RenderStage,
37+
Extract, RenderApp, RenderStage,
3838
};
3939
use bevy_utils::{tracing::error, HashMap, HashSet};
4040
use std::hash::Hash;
@@ -455,15 +455,15 @@ pub type RenderMaterials<T> = HashMap<Handle<T>, PreparedMaterial<T>>;
455455
/// into the "render world".
456456
fn extract_materials<M: Material>(
457457
mut commands: Commands,
458-
mut events: EventReader<AssetEvent<M>>,
459-
assets: Res<Assets<M>>,
458+
mut events: Extract<EventReader<AssetEvent<M>>>,
459+
assets: Extract<Res<Assets<M>>>,
460460
) {
461461
let mut changed_assets = HashSet::default();
462462
let mut removed = Vec::new();
463463
for event in events.iter() {
464464
match event {
465465
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
466-
changed_assets.insert(handle);
466+
changed_assets.insert(handle.clone_weak());
467467
}
468468
AssetEvent::Removed { handle } => {
469469
changed_assets.remove(handle);
@@ -474,8 +474,8 @@ fn extract_materials<M: Material>(
474474

475475
let mut extracted_assets = Vec::new();
476476
for handle in changed_assets.drain() {
477-
if let Some(asset) = assets.get(handle) {
478-
extracted_assets.push((handle.clone_weak(), asset.clone()));
477+
if let Some(asset) = assets.get(&handle) {
478+
extracted_assets.push((handle, asset.clone()));
479479
}
480480
}
481481

crates/bevy_pbr/src/render/light.rs

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use bevy_render::{
2828
view::{
2929
ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility, VisibleEntities,
3030
},
31+
Extract,
3132
};
3233
use bevy_transform::components::GlobalTransform;
3334
use bevy_utils::FloatOrd;
@@ -386,7 +387,10 @@ pub struct ExtractedClustersPointLights {
386387
data: Vec<VisiblePointLights>,
387388
}
388389

389-
pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters), With<Camera>>) {
390+
pub fn extract_clusters(
391+
mut commands: Commands,
392+
views: Extract<Query<(Entity, &Clusters), With<Camera>>>,
393+
) {
390394
for (entity, clusters) in views.iter() {
391395
commands.get_or_spawn(entity).insert_bundle((
392396
ExtractedClustersPointLights {
@@ -404,20 +408,22 @@ pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters)
404408
#[allow(clippy::too_many_arguments)]
405409
pub fn extract_lights(
406410
mut commands: Commands,
407-
point_light_shadow_map: Res<PointLightShadowMap>,
408-
directional_light_shadow_map: Res<DirectionalLightShadowMap>,
409-
global_point_lights: Res<GlobalVisiblePointLights>,
410-
mut point_lights: Query<(&PointLight, &mut CubemapVisibleEntities, &GlobalTransform)>,
411-
mut spot_lights: Query<(&SpotLight, &mut VisibleEntities, &GlobalTransform)>,
412-
mut directional_lights: Query<
413-
(
414-
Entity,
415-
&DirectionalLight,
416-
&mut VisibleEntities,
417-
&GlobalTransform,
418-
&Visibility,
419-
),
420-
Without<SpotLight>,
411+
point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
412+
directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
413+
global_point_lights: Extract<Res<GlobalVisiblePointLights>>,
414+
point_lights: Extract<Query<(&PointLight, &CubemapVisibleEntities, &GlobalTransform)>>,
415+
spot_lights: Extract<Query<(&SpotLight, &VisibleEntities, &GlobalTransform)>>,
416+
directional_lights: Extract<
417+
Query<
418+
(
419+
Entity,
420+
&DirectionalLight,
421+
&VisibleEntities,
422+
&GlobalTransform,
423+
&Visibility,
424+
),
425+
Without<SpotLight>,
426+
>,
421427
>,
422428
mut previous_point_lights_len: Local<usize>,
423429
mut previous_spot_lights_len: Local<usize>,
@@ -441,10 +447,10 @@ pub fn extract_lights(
441447

442448
let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
443449
for entity in global_point_lights.iter().copied() {
444-
if let Ok((point_light, cubemap_visible_entities, transform)) = point_lights.get_mut(entity)
445-
{
446-
let render_cubemap_visible_entities =
447-
std::mem::take(cubemap_visible_entities.into_inner());
450+
if let Ok((point_light, cubemap_visible_entities, transform)) = point_lights.get(entity) {
451+
// TODO: This is very much not ideal. We should be able to re-use the vector memory.
452+
// However, since exclusive access to the main world in extract is ill-advised, we just clone here.
453+
let render_cubemap_visible_entities = cubemap_visible_entities.clone();
448454
point_lights_values.push((
449455
entity,
450456
(
@@ -475,8 +481,10 @@ pub fn extract_lights(
475481

476482
let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
477483
for entity in global_point_lights.iter().copied() {
478-
if let Ok((spot_light, visible_entities, transform)) = spot_lights.get_mut(entity) {
479-
let render_visible_entities = std::mem::take(visible_entities.into_inner());
484+
if let Ok((spot_light, visible_entities, transform)) = spot_lights.get(entity) {
485+
// TODO: This is very much not ideal. We should be able to re-use the vector memory.
486+
// However, since exclusive access to the main world in extract is ill-advised, we just clone here.
487+
let render_visible_entities = visible_entities.clone();
480488
let texel_size =
481489
2.0 * spot_light.outer_angle.tan() / directional_light_shadow_map.size as f32;
482490

@@ -512,7 +520,7 @@ pub fn extract_lights(
512520
commands.insert_or_spawn_batch(spot_lights_values);
513521

514522
for (entity, directional_light, visible_entities, transform, visibility) in
515-
directional_lights.iter_mut()
523+
directional_lights.iter()
516524
{
517525
if !visibility.is_visible {
518526
continue;
@@ -530,7 +538,8 @@ pub fn extract_lights(
530538
);
531539
let directional_light_texel_size =
532540
largest_dimension / directional_light_shadow_map.size as f32;
533-
let render_visible_entities = std::mem::take(visible_entities.into_inner());
541+
// TODO: As above
542+
let render_visible_entities = visible_entities.clone();
534543
commands.get_or_spawn(entity).insert_bundle((
535544
ExtractedDirectionalLight {
536545
color: directional_light.color,

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use bevy_render::{
2525
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
2626
},
2727
view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms},
28-
RenderApp, RenderStage,
28+
Extract, RenderApp, RenderStage,
2929
};
3030
use bevy_transform::components::GlobalTransform;
3131
use std::num::NonZeroU64;
@@ -118,14 +118,16 @@ pub fn extract_meshes(
118118
mut commands: Commands,
119119
mut prev_caster_commands_len: Local<usize>,
120120
mut prev_not_caster_commands_len: Local<usize>,
121-
meshes_query: Query<(
122-
Entity,
123-
&ComputedVisibility,
124-
&GlobalTransform,
125-
&Handle<Mesh>,
126-
Option<With<NotShadowReceiver>>,
127-
Option<With<NotShadowCaster>>,
128-
)>,
121+
meshes_query: Extract<
122+
Query<(
123+
Entity,
124+
&ComputedVisibility,
125+
&GlobalTransform,
126+
&Handle<Mesh>,
127+
Option<With<NotShadowReceiver>>,
128+
Option<With<NotShadowCaster>>,
129+
)>,
130+
>,
129131
) {
130132
let mut caster_commands = Vec::with_capacity(*prev_caster_commands_len);
131133
let mut not_caster_commands = Vec::with_capacity(*prev_not_caster_commands_len);
@@ -202,12 +204,12 @@ impl SkinnedMeshJoints {
202204
}
203205

204206
pub fn extract_skinned_meshes(
205-
query: Query<(Entity, &ComputedVisibility, &SkinnedMesh)>,
206-
inverse_bindposes: Res<Assets<SkinnedMeshInverseBindposes>>,
207-
joint_query: Query<&GlobalTransform>,
208207
mut commands: Commands,
209208
mut previous_len: Local<usize>,
210209
mut previous_joint_len: Local<usize>,
210+
query: Extract<Query<(Entity, &ComputedVisibility, &SkinnedMesh)>>,
211+
inverse_bindposes: Extract<Res<Assets<SkinnedMeshInverseBindposes>>>,
212+
joint_query: Extract<Query<&GlobalTransform>>,
211213
) {
212214
let mut values = Vec::with_capacity(*previous_len);
213215
let mut joints = Vec::with_capacity(*previous_joint_len);

crates/bevy_render/src/camera/camera.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
render_asset::RenderAssets,
55
render_resource::TextureView,
66
view::{ExtractedView, ExtractedWindows, VisibleEntities},
7+
Extract,
78
};
89
use bevy_asset::{AssetEvent, Assets, Handle};
910
use bevy_derive::{Deref, DerefMut};
@@ -393,13 +394,15 @@ pub struct ExtractedCamera {
393394

394395
pub fn extract_cameras(
395396
mut commands: Commands,
396-
query: Query<(
397-
Entity,
398-
&Camera,
399-
&CameraRenderGraph,
400-
&GlobalTransform,
401-
&VisibleEntities,
402-
)>,
397+
query: Extract<
398+
Query<(
399+
Entity,
400+
&Camera,
401+
&CameraRenderGraph,
402+
&GlobalTransform,
403+
&VisibleEntities,
404+
)>,
405+
>,
403406
) {
404407
for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() {
405408
if !camera.is_active {

crates/bevy_render/src/extract_component.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ use crate::{
22
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
33
renderer::{RenderDevice, RenderQueue},
44
view::ComputedVisibility,
5-
RenderApp, RenderStage,
5+
Extract, RenderApp, RenderStage,
66
};
77
use bevy_app::{App, Plugin};
88
use bevy_asset::{Asset, Handle};
99
use bevy_ecs::{
1010
component::Component,
1111
prelude::*,
12-
query::{QueryItem, WorldQuery},
13-
system::{lifetimeless::Read, StaticSystemParam},
12+
query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
13+
system::lifetimeless::Read,
1414
};
1515
use std::{marker::PhantomData, ops::Deref};
1616

@@ -34,9 +34,9 @@ impl<C: Component> DynamicUniformIndex<C> {
3434
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
3535
pub trait ExtractComponent: Component {
3636
/// ECS [`WorldQuery`] to fetch the components to extract.
37-
type Query: WorldQuery;
37+
type Query: WorldQuery + ReadOnlyWorldQuery;
3838
/// Filters the entities with additional constraints.
39-
type Filter: WorldQuery;
39+
type Filter: WorldQuery + ReadOnlyWorldQuery;
4040
/// Defines how the component is transferred into the "render world".
4141
fn extract_component(item: QueryItem<Self::Query>) -> Self;
4242
}
@@ -182,7 +182,7 @@ impl<T: Asset> ExtractComponent for Handle<T> {
182182
fn extract_components<C: ExtractComponent>(
183183
mut commands: Commands,
184184
mut previous_len: Local<usize>,
185-
mut query: StaticSystemParam<Query<(Entity, C::Query), C::Filter>>,
185+
mut query: Extract<Query<(Entity, C::Query), C::Filter>>,
186186
) {
187187
let mut values = Vec::with_capacity(*previous_len);
188188
for (entity, query_item) in query.iter_mut() {
@@ -196,7 +196,7 @@ fn extract_components<C: ExtractComponent>(
196196
fn extract_visible_components<C: ExtractComponent>(
197197
mut commands: Commands,
198198
mut previous_len: Local<usize>,
199-
mut query: StaticSystemParam<Query<(Entity, Read<ComputedVisibility>, C::Query), C::Filter>>,
199+
mut query: Extract<Query<(Entity, &ComputedVisibility, C::Query), C::Filter>>,
200200
) {
201201
let mut values = Vec::with_capacity(*previous_len);
202202
for (entity, computed_visibility, query_item) in query.iter_mut() {

0 commit comments

Comments
 (0)