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 https://github.com/bevyengine/bevy/pull/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 <mcanders1@gmail.com>
This commit is contained in:
		
							parent
							
								
									e6faf993b0
								
							
						
					
					
						commit
						7b2cf98896
					
				@ -25,7 +25,7 @@ use bevy_render::{
 | 
			
		||||
        DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem, RenderPhase,
 | 
			
		||||
    },
 | 
			
		||||
    render_resource::CachedRenderPipelineId,
 | 
			
		||||
    RenderApp, RenderStage,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_utils::FloatOrd;
 | 
			
		||||
use std::ops::Range;
 | 
			
		||||
@ -123,7 +123,7 @@ impl BatchedPhaseItem for Transparent2d {
 | 
			
		||||
 | 
			
		||||
pub fn extract_core_2d_camera_phases(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    cameras_2d: Query<(Entity, &Camera), With<Camera2d>>,
 | 
			
		||||
    cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
 | 
			
		||||
) {
 | 
			
		||||
    for (entity, camera) in cameras_2d.iter() {
 | 
			
		||||
        if camera.is_active {
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ use bevy_render::{
 | 
			
		||||
    renderer::RenderDevice,
 | 
			
		||||
    texture::TextureCache,
 | 
			
		||||
    view::ViewDepthTexture,
 | 
			
		||||
    RenderApp, RenderStage,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_utils::{FloatOrd, HashMap};
 | 
			
		||||
 | 
			
		||||
@ -208,7 +208,7 @@ impl CachedRenderPipelinePhaseItem for Transparent3d {
 | 
			
		||||
 | 
			
		||||
pub fn extract_core_3d_camera_phases(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    cameras_3d: Query<(Entity, &Camera), With<Camera3d>>,
 | 
			
		||||
    cameras_3d: Extract<Query<(Entity, &Camera), With<Camera3d>>>,
 | 
			
		||||
) {
 | 
			
		||||
    for (entity, camera) in cameras_3d.iter() {
 | 
			
		||||
        if camera.is_active {
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,10 @@ use crate::{
 | 
			
		||||
    },
 | 
			
		||||
    world::{World, WorldId},
 | 
			
		||||
};
 | 
			
		||||
use bevy_utils::{tracing::info, HashMap, HashSet};
 | 
			
		||||
use bevy_utils::{
 | 
			
		||||
    tracing::{info, warn},
 | 
			
		||||
    HashMap, HashSet,
 | 
			
		||||
};
 | 
			
		||||
use downcast_rs::{impl_downcast, Downcast};
 | 
			
		||||
use fixedbitset::FixedBitSet;
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
@ -88,6 +91,7 @@ pub struct SystemStage {
 | 
			
		||||
    last_tick_check: u32,
 | 
			
		||||
    /// If true, buffers will be automatically applied at the end of the stage. If false, buffers must be manually applied.
 | 
			
		||||
    apply_buffers: bool,
 | 
			
		||||
    must_read_resource: Option<ComponentId>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SystemStage {
 | 
			
		||||
@ -110,6 +114,7 @@ impl SystemStage {
 | 
			
		||||
            uninitialized_at_end: vec![],
 | 
			
		||||
            last_tick_check: Default::default(),
 | 
			
		||||
            apply_buffers: true,
 | 
			
		||||
            must_read_resource: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -139,6 +144,10 @@ impl SystemStage {
 | 
			
		||||
        self.executor = executor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn set_must_read_resource(&mut self, resource_id: ComponentId) {
 | 
			
		||||
        self.must_read_resource = Some(resource_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn with_system<Params>(mut self, system: impl IntoSystemDescriptor<Params>) -> Self {
 | 
			
		||||
        self.add_system(system);
 | 
			
		||||
@ -563,6 +572,20 @@ impl SystemStage {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn check_uses_resource(&self, resource_id: ComponentId, world: &World) {
 | 
			
		||||
        debug_assert!(!self.systems_modified);
 | 
			
		||||
        for system in &self.parallel {
 | 
			
		||||
            let access = system.component_access().unwrap();
 | 
			
		||||
            if !access.has_read(resource_id) {
 | 
			
		||||
                let component_name = world.components().get_info(resource_id).unwrap().name();
 | 
			
		||||
                warn!(
 | 
			
		||||
                    "System {} doesn't access resource {component_name}, despite being required to",
 | 
			
		||||
                    system.name()
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// All system and component change ticks are scanned once the world counter has incremented
 | 
			
		||||
    /// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD)
 | 
			
		||||
    /// times since the previous `check_tick` scan.
 | 
			
		||||
@ -782,6 +805,9 @@ impl Stage for SystemStage {
 | 
			
		||||
            if world.contains_resource::<ReportExecutionOrderAmbiguities>() {
 | 
			
		||||
                self.report_ambiguities(world);
 | 
			
		||||
            }
 | 
			
		||||
            if let Some(resource_id) = self.must_read_resource {
 | 
			
		||||
                self.check_uses_resource(resource_id, world);
 | 
			
		||||
            }
 | 
			
		||||
        } else if self.executor_modified {
 | 
			
		||||
            self.executor.rebuild_cached_data(&self.parallel);
 | 
			
		||||
            self.executor_modified = false;
 | 
			
		||||
 | 
			
		||||
@ -145,12 +145,6 @@ impl<'w, 's> Commands<'w, 's> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Spawns a [`Bundle`] without pre-allocating an [`Entity`]. The [`Entity`] will be allocated
 | 
			
		||||
    /// when this [`Command`] is applied.
 | 
			
		||||
    pub fn spawn_and_forget(&mut self, bundle: impl Bundle) {
 | 
			
		||||
        self.queue.push(Spawn { bundle });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Creates a new entity with the components contained in `bundle`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This returns an [`EntityCommands`] builder, which enables inserting more components and
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ use bevy_render::{
 | 
			
		||||
    renderer::RenderDevice,
 | 
			
		||||
    texture::FallbackImage,
 | 
			
		||||
    view::{ExtractedView, Msaa, VisibleEntities},
 | 
			
		||||
    RenderApp, RenderStage,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_utils::{tracing::error, HashMap, HashSet};
 | 
			
		||||
use std::hash::Hash;
 | 
			
		||||
@ -455,15 +455,15 @@ pub type RenderMaterials<T> = HashMap<Handle<T>, PreparedMaterial<T>>;
 | 
			
		||||
/// into the "render world".
 | 
			
		||||
fn extract_materials<M: Material>(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut events: EventReader<AssetEvent<M>>,
 | 
			
		||||
    assets: Res<Assets<M>>,
 | 
			
		||||
    mut events: Extract<EventReader<AssetEvent<M>>>,
 | 
			
		||||
    assets: Extract<Res<Assets<M>>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut changed_assets = HashSet::default();
 | 
			
		||||
    let mut removed = Vec::new();
 | 
			
		||||
    for event in events.iter() {
 | 
			
		||||
        match event {
 | 
			
		||||
            AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
 | 
			
		||||
                changed_assets.insert(handle);
 | 
			
		||||
                changed_assets.insert(handle.clone_weak());
 | 
			
		||||
            }
 | 
			
		||||
            AssetEvent::Removed { handle } => {
 | 
			
		||||
                changed_assets.remove(handle);
 | 
			
		||||
@ -474,8 +474,8 @@ fn extract_materials<M: Material>(
 | 
			
		||||
 | 
			
		||||
    let mut extracted_assets = Vec::new();
 | 
			
		||||
    for handle in changed_assets.drain() {
 | 
			
		||||
        if let Some(asset) = assets.get(handle) {
 | 
			
		||||
            extracted_assets.push((handle.clone_weak(), asset.clone()));
 | 
			
		||||
        if let Some(asset) = assets.get(&handle) {
 | 
			
		||||
            extracted_assets.push((handle, asset.clone()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,7 @@ use bevy_render::{
 | 
			
		||||
    view::{
 | 
			
		||||
        ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility, VisibleEntities,
 | 
			
		||||
    },
 | 
			
		||||
    Extract,
 | 
			
		||||
};
 | 
			
		||||
use bevy_transform::components::GlobalTransform;
 | 
			
		||||
use bevy_utils::FloatOrd;
 | 
			
		||||
@ -386,7 +387,10 @@ pub struct ExtractedClustersPointLights {
 | 
			
		||||
    data: Vec<VisiblePointLights>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters), With<Camera>>) {
 | 
			
		||||
pub fn extract_clusters(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    views: Extract<Query<(Entity, &Clusters), With<Camera>>>,
 | 
			
		||||
) {
 | 
			
		||||
    for (entity, clusters) in views.iter() {
 | 
			
		||||
        commands.get_or_spawn(entity).insert_bundle((
 | 
			
		||||
            ExtractedClustersPointLights {
 | 
			
		||||
@ -404,20 +408,22 @@ pub fn extract_clusters(mut commands: Commands, views: Query<(Entity, &Clusters)
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
pub fn extract_lights(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    point_light_shadow_map: Res<PointLightShadowMap>,
 | 
			
		||||
    directional_light_shadow_map: Res<DirectionalLightShadowMap>,
 | 
			
		||||
    global_point_lights: Res<GlobalVisiblePointLights>,
 | 
			
		||||
    mut point_lights: Query<(&PointLight, &mut CubemapVisibleEntities, &GlobalTransform)>,
 | 
			
		||||
    mut spot_lights: Query<(&SpotLight, &mut VisibleEntities, &GlobalTransform)>,
 | 
			
		||||
    mut directional_lights: Query<
 | 
			
		||||
        (
 | 
			
		||||
            Entity,
 | 
			
		||||
            &DirectionalLight,
 | 
			
		||||
            &mut VisibleEntities,
 | 
			
		||||
            &GlobalTransform,
 | 
			
		||||
            &Visibility,
 | 
			
		||||
        ),
 | 
			
		||||
        Without<SpotLight>,
 | 
			
		||||
    point_light_shadow_map: Extract<Res<PointLightShadowMap>>,
 | 
			
		||||
    directional_light_shadow_map: Extract<Res<DirectionalLightShadowMap>>,
 | 
			
		||||
    global_point_lights: Extract<Res<GlobalVisiblePointLights>>,
 | 
			
		||||
    point_lights: Extract<Query<(&PointLight, &CubemapVisibleEntities, &GlobalTransform)>>,
 | 
			
		||||
    spot_lights: Extract<Query<(&SpotLight, &VisibleEntities, &GlobalTransform)>>,
 | 
			
		||||
    directional_lights: Extract<
 | 
			
		||||
        Query<
 | 
			
		||||
            (
 | 
			
		||||
                Entity,
 | 
			
		||||
                &DirectionalLight,
 | 
			
		||||
                &VisibleEntities,
 | 
			
		||||
                &GlobalTransform,
 | 
			
		||||
                &Visibility,
 | 
			
		||||
            ),
 | 
			
		||||
            Without<SpotLight>,
 | 
			
		||||
        >,
 | 
			
		||||
    >,
 | 
			
		||||
    mut previous_point_lights_len: Local<usize>,
 | 
			
		||||
    mut previous_spot_lights_len: Local<usize>,
 | 
			
		||||
@ -441,10 +447,10 @@ pub fn extract_lights(
 | 
			
		||||
 | 
			
		||||
    let mut point_lights_values = Vec::with_capacity(*previous_point_lights_len);
 | 
			
		||||
    for entity in global_point_lights.iter().copied() {
 | 
			
		||||
        if let Ok((point_light, cubemap_visible_entities, transform)) = point_lights.get_mut(entity)
 | 
			
		||||
        {
 | 
			
		||||
            let render_cubemap_visible_entities =
 | 
			
		||||
                std::mem::take(cubemap_visible_entities.into_inner());
 | 
			
		||||
        if let Ok((point_light, cubemap_visible_entities, transform)) = point_lights.get(entity) {
 | 
			
		||||
            // TODO: This is very much not ideal. We should be able to re-use the vector memory.
 | 
			
		||||
            // However, since exclusive access to the main world in extract is ill-advised, we just clone here.
 | 
			
		||||
            let render_cubemap_visible_entities = cubemap_visible_entities.clone();
 | 
			
		||||
            point_lights_values.push((
 | 
			
		||||
                entity,
 | 
			
		||||
                (
 | 
			
		||||
@ -475,8 +481,10 @@ pub fn extract_lights(
 | 
			
		||||
 | 
			
		||||
    let mut spot_lights_values = Vec::with_capacity(*previous_spot_lights_len);
 | 
			
		||||
    for entity in global_point_lights.iter().copied() {
 | 
			
		||||
        if let Ok((spot_light, visible_entities, transform)) = spot_lights.get_mut(entity) {
 | 
			
		||||
            let render_visible_entities = std::mem::take(visible_entities.into_inner());
 | 
			
		||||
        if let Ok((spot_light, visible_entities, transform)) = spot_lights.get(entity) {
 | 
			
		||||
            // TODO: This is very much not ideal. We should be able to re-use the vector memory.
 | 
			
		||||
            // However, since exclusive access to the main world in extract is ill-advised, we just clone here.
 | 
			
		||||
            let render_visible_entities = visible_entities.clone();
 | 
			
		||||
            let texel_size =
 | 
			
		||||
                2.0 * spot_light.outer_angle.tan() / directional_light_shadow_map.size as f32;
 | 
			
		||||
 | 
			
		||||
@ -512,7 +520,7 @@ pub fn extract_lights(
 | 
			
		||||
    commands.insert_or_spawn_batch(spot_lights_values);
 | 
			
		||||
 | 
			
		||||
    for (entity, directional_light, visible_entities, transform, visibility) in
 | 
			
		||||
        directional_lights.iter_mut()
 | 
			
		||||
        directional_lights.iter()
 | 
			
		||||
    {
 | 
			
		||||
        if !visibility.is_visible {
 | 
			
		||||
            continue;
 | 
			
		||||
@ -530,7 +538,8 @@ pub fn extract_lights(
 | 
			
		||||
            );
 | 
			
		||||
        let directional_light_texel_size =
 | 
			
		||||
            largest_dimension / directional_light_shadow_map.size as f32;
 | 
			
		||||
        let render_visible_entities = std::mem::take(visible_entities.into_inner());
 | 
			
		||||
        // TODO: As above
 | 
			
		||||
        let render_visible_entities = visible_entities.clone();
 | 
			
		||||
        commands.get_or_spawn(entity).insert_bundle((
 | 
			
		||||
            ExtractedDirectionalLight {
 | 
			
		||||
                color: directional_light.color,
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ use bevy_render::{
 | 
			
		||||
        BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
 | 
			
		||||
    },
 | 
			
		||||
    view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms},
 | 
			
		||||
    RenderApp, RenderStage,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_transform::components::GlobalTransform;
 | 
			
		||||
use std::num::NonZeroU64;
 | 
			
		||||
@ -118,14 +118,16 @@ pub fn extract_meshes(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut prev_caster_commands_len: Local<usize>,
 | 
			
		||||
    mut prev_not_caster_commands_len: Local<usize>,
 | 
			
		||||
    meshes_query: Query<(
 | 
			
		||||
        Entity,
 | 
			
		||||
        &ComputedVisibility,
 | 
			
		||||
        &GlobalTransform,
 | 
			
		||||
        &Handle<Mesh>,
 | 
			
		||||
        Option<With<NotShadowReceiver>>,
 | 
			
		||||
        Option<With<NotShadowCaster>>,
 | 
			
		||||
    )>,
 | 
			
		||||
    meshes_query: Extract<
 | 
			
		||||
        Query<(
 | 
			
		||||
            Entity,
 | 
			
		||||
            &ComputedVisibility,
 | 
			
		||||
            &GlobalTransform,
 | 
			
		||||
            &Handle<Mesh>,
 | 
			
		||||
            Option<With<NotShadowReceiver>>,
 | 
			
		||||
            Option<With<NotShadowCaster>>,
 | 
			
		||||
        )>,
 | 
			
		||||
    >,
 | 
			
		||||
) {
 | 
			
		||||
    let mut caster_commands = Vec::with_capacity(*prev_caster_commands_len);
 | 
			
		||||
    let mut not_caster_commands = Vec::with_capacity(*prev_not_caster_commands_len);
 | 
			
		||||
@ -202,12 +204,12 @@ impl SkinnedMeshJoints {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_skinned_meshes(
 | 
			
		||||
    query: Query<(Entity, &ComputedVisibility, &SkinnedMesh)>,
 | 
			
		||||
    inverse_bindposes: Res<Assets<SkinnedMeshInverseBindposes>>,
 | 
			
		||||
    joint_query: Query<&GlobalTransform>,
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut previous_len: Local<usize>,
 | 
			
		||||
    mut previous_joint_len: Local<usize>,
 | 
			
		||||
    query: Extract<Query<(Entity, &ComputedVisibility, &SkinnedMesh)>>,
 | 
			
		||||
    inverse_bindposes: Extract<Res<Assets<SkinnedMeshInverseBindposes>>>,
 | 
			
		||||
    joint_query: Extract<Query<&GlobalTransform>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut values = Vec::with_capacity(*previous_len);
 | 
			
		||||
    let mut joints = Vec::with_capacity(*previous_joint_len);
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ use crate::{
 | 
			
		||||
    render_asset::RenderAssets,
 | 
			
		||||
    render_resource::TextureView,
 | 
			
		||||
    view::{ExtractedView, ExtractedWindows, VisibleEntities},
 | 
			
		||||
    Extract,
 | 
			
		||||
};
 | 
			
		||||
use bevy_asset::{AssetEvent, Assets, Handle};
 | 
			
		||||
use bevy_derive::{Deref, DerefMut};
 | 
			
		||||
@ -393,13 +394,15 @@ pub struct ExtractedCamera {
 | 
			
		||||
 | 
			
		||||
pub fn extract_cameras(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    query: Query<(
 | 
			
		||||
        Entity,
 | 
			
		||||
        &Camera,
 | 
			
		||||
        &CameraRenderGraph,
 | 
			
		||||
        &GlobalTransform,
 | 
			
		||||
        &VisibleEntities,
 | 
			
		||||
    )>,
 | 
			
		||||
    query: Extract<
 | 
			
		||||
        Query<(
 | 
			
		||||
            Entity,
 | 
			
		||||
            &Camera,
 | 
			
		||||
            &CameraRenderGraph,
 | 
			
		||||
            &GlobalTransform,
 | 
			
		||||
            &VisibleEntities,
 | 
			
		||||
        )>,
 | 
			
		||||
    >,
 | 
			
		||||
) {
 | 
			
		||||
    for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() {
 | 
			
		||||
        if !camera.is_active {
 | 
			
		||||
 | 
			
		||||
@ -2,15 +2,15 @@ use crate::{
 | 
			
		||||
    render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
 | 
			
		||||
    renderer::{RenderDevice, RenderQueue},
 | 
			
		||||
    view::ComputedVisibility,
 | 
			
		||||
    RenderApp, RenderStage,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_app::{App, Plugin};
 | 
			
		||||
use bevy_asset::{Asset, Handle};
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
    component::Component,
 | 
			
		||||
    prelude::*,
 | 
			
		||||
    query::{QueryItem, WorldQuery},
 | 
			
		||||
    system::{lifetimeless::Read, StaticSystemParam},
 | 
			
		||||
    query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
 | 
			
		||||
    system::lifetimeless::Read,
 | 
			
		||||
};
 | 
			
		||||
use std::{marker::PhantomData, ops::Deref};
 | 
			
		||||
 | 
			
		||||
@ -34,9 +34,9 @@ impl<C: Component> DynamicUniformIndex<C> {
 | 
			
		||||
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
 | 
			
		||||
pub trait ExtractComponent: Component {
 | 
			
		||||
    /// ECS [`WorldQuery`] to fetch the components to extract.
 | 
			
		||||
    type Query: WorldQuery;
 | 
			
		||||
    type Query: WorldQuery + ReadOnlyWorldQuery;
 | 
			
		||||
    /// Filters the entities with additional constraints.
 | 
			
		||||
    type Filter: WorldQuery;
 | 
			
		||||
    type Filter: WorldQuery + ReadOnlyWorldQuery;
 | 
			
		||||
    /// Defines how the component is transferred into the "render world".
 | 
			
		||||
    fn extract_component(item: QueryItem<Self::Query>) -> Self;
 | 
			
		||||
}
 | 
			
		||||
@ -182,7 +182,7 @@ impl<T: Asset> ExtractComponent for Handle<T> {
 | 
			
		||||
fn extract_components<C: ExtractComponent>(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut previous_len: Local<usize>,
 | 
			
		||||
    mut query: StaticSystemParam<Query<(Entity, C::Query), C::Filter>>,
 | 
			
		||||
    mut query: Extract<Query<(Entity, C::Query), C::Filter>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut values = Vec::with_capacity(*previous_len);
 | 
			
		||||
    for (entity, query_item) in query.iter_mut() {
 | 
			
		||||
@ -196,7 +196,7 @@ fn extract_components<C: ExtractComponent>(
 | 
			
		||||
fn extract_visible_components<C: ExtractComponent>(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut previous_len: Local<usize>,
 | 
			
		||||
    mut query: StaticSystemParam<Query<(Entity, Read<ComputedVisibility>, C::Query), C::Filter>>,
 | 
			
		||||
    mut query: Extract<Query<(Entity, &ComputedVisibility, C::Query), C::Filter>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut values = Vec::with_capacity(*previous_len);
 | 
			
		||||
    for (entity, computed_visibility, query_item) in query.iter_mut() {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										120
									
								
								crates/bevy_render/src/extract_param.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								crates/bevy_render/src/extract_param.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
			
		||||
use crate::MainWorld;
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
    prelude::*,
 | 
			
		||||
    system::{
 | 
			
		||||
        ReadOnlySystemParamFetch, ResState, SystemMeta, SystemParam, SystemParamFetch,
 | 
			
		||||
        SystemParamState, SystemState,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
use std::ops::{Deref, DerefMut};
 | 
			
		||||
 | 
			
		||||
/// A helper for accessing [`MainWorld`] content using a system parameter.
 | 
			
		||||
///
 | 
			
		||||
/// A [`SystemParam`] adapter which applies the contained `SystemParam` to the [`World`]
 | 
			
		||||
/// contained in [`MainWorld`]. This parameter only works for systems run
 | 
			
		||||
/// during [`RenderStage::Extract`].
 | 
			
		||||
///
 | 
			
		||||
/// This requires that the contained [`SystemParam`] does not mutate the world, as it
 | 
			
		||||
/// uses a read-only reference to [`MainWorld`] internally.
 | 
			
		||||
///
 | 
			
		||||
/// ## Context
 | 
			
		||||
///
 | 
			
		||||
/// [`RenderStage::Extract`] is used to extract (move) data from the simulation world ([`MainWorld`]) to the
 | 
			
		||||
/// render world. The render world drives rendering each frame (generally to a [Window]).
 | 
			
		||||
/// This design is used to allow performing calculations related to rendering a prior frame at the same
 | 
			
		||||
/// time as the next frame is simulated, which increases throughput (FPS).
 | 
			
		||||
///
 | 
			
		||||
/// [`Extract`] is used to get data from the main world during [`RenderStage::Extract`].
 | 
			
		||||
///
 | 
			
		||||
/// ## Examples
 | 
			
		||||
///
 | 
			
		||||
/// ```rust
 | 
			
		||||
/// use bevy_ecs::prelude::*;
 | 
			
		||||
/// use bevy_render::Extract;
 | 
			
		||||
/// # #[derive(Component)]
 | 
			
		||||
/// # struct Cloud;
 | 
			
		||||
/// fn extract_clouds(mut commands: Commands, clouds: Extract<Query<Entity, With<Cloud>>>) {
 | 
			
		||||
///     for cloud in clouds.iter() {
 | 
			
		||||
///         commands.get_or_spawn(cloud).insert(Cloud);
 | 
			
		||||
///     }
 | 
			
		||||
/// }
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// [`RenderStage::Extract`]: crate::RenderStage::Extract
 | 
			
		||||
/// [Window]: bevy_window::Window
 | 
			
		||||
pub struct Extract<'w, 's, P: SystemParam + 'static>
 | 
			
		||||
where
 | 
			
		||||
    P::Fetch: ReadOnlySystemParamFetch,
 | 
			
		||||
{
 | 
			
		||||
    item: <P::Fetch as SystemParamFetch<'w, 's>>::Item,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'w, 's, P: SystemParam> SystemParam for Extract<'w, 's, P>
 | 
			
		||||
where
 | 
			
		||||
    P::Fetch: ReadOnlySystemParamFetch,
 | 
			
		||||
{
 | 
			
		||||
    type Fetch = ExtractState<P>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[doc(hidden)]
 | 
			
		||||
pub struct ExtractState<P: SystemParam> {
 | 
			
		||||
    state: SystemState<P>,
 | 
			
		||||
    main_world_state: ResState<MainWorld>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SAFETY: only accesses MainWorld resource with read only system params using ResState,
 | 
			
		||||
// which is initialized in init()
 | 
			
		||||
unsafe impl<P: SystemParam + 'static> SystemParamState for ExtractState<P> {
 | 
			
		||||
    fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
 | 
			
		||||
        let mut main_world = world.resource_mut::<MainWorld>();
 | 
			
		||||
        Self {
 | 
			
		||||
            state: SystemState::new(&mut main_world),
 | 
			
		||||
            main_world_state: ResState::init(world, system_meta),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'w, 's, P: SystemParam + 'static> SystemParamFetch<'w, 's> for ExtractState<P>
 | 
			
		||||
where
 | 
			
		||||
    P::Fetch: ReadOnlySystemParamFetch,
 | 
			
		||||
{
 | 
			
		||||
    type Item = Extract<'w, 's, P>;
 | 
			
		||||
 | 
			
		||||
    unsafe fn get_param(
 | 
			
		||||
        state: &'s mut Self,
 | 
			
		||||
        system_meta: &SystemMeta,
 | 
			
		||||
        world: &'w World,
 | 
			
		||||
        change_tick: u32,
 | 
			
		||||
    ) -> Self::Item {
 | 
			
		||||
        let main_world = ResState::<MainWorld>::get_param(
 | 
			
		||||
            &mut state.main_world_state,
 | 
			
		||||
            system_meta,
 | 
			
		||||
            world,
 | 
			
		||||
            change_tick,
 | 
			
		||||
        );
 | 
			
		||||
        let item = state.state.get(main_world.into_inner());
 | 
			
		||||
        Extract { item }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'w, 's, P: SystemParam> Deref for Extract<'w, 's, P>
 | 
			
		||||
where
 | 
			
		||||
    P::Fetch: ReadOnlySystemParamFetch,
 | 
			
		||||
{
 | 
			
		||||
    type Target = <P::Fetch as SystemParamFetch<'w, 's>>::Item;
 | 
			
		||||
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn deref(&self) -> &Self::Target {
 | 
			
		||||
        &self.item
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'w, 's, P: SystemParam> DerefMut for Extract<'w, 's, P>
 | 
			
		||||
where
 | 
			
		||||
    P::Fetch: ReadOnlySystemParamFetch,
 | 
			
		||||
{
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn deref_mut(&mut self) -> &mut Self::Target {
 | 
			
		||||
        &mut self.item
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -4,7 +4,7 @@ use bevy_app::{App, Plugin};
 | 
			
		||||
use bevy_ecs::system::{Commands, Res, Resource};
 | 
			
		||||
pub use bevy_render_macros::ExtractResource;
 | 
			
		||||
 | 
			
		||||
use crate::{RenderApp, RenderStage};
 | 
			
		||||
use crate::{Extract, RenderApp, RenderStage};
 | 
			
		||||
 | 
			
		||||
/// Describes how a resource gets extracted for rendering.
 | 
			
		||||
///
 | 
			
		||||
@ -39,8 +39,11 @@ impl<R: ExtractResource> Plugin for ExtractResourcePlugin<R> {
 | 
			
		||||
 | 
			
		||||
/// This system extracts the resource of the corresponding [`Resource`] type
 | 
			
		||||
/// by cloning it.
 | 
			
		||||
pub fn extract_resource<R: ExtractResource>(mut commands: Commands, resource: Res<R::Source>) {
 | 
			
		||||
pub fn extract_resource<R: ExtractResource>(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    resource: Extract<Res<R::Source>>,
 | 
			
		||||
) {
 | 
			
		||||
    if resource.is_changed() {
 | 
			
		||||
        commands.insert_resource(R::extract_resource(resource.into_inner()));
 | 
			
		||||
        commands.insert_resource(R::extract_resource(&*resource));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ extern crate core;
 | 
			
		||||
pub mod camera;
 | 
			
		||||
pub mod color;
 | 
			
		||||
pub mod extract_component;
 | 
			
		||||
mod extract_param;
 | 
			
		||||
pub mod extract_resource;
 | 
			
		||||
pub mod mesh;
 | 
			
		||||
pub mod primitives;
 | 
			
		||||
@ -16,6 +17,8 @@ pub mod settings;
 | 
			
		||||
pub mod texture;
 | 
			
		||||
pub mod view;
 | 
			
		||||
 | 
			
		||||
pub use extract_param::Extract;
 | 
			
		||||
 | 
			
		||||
pub mod prelude {
 | 
			
		||||
    #[doc(hidden)]
 | 
			
		||||
    pub use crate::{
 | 
			
		||||
@ -45,7 +48,10 @@ use bevy_app::{App, AppLabel, Plugin};
 | 
			
		||||
use bevy_asset::{AddAsset, AssetServer};
 | 
			
		||||
use bevy_ecs::prelude::*;
 | 
			
		||||
use bevy_utils::tracing::debug;
 | 
			
		||||
use std::ops::{Deref, DerefMut};
 | 
			
		||||
use std::{
 | 
			
		||||
    any::TypeId,
 | 
			
		||||
    ops::{Deref, DerefMut},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Contains the default Bevy rendering backend based on wgpu.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
@ -79,11 +85,14 @@ pub enum RenderStage {
 | 
			
		||||
    Cleanup,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The Render App World. This is only available as a resource during the Extract step.
 | 
			
		||||
/// The simulation [`World`] of the application, stored as a resource.
 | 
			
		||||
/// This resource is only available during [`RenderStage::Extract`] and not
 | 
			
		||||
/// during command application of that stage.
 | 
			
		||||
/// See [`Extract`] for more details.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct RenderWorld(World);
 | 
			
		||||
pub struct MainWorld(World);
 | 
			
		||||
 | 
			
		||||
impl Deref for RenderWorld {
 | 
			
		||||
impl Deref for MainWorld {
 | 
			
		||||
    type Target = World;
 | 
			
		||||
 | 
			
		||||
    fn deref(&self) -> &Self::Target {
 | 
			
		||||
@ -91,7 +100,7 @@ impl Deref for RenderWorld {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DerefMut for RenderWorld {
 | 
			
		||||
impl DerefMut for MainWorld {
 | 
			
		||||
    fn deref_mut(&mut self) -> &mut Self::Target {
 | 
			
		||||
        &mut self.0
 | 
			
		||||
    }
 | 
			
		||||
@ -107,11 +116,6 @@ pub mod main_graph {
 | 
			
		||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
 | 
			
		||||
pub struct RenderApp;
 | 
			
		||||
 | 
			
		||||
/// A "scratch" world used to avoid allocating new worlds every frame when
 | 
			
		||||
/// swapping out the [`RenderWorld`].
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
struct ScratchRenderWorld(World);
 | 
			
		||||
 | 
			
		||||
impl Plugin for RenderPlugin {
 | 
			
		||||
    /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
 | 
			
		||||
    fn build(&self, app: &mut App) {
 | 
			
		||||
@ -150,7 +154,7 @@ impl Plugin for RenderPlugin {
 | 
			
		||||
            app.insert_resource(device.clone())
 | 
			
		||||
                .insert_resource(queue.clone())
 | 
			
		||||
                .insert_resource(adapter_info.clone())
 | 
			
		||||
                .init_resource::<ScratchRenderWorld>()
 | 
			
		||||
                .init_resource::<ScratchMainWorld>()
 | 
			
		||||
                .register_type::<Frustum>()
 | 
			
		||||
                .register_type::<CubemapFrusta>();
 | 
			
		||||
 | 
			
		||||
@ -160,8 +164,20 @@ impl Plugin for RenderPlugin {
 | 
			
		||||
            let mut render_app = App::empty();
 | 
			
		||||
            let mut extract_stage =
 | 
			
		||||
                SystemStage::parallel().with_system(PipelineCache::extract_shaders);
 | 
			
		||||
            // Get the ComponentId for MainWorld. This does technically 'waste' a `WorldId`, but that's probably fine
 | 
			
		||||
            render_app.init_resource::<MainWorld>();
 | 
			
		||||
            render_app.world.remove_resource::<MainWorld>();
 | 
			
		||||
            let main_world_in_render = render_app
 | 
			
		||||
                .world
 | 
			
		||||
                .components()
 | 
			
		||||
                .get_resource_id(TypeId::of::<MainWorld>());
 | 
			
		||||
            // `Extract` systems must read from the main world. We want to emit an error when that doesn't occur
 | 
			
		||||
            // Safe to unwrap: Ensured it existed just above
 | 
			
		||||
            extract_stage.set_must_read_resource(main_world_in_render.unwrap());
 | 
			
		||||
            // don't apply buffers when the stage finishes running
 | 
			
		||||
            // extract stage runs on the app world, but the buffers are applied to the render world
 | 
			
		||||
            // extract stage runs on the render world, but buffers are applied
 | 
			
		||||
            // after access to the main world is removed
 | 
			
		||||
            // See also https://github.com/bevyengine/bevy/issues/5082
 | 
			
		||||
            extract_stage.set_apply_buffers(false);
 | 
			
		||||
            render_app
 | 
			
		||||
                .add_stage(RenderStage::Extract, extract_stage)
 | 
			
		||||
@ -300,6 +316,11 @@ impl Plugin for RenderPlugin {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A "scratch" world used to avoid allocating new worlds every frame when
 | 
			
		||||
/// swapping out the [`MainWorld`] for [`RenderStage::Extract`].
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
struct ScratchMainWorld(World);
 | 
			
		||||
 | 
			
		||||
/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer.
 | 
			
		||||
/// This updates the render world with the extracted ECS data of the current frame.
 | 
			
		||||
fn extract(app_world: &mut World, render_app: &mut App) {
 | 
			
		||||
@ -308,17 +329,20 @@ fn extract(app_world: &mut World, render_app: &mut App) {
 | 
			
		||||
        .get_stage_mut::<SystemStage>(&RenderStage::Extract)
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    // temporarily add the render world to the app world as a resource
 | 
			
		||||
    let scratch_world = app_world.remove_resource::<ScratchRenderWorld>().unwrap();
 | 
			
		||||
    let render_world = std::mem::replace(&mut render_app.world, scratch_world.0);
 | 
			
		||||
    app_world.insert_resource(RenderWorld(render_world));
 | 
			
		||||
    // temporarily add the app world to the render world as a resource
 | 
			
		||||
    let scratch_world = app_world.remove_resource::<ScratchMainWorld>().unwrap();
 | 
			
		||||
    let inserted_world = std::mem::replace(app_world, scratch_world.0);
 | 
			
		||||
    let running_world = &mut render_app.world;
 | 
			
		||||
    running_world.insert_resource(MainWorld(inserted_world));
 | 
			
		||||
 | 
			
		||||
    extract.run(app_world);
 | 
			
		||||
    extract.run(running_world);
 | 
			
		||||
    // move the app world back, as if nothing happened.
 | 
			
		||||
    let inserted_world = running_world.remove_resource::<MainWorld>().unwrap();
 | 
			
		||||
    let scratch_world = std::mem::replace(app_world, inserted_world.0);
 | 
			
		||||
    app_world.insert_resource(ScratchMainWorld(scratch_world));
 | 
			
		||||
 | 
			
		||||
    // add the render world back to the render app
 | 
			
		||||
    let render_world = app_world.remove_resource::<RenderWorld>().unwrap();
 | 
			
		||||
    let scratch_world = std::mem::replace(&mut render_app.world, render_world.0);
 | 
			
		||||
    app_world.insert_resource(ScratchRenderWorld(scratch_world));
 | 
			
		||||
 | 
			
		||||
    extract.apply_buffers(&mut render_app.world);
 | 
			
		||||
    // Note: We apply buffers (read, Commands) after the `MainWorld` has been removed from the render app's world
 | 
			
		||||
    // so that in future, pipelining will be able to do this too without any code relying on it.
 | 
			
		||||
    // see <https://github.com/bevyengine/bevy/issues/5082>
 | 
			
		||||
    extract.apply_buffers(running_world);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
use crate::{RenderApp, RenderStage};
 | 
			
		||||
use crate::{Extract, RenderApp, RenderStage};
 | 
			
		||||
use bevy_app::{App, Plugin};
 | 
			
		||||
use bevy_asset::{Asset, AssetEvent, Assets, Handle};
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
@ -123,15 +123,15 @@ pub type RenderAssets<A> = HashMap<Handle<A>, <A as RenderAsset>::PreparedAsset>
 | 
			
		||||
/// into the "render world".
 | 
			
		||||
fn extract_render_asset<A: RenderAsset>(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut events: EventReader<AssetEvent<A>>,
 | 
			
		||||
    assets: Res<Assets<A>>,
 | 
			
		||||
    mut events: Extract<EventReader<AssetEvent<A>>>,
 | 
			
		||||
    assets: Extract<Res<Assets<A>>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut changed_assets = HashSet::default();
 | 
			
		||||
    let mut removed = Vec::new();
 | 
			
		||||
    for event in events.iter() {
 | 
			
		||||
        match event {
 | 
			
		||||
            AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
 | 
			
		||||
                changed_assets.insert(handle);
 | 
			
		||||
                changed_assets.insert(handle.clone_weak());
 | 
			
		||||
            }
 | 
			
		||||
            AssetEvent::Removed { handle } => {
 | 
			
		||||
                changed_assets.remove(handle);
 | 
			
		||||
@ -142,8 +142,8 @@ fn extract_render_asset<A: RenderAsset>(
 | 
			
		||||
 | 
			
		||||
    let mut extracted_assets = Vec::new();
 | 
			
		||||
    for handle in changed_assets.drain() {
 | 
			
		||||
        if let Some(asset) = assets.get(handle) {
 | 
			
		||||
            extracted_assets.push((handle.clone_weak(), asset.extract_asset()));
 | 
			
		||||
        if let Some(asset) = assets.get(&handle) {
 | 
			
		||||
            extracted_assets.push((handle, asset.extract_asset()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ use crate::{
 | 
			
		||||
        ShaderProcessor, ShaderReflectError,
 | 
			
		||||
    },
 | 
			
		||||
    renderer::RenderDevice,
 | 
			
		||||
    RenderWorld,
 | 
			
		||||
    Extract,
 | 
			
		||||
};
 | 
			
		||||
use bevy_asset::{AssetEvent, Assets, Handle};
 | 
			
		||||
use bevy_ecs::event::EventReader;
 | 
			
		||||
@ -546,11 +546,10 @@ impl PipelineCache {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn extract_shaders(
 | 
			
		||||
        mut world: ResMut<RenderWorld>,
 | 
			
		||||
        shaders: Res<Assets<Shader>>,
 | 
			
		||||
        mut events: EventReader<AssetEvent<Shader>>,
 | 
			
		||||
        mut cache: ResMut<Self>,
 | 
			
		||||
        shaders: Extract<Res<Assets<Shader>>>,
 | 
			
		||||
        mut events: Extract<EventReader<AssetEvent<Shader>>>,
 | 
			
		||||
    ) {
 | 
			
		||||
        let mut cache = world.resource_mut::<Self>();
 | 
			
		||||
        for event in events.iter() {
 | 
			
		||||
            match event {
 | 
			
		||||
                AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ use crate::{
 | 
			
		||||
    render_resource::TextureView,
 | 
			
		||||
    renderer::{RenderDevice, RenderInstance},
 | 
			
		||||
    texture::BevyDefault,
 | 
			
		||||
    RenderApp, RenderStage, RenderWorld,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_app::{App, Plugin};
 | 
			
		||||
use bevy_ecs::prelude::*;
 | 
			
		||||
@ -68,11 +68,10 @@ impl DerefMut for ExtractedWindows {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_windows(
 | 
			
		||||
    mut render_world: ResMut<RenderWorld>,
 | 
			
		||||
    mut closed: EventReader<WindowClosed>,
 | 
			
		||||
    windows: Res<Windows>,
 | 
			
		||||
    mut extracted_windows: ResMut<ExtractedWindows>,
 | 
			
		||||
    mut closed: Extract<EventReader<WindowClosed>>,
 | 
			
		||||
    windows: Extract<Res<Windows>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut extracted_windows = render_world.get_resource_mut::<ExtractedWindows>().unwrap();
 | 
			
		||||
    for window in windows.iter() {
 | 
			
		||||
        let (new_width, new_height) = (
 | 
			
		||||
            window.physical_width().max(1),
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ use bevy_render::{
 | 
			
		||||
        BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
 | 
			
		||||
    },
 | 
			
		||||
    view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms},
 | 
			
		||||
    RenderApp, RenderStage,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_transform::components::GlobalTransform;
 | 
			
		||||
 | 
			
		||||
@ -116,7 +116,7 @@ bitflags::bitflags! {
 | 
			
		||||
pub fn extract_mesh2d(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut previous_len: Local<usize>,
 | 
			
		||||
    query: Query<(Entity, &ComputedVisibility, &GlobalTransform, &Mesh2dHandle)>,
 | 
			
		||||
    query: Extract<Query<(Entity, &ComputedVisibility, &GlobalTransform, &Mesh2dHandle)>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut values = Vec::with_capacity(*previous_len);
 | 
			
		||||
    for (entity, computed_visibility, transform, handle) in query.iter() {
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ use bevy_render::{
 | 
			
		||||
    renderer::{RenderDevice, RenderQueue},
 | 
			
		||||
    texture::{BevyDefault, Image},
 | 
			
		||||
    view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility},
 | 
			
		||||
    RenderWorld,
 | 
			
		||||
    Extract,
 | 
			
		||||
};
 | 
			
		||||
use bevy_transform::components::GlobalTransform;
 | 
			
		||||
use bevy_utils::FloatOrd;
 | 
			
		||||
@ -197,10 +197,9 @@ pub struct SpriteAssetEvents {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_sprite_events(
 | 
			
		||||
    mut render_world: ResMut<RenderWorld>,
 | 
			
		||||
    mut image_events: EventReader<AssetEvent<Image>>,
 | 
			
		||||
    mut events: ResMut<SpriteAssetEvents>,
 | 
			
		||||
    mut image_events: Extract<EventReader<AssetEvent<Image>>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut events = render_world.resource_mut::<SpriteAssetEvents>();
 | 
			
		||||
    let SpriteAssetEvents { ref mut images } = *events;
 | 
			
		||||
    images.clear();
 | 
			
		||||
 | 
			
		||||
@ -221,17 +220,18 @@ pub fn extract_sprite_events(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_sprites(
 | 
			
		||||
    mut render_world: ResMut<RenderWorld>,
 | 
			
		||||
    texture_atlases: Res<Assets<TextureAtlas>>,
 | 
			
		||||
    sprite_query: Query<(&Visibility, &Sprite, &GlobalTransform, &Handle<Image>)>,
 | 
			
		||||
    atlas_query: Query<(
 | 
			
		||||
        &Visibility,
 | 
			
		||||
        &TextureAtlasSprite,
 | 
			
		||||
        &GlobalTransform,
 | 
			
		||||
        &Handle<TextureAtlas>,
 | 
			
		||||
    )>,
 | 
			
		||||
    mut extracted_sprites: ResMut<ExtractedSprites>,
 | 
			
		||||
    texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
 | 
			
		||||
    sprite_query: Extract<Query<(&Visibility, &Sprite, &GlobalTransform, &Handle<Image>)>>,
 | 
			
		||||
    atlas_query: Extract<
 | 
			
		||||
        Query<(
 | 
			
		||||
            &Visibility,
 | 
			
		||||
            &TextureAtlasSprite,
 | 
			
		||||
            &GlobalTransform,
 | 
			
		||||
            &Handle<TextureAtlas>,
 | 
			
		||||
        )>,
 | 
			
		||||
    >,
 | 
			
		||||
) {
 | 
			
		||||
    let mut extracted_sprites = render_world.resource_mut::<ExtractedSprites>();
 | 
			
		||||
    extracted_sprites.sprites.clear();
 | 
			
		||||
    for (visibility, sprite, transform, handle) in sprite_query.iter() {
 | 
			
		||||
        if !visibility.is_visible {
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ use bevy_ecs::{
 | 
			
		||||
};
 | 
			
		||||
use bevy_math::{Vec2, Vec3};
 | 
			
		||||
use bevy_reflect::Reflect;
 | 
			
		||||
use bevy_render::{texture::Image, view::Visibility, RenderWorld};
 | 
			
		||||
use bevy_render::{texture::Image, view::Visibility, Extract};
 | 
			
		||||
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
 | 
			
		||||
use bevy_transform::prelude::{GlobalTransform, Transform};
 | 
			
		||||
use bevy_utils::HashSet;
 | 
			
		||||
@ -61,16 +61,13 @@ pub struct Text2dBundle {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_text2d_sprite(
 | 
			
		||||
    mut render_world: ResMut<RenderWorld>,
 | 
			
		||||
    texture_atlases: Res<Assets<TextureAtlas>>,
 | 
			
		||||
    text_pipeline: Res<DefaultTextPipeline>,
 | 
			
		||||
    windows: Res<Windows>,
 | 
			
		||||
    text2d_query: Query<(Entity, &Visibility, &Text, &GlobalTransform, &Text2dSize)>,
 | 
			
		||||
    mut extracted_sprites: ResMut<ExtractedSprites>,
 | 
			
		||||
    texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
 | 
			
		||||
    text_pipeline: Extract<Res<DefaultTextPipeline>>,
 | 
			
		||||
    windows: Extract<Res<Windows>>,
 | 
			
		||||
    text2d_query: Extract<Query<(Entity, &Visibility, &Text, &GlobalTransform, &Text2dSize)>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut extracted_sprites = render_world.resource_mut::<ExtractedSprites>();
 | 
			
		||||
 | 
			
		||||
    let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
 | 
			
		||||
 | 
			
		||||
    for (entity, visibility, text, transform, calculated_size) in text2d_query.iter() {
 | 
			
		||||
        if !visibility.is_visible {
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ use bevy_render::{
 | 
			
		||||
    renderer::{RenderDevice, RenderQueue},
 | 
			
		||||
    texture::Image,
 | 
			
		||||
    view::{ExtractedView, ViewUniforms, Visibility},
 | 
			
		||||
    RenderApp, RenderStage, RenderWorld,
 | 
			
		||||
    Extract, RenderApp, RenderStage,
 | 
			
		||||
};
 | 
			
		||||
use bevy_sprite::{Rect, SpriteAssetEvents, TextureAtlas};
 | 
			
		||||
use bevy_text::{DefaultTextPipeline, Text};
 | 
			
		||||
@ -174,18 +174,19 @@ pub struct ExtractedUiNodes {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_uinodes(
 | 
			
		||||
    mut render_world: ResMut<RenderWorld>,
 | 
			
		||||
    images: Res<Assets<Image>>,
 | 
			
		||||
    uinode_query: Query<(
 | 
			
		||||
        &Node,
 | 
			
		||||
        &GlobalTransform,
 | 
			
		||||
        &UiColor,
 | 
			
		||||
        &UiImage,
 | 
			
		||||
        &Visibility,
 | 
			
		||||
        Option<&CalculatedClip>,
 | 
			
		||||
    )>,
 | 
			
		||||
    mut extracted_uinodes: ResMut<ExtractedUiNodes>,
 | 
			
		||||
    images: Extract<Res<Assets<Image>>>,
 | 
			
		||||
    uinode_query: Extract<
 | 
			
		||||
        Query<(
 | 
			
		||||
            &Node,
 | 
			
		||||
            &GlobalTransform,
 | 
			
		||||
            &UiColor,
 | 
			
		||||
            &UiImage,
 | 
			
		||||
            &Visibility,
 | 
			
		||||
            Option<&CalculatedClip>,
 | 
			
		||||
        )>,
 | 
			
		||||
    >,
 | 
			
		||||
) {
 | 
			
		||||
    let mut extracted_uinodes = render_world.resource_mut::<ExtractedUiNodes>();
 | 
			
		||||
    extracted_uinodes.uinodes.clear();
 | 
			
		||||
    for (uinode, transform, color, image, visibility, clip) in uinode_query.iter() {
 | 
			
		||||
        if !visibility.is_visible {
 | 
			
		||||
@ -226,8 +227,7 @@ pub struct DefaultCameraView(pub Entity);
 | 
			
		||||
 | 
			
		||||
pub fn extract_default_ui_camera_view<T: Component>(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    render_world: Res<RenderWorld>,
 | 
			
		||||
    query: Query<(Entity, &Camera, Option<&UiCameraConfig>), With<T>>,
 | 
			
		||||
    query: Extract<Query<(Entity, &Camera, Option<&UiCameraConfig>), With<T>>>,
 | 
			
		||||
) {
 | 
			
		||||
    for (entity, camera, camera_ui) in query.iter() {
 | 
			
		||||
        // ignore cameras with disabled ui
 | 
			
		||||
@ -245,10 +245,8 @@ pub fn extract_default_ui_camera_view<T: Component>(
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            };
 | 
			
		||||
            projection.update(logical_size.x, logical_size.y);
 | 
			
		||||
            // This roundabout approach is required because spawn().id() won't work in this context
 | 
			
		||||
            let default_camera_view = render_world.entities().reserve_entity();
 | 
			
		||||
            commands
 | 
			
		||||
                .get_or_spawn(default_camera_view)
 | 
			
		||||
            let default_camera_view = commands
 | 
			
		||||
                .spawn()
 | 
			
		||||
                .insert(ExtractedView {
 | 
			
		||||
                    projection: projection.get_projection_matrix(),
 | 
			
		||||
                    transform: GlobalTransform::from_xyz(
 | 
			
		||||
@ -258,7 +256,8 @@ pub fn extract_default_ui_camera_view<T: Component>(
 | 
			
		||||
                    ),
 | 
			
		||||
                    width: physical_size.x,
 | 
			
		||||
                    height: physical_size.y,
 | 
			
		||||
                });
 | 
			
		||||
                })
 | 
			
		||||
                .id();
 | 
			
		||||
            commands.get_or_spawn(entity).insert_bundle((
 | 
			
		||||
                DefaultCameraView(default_camera_view),
 | 
			
		||||
                RenderPhase::<TransparentUi>::default(),
 | 
			
		||||
@ -268,23 +267,22 @@ pub fn extract_default_ui_camera_view<T: Component>(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_text_uinodes(
 | 
			
		||||
    mut render_world: ResMut<RenderWorld>,
 | 
			
		||||
    texture_atlases: Res<Assets<TextureAtlas>>,
 | 
			
		||||
    text_pipeline: Res<DefaultTextPipeline>,
 | 
			
		||||
    windows: Res<Windows>,
 | 
			
		||||
    uinode_query: Query<(
 | 
			
		||||
        Entity,
 | 
			
		||||
        &Node,
 | 
			
		||||
        &GlobalTransform,
 | 
			
		||||
        &Text,
 | 
			
		||||
        &Visibility,
 | 
			
		||||
        Option<&CalculatedClip>,
 | 
			
		||||
    )>,
 | 
			
		||||
    mut extracted_uinodes: ResMut<ExtractedUiNodes>,
 | 
			
		||||
    texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
 | 
			
		||||
    text_pipeline: Extract<Res<DefaultTextPipeline>>,
 | 
			
		||||
    windows: Extract<Res<Windows>>,
 | 
			
		||||
    uinode_query: Extract<
 | 
			
		||||
        Query<(
 | 
			
		||||
            Entity,
 | 
			
		||||
            &Node,
 | 
			
		||||
            &GlobalTransform,
 | 
			
		||||
            &Text,
 | 
			
		||||
            &Visibility,
 | 
			
		||||
            Option<&CalculatedClip>,
 | 
			
		||||
        )>,
 | 
			
		||||
    >,
 | 
			
		||||
) {
 | 
			
		||||
    let mut extracted_uinodes = render_world.resource_mut::<ExtractedUiNodes>();
 | 
			
		||||
 | 
			
		||||
    let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
 | 
			
		||||
 | 
			
		||||
    for (entity, uinode, transform, text, visibility, clip) in uinode_query.iter() {
 | 
			
		||||
        if !visibility.is_visible {
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ use bevy::{
 | 
			
		||||
        },
 | 
			
		||||
        texture::BevyDefault,
 | 
			
		||||
        view::VisibleEntities,
 | 
			
		||||
        RenderApp, RenderStage,
 | 
			
		||||
        Extract, RenderApp, RenderStage,
 | 
			
		||||
    },
 | 
			
		||||
    sprite::{
 | 
			
		||||
        DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform,
 | 
			
		||||
@ -286,7 +286,9 @@ impl Plugin for ColoredMesh2dPlugin {
 | 
			
		||||
pub fn extract_colored_mesh2d(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut previous_len: Local<usize>,
 | 
			
		||||
    query: Query<(Entity, &ComputedVisibility), With<ColoredMesh2d>>,
 | 
			
		||||
    // When extracting, you must use `Extract` to mark the `SystemParam`s
 | 
			
		||||
    // which should be taken from the main world.
 | 
			
		||||
    query: Extract<Query<(Entity, &ComputedVisibility), With<ColoredMesh2d>>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut values = Vec::with_capacity(*previous_len);
 | 
			
		||||
    for (entity, computed_visibility) in query.iter() {
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,18 @@
 | 
			
		||||
 | 
			
		||||
use bevy::{
 | 
			
		||||
    core_pipeline::core_3d::Transparent3d,
 | 
			
		||||
    ecs::system::{lifetimeless::SRes, SystemParamItem},
 | 
			
		||||
    ecs::system::{
 | 
			
		||||
        lifetimeless::{Read, SRes},
 | 
			
		||||
        SystemParamItem,
 | 
			
		||||
    },
 | 
			
		||||
    pbr::{
 | 
			
		||||
        DrawMesh, MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup,
 | 
			
		||||
        SetMeshViewBindGroup,
 | 
			
		||||
    },
 | 
			
		||||
    prelude::*,
 | 
			
		||||
    render::{
 | 
			
		||||
        extract_component::{ExtractComponent, ExtractComponentPlugin},
 | 
			
		||||
        extract_resource::{ExtractResource, ExtractResourcePlugin},
 | 
			
		||||
        mesh::MeshVertexBufferLayout,
 | 
			
		||||
        render_asset::RenderAssets,
 | 
			
		||||
        render_phase::{
 | 
			
		||||
@ -64,6 +69,8 @@ impl Plugin for CustomMaterialPlugin {
 | 
			
		||||
            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
 | 
			
		||||
            mapped_at_creation: false,
 | 
			
		||||
        });
 | 
			
		||||
        app.add_plugin(ExtractComponentPlugin::<CustomMaterial>::default())
 | 
			
		||||
            .add_plugin(ExtractResourcePlugin::<ExtractedTime>::default());
 | 
			
		||||
 | 
			
		||||
        app.sub_app_mut(RenderApp)
 | 
			
		||||
            .add_render_command::<Transparent3d, DrawCustom>()
 | 
			
		||||
@ -73,26 +80,20 @@ impl Plugin for CustomMaterialPlugin {
 | 
			
		||||
            })
 | 
			
		||||
            .init_resource::<CustomPipeline>()
 | 
			
		||||
            .init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
 | 
			
		||||
            .add_system_to_stage(RenderStage::Extract, extract_time)
 | 
			
		||||
            .add_system_to_stage(RenderStage::Extract, extract_custom_material)
 | 
			
		||||
            .add_system_to_stage(RenderStage::Prepare, prepare_time)
 | 
			
		||||
            .add_system_to_stage(RenderStage::Queue, queue_custom)
 | 
			
		||||
            .add_system_to_stage(RenderStage::Queue, queue_time_bind_group);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// extract the `CustomMaterial` component into the render world
 | 
			
		||||
fn extract_custom_material(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    mut previous_len: Local<usize>,
 | 
			
		||||
    mut query: Query<Entity, With<CustomMaterial>>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut values = Vec::with_capacity(*previous_len);
 | 
			
		||||
    for entity in query.iter_mut() {
 | 
			
		||||
        values.push((entity, (CustomMaterial,)));
 | 
			
		||||
impl ExtractComponent for CustomMaterial {
 | 
			
		||||
    type Query = Read<CustomMaterial>;
 | 
			
		||||
 | 
			
		||||
    type Filter = ();
 | 
			
		||||
 | 
			
		||||
    fn extract_component(_: bevy::ecs::query::QueryItem<Self::Query>) -> Self {
 | 
			
		||||
        CustomMaterial
 | 
			
		||||
    }
 | 
			
		||||
    *previous_len = values.len();
 | 
			
		||||
    commands.insert_or_spawn_batch(values);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// add each entity with a mesh and a `CustomMaterial` to every view's `Transparent3d` render phase using the `CustomPipeline`
 | 
			
		||||
@ -138,11 +139,14 @@ struct ExtractedTime {
 | 
			
		||||
    seconds_since_startup: f32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// extract the passed time into a resource in the render world
 | 
			
		||||
fn extract_time(mut commands: Commands, time: Res<Time>) {
 | 
			
		||||
    commands.insert_resource(ExtractedTime {
 | 
			
		||||
        seconds_since_startup: time.seconds_since_startup() as f32,
 | 
			
		||||
    });
 | 
			
		||||
impl ExtractResource for ExtractedTime {
 | 
			
		||||
    type Source = Time;
 | 
			
		||||
 | 
			
		||||
    fn extract_resource(time: &Self::Source) -> Self {
 | 
			
		||||
        ExtractedTime {
 | 
			
		||||
            seconds_since_startup: time.seconds_since_startup() as f32,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct TimeMeta {
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ use bevy::{
 | 
			
		||||
    math::{DVec2, DVec3},
 | 
			
		||||
    pbr::{ExtractedPointLight, GlobalLightMeta},
 | 
			
		||||
    prelude::*,
 | 
			
		||||
    render::{camera::ScalingMode, RenderApp, RenderStage},
 | 
			
		||||
    render::{camera::ScalingMode, Extract, RenderApp, RenderStage},
 | 
			
		||||
    window::PresentMode,
 | 
			
		||||
};
 | 
			
		||||
use rand::{thread_rng, Rng};
 | 
			
		||||
@ -156,7 +156,7 @@ impl Plugin for LogVisibleLights {
 | 
			
		||||
 | 
			
		||||
// System for printing the number of meshes on every tick of the timer
 | 
			
		||||
fn print_visible_light_count(
 | 
			
		||||
    time: Res<Time>,
 | 
			
		||||
    time: Res<ExtractedTime>,
 | 
			
		||||
    mut timer: Local<PrintingTimer>,
 | 
			
		||||
    visible: Query<&ExtractedPointLight>,
 | 
			
		||||
    global_light_meta: Res<GlobalLightMeta>,
 | 
			
		||||
@ -172,8 +172,11 @@ fn print_visible_light_count(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_time(mut commands: Commands, time: Res<Time>) {
 | 
			
		||||
    commands.insert_resource(time.into_inner().clone());
 | 
			
		||||
#[derive(Deref, DerefMut)]
 | 
			
		||||
pub struct ExtractedTime(Time);
 | 
			
		||||
 | 
			
		||||
fn extract_time(mut commands: Commands, time: Extract<Res<Time>>) {
 | 
			
		||||
    commands.insert_resource(ExtractedTime(time.clone()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct PrintingTimer(Timer);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user