diff --git a/Cargo.toml b/Cargo.toml index 4f032612a2dee..52faa3bc76ae7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -303,6 +303,9 @@ bevy_post_process = ["bevy_internal/bevy_post_process"] # Provides various anti aliasing solutions bevy_anti_alias = ["bevy_internal/bevy_anti_alias"] +# Adds extract +bevy_extract = ["bevy_internal/bevy_extract"] + # Adds gamepad support bevy_gilrs = ["gamepad", "bevy_internal/bevy_gilrs"] @@ -748,6 +751,7 @@ bytemuck = "1" bevy_animation = { path = "crates/bevy_animation", version = "0.19.0-dev", default-features = false } bevy_asset = { path = "crates/bevy_asset", version = "0.19.0-dev", default-features = false } bevy_ecs = { path = "crates/bevy_ecs", version = "0.19.0-dev", default-features = false } +bevy_extract = { path = "crates/bevy_extract", version = "0.19.0-dev", default-features = false } bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.19.0-dev", default-features = false } bevy_image = { path = "crates/bevy_image", version = "0.19.0-dev", default-features = false } bevy_reflect = { path = "crates/bevy_reflect", version = "0.19.0-dev", default-features = false } diff --git a/_release-content/migration-guides/extract-extract-a.md b/_release-content/migration-guides/extract-extract-a.md new file mode 100644 index 0000000000000..c8a818d2f4a7c --- /dev/null +++ b/_release-content/migration-guides/extract-extract-a.md @@ -0,0 +1,10 @@ +--- +title: Extract Extract +pull_requests: [] +--- + +Extraction used to be specific of Main World to Render World, but will now be generic + +- Use `TemporaryRenderEntity::default()` instead of `TemporaryRenderEntity` + +NOTE: more to come diff --git a/_release-content/migration-guides/extract-extract-b.md b/_release-content/migration-guides/extract-extract-b.md new file mode 100644 index 0000000000000..1ec51aa8f532a --- /dev/null +++ b/_release-content/migration-guides/extract-extract-b.md @@ -0,0 +1,30 @@ +--- +title: Extract Extract +pull_requests: [] +--- + +Extraction used to be specific of Main World to Render World, but will now be generic + +- When using traits, specify the `AppLabel`, e.g. `SyncComponent`, `ExtractComponent` + +Before: + +```rust,ignore +impl SyncComponent for TemporalAntiAliasing { ... } + +#[derive(Component, ExtractComponent)] +#[extract_app(RenderApp)] +pub struct Foo { ... } +``` + +After: + +```rust,ignore +impl SyncComponent for TemporalAntiAliasing { ... } + +#[derive(Component, ExtractComponent)] +#[extract_app(RenderApp)] +pub struct Foo { ... } +``` + +NOTE: more to come diff --git a/_release-content/migration-guides/extract-extract-e.md b/_release-content/migration-guides/extract-extract-e.md new file mode 100644 index 0000000000000..5c6672340c02a --- /dev/null +++ b/_release-content/migration-guides/extract-extract-e.md @@ -0,0 +1,12 @@ +--- +title: Extract Extract +pull_requests: [] +--- + +Extraction used to be specific of Main World to Render World, but will now be generic + +All above has moved to new crate `bevy_extract`. + +Most extraction parts are re-exported by `bevy_render` , but some migrations are needed + +NOTE: more to come diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 13b9d229fabac..7618e25a5280c 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -17,6 +17,7 @@ seq-macro = "0.3.6" # Bevy crates bevy_app = { path = "../crates/bevy_app" } bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] } +bevy_extract = { path = "../crates/bevy_extract" } bevy_math = { path = "../crates/bevy_math" } bevy_picking = { path = "../crates/bevy_picking", features = ["mesh_picking"] } bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] } diff --git a/benches/benches/bevy_render/extract_render_asset.rs b/benches/benches/bevy_render/extract_render_asset.rs index 350de956b4e75..fdb6c92359b33 100644 --- a/benches/benches/bevy_render/extract_render_asset.rs +++ b/benches/benches/bevy_render/extract_render_asset.rs @@ -1,11 +1,11 @@ use bevy_app::{App, AppLabel}; use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, Assets, RenderAssetUsages}; -use bevy_ecs::prelude::*; +use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use bevy_reflect::TypePath; use bevy_render::{ extract_plugin::ExtractPlugin, render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, - RenderApp, + Render, RenderApp, RenderSystems, }; use criterion::{criterion_group, BenchmarkId, Criterion, Throughput}; use std::time::{Duration, Instant}; @@ -33,6 +33,8 @@ impl RenderAsset for DummyRenderAsset { } } +pub(crate) fn pre_extract(_main_world: &mut World, _render_world: &mut World) {} + fn extract_render_asset_bench(c: &mut Criterion) { let mut group = c.benchmark_group("extract_render_asset"); @@ -45,7 +47,13 @@ fn extract_render_asset_bench(c: &mut Criterion) { app.add_plugins(bevy_asset::AssetPlugin::default()); app.init_asset::(); - app.add_plugins(ExtractPlugin::default()); + app.add_plugins(ExtractPlugin::::new( + pre_extract, + Render::base_schedule, + Render.intern(), + RenderSystems::ExtractCommands.intern(), + RenderSystems::PostCleanup.intern(), + )); app.add_plugins(RenderAssetPlugin::::default()); app.finish(); @@ -80,7 +88,7 @@ fn extract_render_asset_bench(c: &mut Criterion) { // Measuring the extract call let start = Instant::now(); - bevy_render::extract_plugin::extract(main.world_mut(), render_world); + bevy_extract::extract_plugin::extract(main.world_mut(), render_world); total += Instant::now().duration_since(start); // Run a standard app update to allow Bevy's internal systems to flush/clear the message queues. diff --git a/crates/bevy_anti_alias/Cargo.toml b/crates/bevy_anti_alias/Cargo.toml index 53567c8390439..df4c8cd00cdc1 100644 --- a/crates/bevy_anti_alias/Cargo.toml +++ b/crates/bevy_anti_alias/Cargo.toml @@ -29,6 +29,7 @@ bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } bevy_shader = { path = "../bevy_shader", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.19.0-dev" } diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs index da7153f049a27..ed1cb62595a47 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs @@ -75,11 +75,11 @@ pub struct CasUniform { sharpness: f32, } -impl SyncComponent for ContrastAdaptiveSharpening { +impl SyncComponent for ContrastAdaptiveSharpening { type Target = (DenoiseCas, CasUniform); } -impl ExtractComponent for ContrastAdaptiveSharpening { +impl ExtractComponent for ContrastAdaptiveSharpening { type QueryData = &'static Self; type QueryFilter = With; type Out = (DenoiseCas, CasUniform); diff --git a/crates/bevy_anti_alias/src/fxaa/mod.rs b/crates/bevy_anti_alias/src/fxaa/mod.rs index aa44af488358c..b3f7187ea70e1 100644 --- a/crates/bevy_anti_alias/src/fxaa/mod.rs +++ b/crates/bevy_anti_alias/src/fxaa/mod.rs @@ -54,6 +54,7 @@ impl Sensitivity { #[reflect(Component, Default, Clone)] #[extract_component_filter(With)] #[doc(alias = "FastApproximateAntiAliasing")] +#[extract_app(RenderApp)] pub struct Fxaa { /// Enable render passes for FXAA. pub enabled: bool, diff --git a/crates/bevy_anti_alias/src/smaa/mod.rs b/crates/bevy_anti_alias/src/smaa/mod.rs index dde46f1bf1379..76c44956ee40c 100644 --- a/crates/bevy_anti_alias/src/smaa/mod.rs +++ b/crates/bevy_anti_alias/src/smaa/mod.rs @@ -91,6 +91,7 @@ pub struct SmaaPlugin; ViewSmaaPipelines, ))] #[doc(alias = "SubpixelMorphologicalAntiAliasing")] +#[extract_app(RenderApp)] pub struct Smaa { /// A predefined set of SMAA parameters: i.e. a quality level. /// diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index b2dcad09a6d5f..98940e9dc3dce 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -129,7 +129,7 @@ impl Default for TemporalAntiAliasing { } } -impl SyncComponent for TemporalAntiAliasing { +impl SyncComponent for TemporalAntiAliasing { type Target = Self; } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 2a0add491197b..1235d82597f23 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1950,7 +1950,7 @@ mod tests { fn test_extract_sees_changes() { use super::AppLabel; - #[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)] + #[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug, Default)] struct MySubApp; #[derive(Resource)] diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 2d254666c676b..347f87986fd16 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -26,14 +26,15 @@ type ExtractFn = Box; /// # Example /// /// ``` -/// # use bevy_app::{App, AppLabel, SubApp, Main}; +/// # use bevy_app::{App, SubApp, Main}; +/// # use bevy_derive::AppLabel; /// # use bevy_ecs::prelude::*; /// # use bevy_ecs::schedule::ScheduleLabel; /// /// #[derive(Resource, Default)] /// struct Val(pub i32); /// -/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] +/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel, Default)] /// struct ExampleApp; /// /// // Create an app with a certain resource. diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 639a8b30dff4c..e19347e3d51e4 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -22,6 +22,7 @@ bevy_color = { path = "../bevy_color", version = "0.19.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } bevy_log = { path = "../bevy_log", version = "0.19.0-dev" } bevy_light = { path = "../bevy_light", version = "0.19.0-dev" } diff --git a/crates/bevy_core_pipeline/src/fullscreen_material.rs b/crates/bevy_core_pipeline/src/fullscreen_material.rs index cd2905daa3ed0..e7ea98a6dad02 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_material.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_material.rs @@ -75,7 +75,7 @@ impl Plugin for FullscreenMaterialPlugin { /// A trait to define a material that will render to the entire screen using a fullscreen triangle. pub trait FullscreenMaterial: - Component + ExtractComponent + Clone + Copy + ShaderType + WriteInto + Default + Component + ExtractComponent + Clone + Copy + ShaderType + WriteInto + Default { /// The shader that will run on the entire screen using a fullscreen triangle. fn fragment_shader() -> ShaderRef; diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 993631783c2e5..6ea2c71e571cb 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -39,6 +39,7 @@ pub mod resolve; #[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType, Component)] #[extract_component_sync_target((Self, OrderIndependentTransparencySettingsOffset, OitResolvePipelineId))] #[reflect(Clone, Default)] +#[extract_app(RenderApp)] pub struct OrderIndependentTransparencySettings { /// Controls how many fragments will be exactly sorted. /// If the scene has more fragments than this, they will be merged approximately. diff --git a/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs b/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs index 06f5566aee39f..3a76391f8bb98 100644 --- a/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs +++ b/crates/bevy_core_pipeline/src/prepass/background_motion_vectors.rs @@ -56,11 +56,11 @@ use crate::{ #[reflect(Component, Default, Clone)] pub struct NoBackgroundMotionVectors; -impl SyncComponent for NoBackgroundMotionVectors { +impl SyncComponent for NoBackgroundMotionVectors { type Target = Self; } -impl ExtractComponent for NoBackgroundMotionVectors { +impl ExtractComponent for NoBackgroundMotionVectors { type QueryData = Read; type QueryFilter = (); type Out = Self; diff --git a/crates/bevy_core_pipeline/src/skybox/mod.rs b/crates/bevy_core_pipeline/src/skybox/mod.rs index 2b74db367892d..f29f375fbcfa1 100644 --- a/crates/bevy_core_pipeline/src/skybox/mod.rs +++ b/crates/bevy_core_pipeline/src/skybox/mod.rs @@ -59,7 +59,7 @@ impl Plugin for SkyboxPlugin { } } -impl SyncComponent for Skybox { +impl SyncComponent for Skybox { type Target = (Self, SkyboxUniforms, SkyboxPipelineId, SkyboxBindGroup); } diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index a100635d7227c..52ccbde036f0b 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -33,6 +33,7 @@ use crate::FullscreenShader; /// 3D LUT (look up table) textures used for tonemapping #[derive(Resource, Clone, ExtractResource)] +#[extract_app(RenderApp)] pub struct TonemappingLuts { pub blender_filmic: Handle, pub agx: Handle, @@ -116,6 +117,7 @@ pub struct TonemappingPipeline { )] #[extract_component_filter(With)] #[reflect(Component, Debug, Hash, Default, PartialEq)] +#[extract_app(RenderApp)] pub enum Tonemapping { /// Bypass tonemapping. None, @@ -377,6 +379,7 @@ pub fn prepare_view_tonemapping_pipelines( )] #[extract_component_filter(With)] #[reflect(Component, Debug, Hash, Default, PartialEq)] +#[extract_app(RenderApp)] pub enum DebandDither { #[default] Disabled, diff --git a/crates/bevy_dev_tools/Cargo.toml b/crates/bevy_dev_tools/Cargo.toml index cb8c575f73554..799e02193d056 100644 --- a/crates/bevy_dev_tools/Cargo.toml +++ b/crates/bevy_dev_tools/Cargo.toml @@ -30,6 +30,7 @@ bevy_color = { path = "../bevy_color", version = "0.19.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } bevy_input = { path = "../bevy_input", version = "0.19.0-dev" } bevy_log = { path = "../bevy_log", version = "0.19.0-dev" } diff --git a/crates/bevy_dev_tools/src/render_debug.rs b/crates/bevy_dev_tools/src/render_debug.rs index 7fbecce018ccb..9f70cb2e789f3 100644 --- a/crates/bevy_dev_tools/src/render_debug.rs +++ b/crates/bevy_dev_tools/src/render_debug.rs @@ -272,6 +272,7 @@ pub enum RenderDebugOverlayEvent { /// Overwrites the default [`GlobalRenderDebugOverlay`] resource. #[derive(Component, Clone, ExtractComponent, Reflect, PartialEq)] #[reflect(Component, Default)] +#[extract_app(RenderApp)] pub struct RenderDebugOverlay { /// Enables or disables drawing the overlay. pub enabled: bool, @@ -295,6 +296,7 @@ impl Default for RenderDebugOverlay { /// Can be overwritten by using a [`RenderDebugOverlay`] component. #[derive(Resource, Clone, ExtractResource, ExtractComponent, Reflect, PartialEq)] #[reflect(Resource, Default)] +#[extract_app(RenderApp)] pub struct GlobalRenderDebugOverlay { /// Enables or disables drawing the overlay. pub enabled: bool, diff --git a/crates/bevy_extract/Cargo.toml b/crates/bevy_extract/Cargo.toml new file mode 100644 index 0000000000000..6a8eafe2fb0c5 --- /dev/null +++ b/crates/bevy_extract/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "bevy_extract" +version = "0.19.0-dev" +edition = "2024" +description = "Provides extract functionality for Bevy Engine" +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[features] +default = [] +trace = [] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.19.0-dev" } +bevy_camera = { path = "../bevy_camera", version = "0.19.0-dev" } +bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract_macros = { path = "../bevy_extract/macros", version = "0.19.0-dev" } +bevy_log = { path = "../bevy_log", version = "0.19.0-dev" } +bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev" } +bevy_time = { path = "../bevy_time", version = "0.19.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.19.0-dev" } + +# other +# downcast-rs = { version = "2", default-features = false } + +[dev-dependencies] +# crossbeam-channel = "0.5.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_extract/LICENSE-APACHE b/crates/bevy_extract/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_extract/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_extract/LICENSE-MIT b/crates/bevy_extract/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_extract/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_extract/README.md b/crates/bevy_extract/README.md new file mode 100644 index 0000000000000..53c782c8d241f --- /dev/null +++ b/crates/bevy_extract/README.md @@ -0,0 +1,9 @@ +# Bevy App + +[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license) +[![Crates.io](https://img.shields.io/crates/v/bevy.svg)](https://crates.io/crates/bevy_extract) +[![Downloads](https://img.shields.io/crates/d/bevy_extract.svg)](https://crates.io/crates/bevy_extract) +[![Docs](https://docs.rs/bevy_extract/badge.svg)](https://docs.rs/bevy_extract/latest/bevy_extract/) +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) + +This crate makes it easy to copy out main world state to a sub app's world. diff --git a/crates/bevy_extract/macros/Cargo.toml b/crates/bevy_extract/macros/Cargo.toml new file mode 100644 index 0000000000000..8fdb02d6bd117 --- /dev/null +++ b/crates/bevy_extract/macros/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "bevy_extract_macros" +version = "0.19.0-dev" +edition = "2024" +description = "Derive implementations for bevy_extract" +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[lib] +proc-macro = true + +[dependencies] +bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.19.0-dev" } + +syn = { version = "2.0", features = ["full"] } +proc-macro2 = "1.0" +quote = "1.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_extract/macros/LICENSE-APACHE b/crates/bevy_extract/macros/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_extract/macros/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_extract/macros/LICENSE-MIT b/crates/bevy_extract/macros/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_extract/macros/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/bevy_render/macros/src/extract_component.rs b/crates/bevy_extract/macros/src/extract_component.rs similarity index 69% rename from crates/bevy_render/macros/src/extract_component.rs rename to crates/bevy_extract/macros/src/extract_component.rs index 0a672b0d7ef54..2dd6410ed84bc 100644 --- a/crates/bevy_render/macros/src/extract_component.rs +++ b/crates/bevy_extract/macros/src/extract_component.rs @@ -5,7 +5,7 @@ use syn::{parse_macro_input, parse_quote, DeriveInput, Path}; pub fn derive_extract_component(input: TokenStream) -> TokenStream { let mut ast = parse_macro_input!(input as DeriveInput); - let bevy_render_path: Path = crate::bevy_render_path(); + let bevy_extract_path: Path = crate::bevy_extract_path(); let bevy_ecs_path: Path = bevy_macro_utils::BevyManifest::shared(|manifest| { manifest .maybe_get_path("bevy_ecs") @@ -20,6 +20,21 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let app_label = match ast.attrs.iter().find(|a| a.path().is_ident("extract_app")) { + Some(attr) => match attr.parse_args::() { + Ok(label) => label, + Err(e) => return e.to_compile_error().into(), + }, + None => { + return syn::Error::new_spanned( + &ast.ident, + "ExtractComponent requires #[extract_app(MyAppLabel)] to specify the target sub-app", + ) + .to_compile_error() + .into(); + } + }; + let filter = if let Some(attr) = ast .attrs .iter() @@ -59,12 +74,13 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream { }; TokenStream::from(quote! { - impl #impl_generics #bevy_render_path::sync_component::SyncComponent for #struct_name #type_generics #where_clause { + impl #impl_generics #bevy_extract_path::sync_component::SyncComponent<#app_label> for #struct_name #type_generics #where_clause { type Target = #sync_target; } - impl #impl_generics #bevy_render_path::extract_component::ExtractComponent for #struct_name #type_generics #where_clause { + impl #impl_generics #bevy_extract_path::extract_component::ExtractComponent<#app_label> for #struct_name #type_generics #where_clause { type QueryData = &'static Self; + type QueryFilter = #filter; type Out = Self; diff --git a/crates/bevy_extract/macros/src/extract_resource.rs b/crates/bevy_extract/macros/src/extract_resource.rs new file mode 100644 index 0000000000000..7fe1b886e5200 --- /dev/null +++ b/crates/bevy_extract/macros/src/extract_resource.rs @@ -0,0 +1,42 @@ +use bevy_macro_utils::fq_std::FQClone; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, parse_quote, DeriveInput, Path}; + +pub fn derive_extract_resource(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_extract_path: Path = crate::bevy_extract_path(); + + ast.generics + .make_where_clause() + .predicates + .push(parse_quote! { Self: #FQClone }); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + let app_label = match ast.attrs.iter().find(|a| a.path().is_ident("extract_app")) { + Some(attr) => match attr.parse_args::() { + Ok(label) => label, + Err(e) => return e.to_compile_error().into(), + }, + None => { + return syn::Error::new_spanned( + &ast.ident, + "ExtractResource requires #[extract_app(MyAppLabel)] to specify the target sub-app", + ) + .to_compile_error() + .into(); + } + }; + + TokenStream::from(quote! { + impl #impl_generics #bevy_extract_path::extract_resource::ExtractResource<#app_label> for #struct_name #type_generics #where_clause { + type Source = Self; + + fn extract_resource(source: &Self::Source) -> Self { + source.clone() + } + } + }) +} diff --git a/crates/bevy_extract/macros/src/lib.rs b/crates/bevy_extract/macros/src/lib.rs new file mode 100644 index 0000000000000..618038b362a1f --- /dev/null +++ b/crates/bevy_extract/macros/src/lib.rs @@ -0,0 +1,54 @@ +#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![cfg_attr(docsrs, feature(doc_cfg))] + +mod extract_component; +mod extract_resource; + +use bevy_macro_utils::BevyManifest; +use proc_macro::TokenStream; + +pub(crate) fn bevy_extract_path() -> syn::Path { + BevyManifest::shared(|manifest| manifest.get_path("bevy_extract")) +} + +#[proc_macro_derive(ExtractResource, attributes(extract_app))] +pub fn derive_extract_resource(input: TokenStream) -> TokenStream { + extract_resource::derive_extract_resource(input) +} + +/// Implements `ExtractComponent` trait for a component. +/// +/// The component must implement [`Clone`]. +/// The component will be extracted into the render world via cloning. +/// Note that this only enables extraction of the component, it does not execute the extraction. +/// See `ExtractComponentPlugin` to actually perform the extraction. +/// +/// If you only want to extract a component conditionally, you may use the `extract_component_filter` attribute. +/// To specify `SyncComponent::Target`, you can use the `extract_component_sync_target` attribute. +/// +/// # Example +/// +/// ```no_compile +/// use bevy_ecs::component::Component; +/// use bevy_extract_macros::ExtractComponent; +/// +/// #[derive(Component, Clone, ExtractComponent)] +/// #[extract_component_filter(With)] +/// #[extract_component_sync_target((Self, OtherNeedsCleanup))] +/// pub struct Foo { +/// pub should_foo: bool, +/// } +/// +/// // Without a filter (unconditional). +/// #[derive(Component, Clone, ExtractComponent)] +/// pub struct Bar { +/// pub should_bar: bool, +/// } +/// ``` +#[proc_macro_derive( + ExtractComponent, + attributes(extract_component_filter, extract_component_sync_target, extract_app) +)] +pub fn derive_extract_component(input: TokenStream) -> TokenStream { + extract_component::derive_extract_component(input) +} diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_extract/src/extract_component.rs similarity index 68% rename from crates/bevy_render/src/extract_component.rs rename to crates/bevy_extract/src/extract_component.rs index 4d32b3f369a13..32977e3fc5af2 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_extract/src/extract_component.rs @@ -1,9 +1,9 @@ use crate::{ sync_component::{SyncComponent, SyncComponentPlugin}, - sync_world::RenderEntity, - Extract, ExtractSchedule, RenderApp, + sync_world::SubEntity, + Extract, ExtractSchedule, }; -use bevy_app::{App, Plugin}; +use bevy_app::{App, AppLabel, Plugin}; use bevy_camera::visibility::ViewVisibility; use bevy_ecs::{ bundle::NoBundleEffect, @@ -12,13 +12,11 @@ use bevy_ecs::{ }; use core::marker::PhantomData; -pub use crate::uniform::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}; +pub use bevy_extract_macros::ExtractComponent; -pub use bevy_render_macros::ExtractComponent; - -/// Describes how a component gets extracted for rendering. +/// Describes how a component gets extracted for processing. /// -/// Therefore the component is transferred from the "app world" into the "render +/// Therefore the component is transferred from the "app world" into the "sub /// world" in the [`ExtractSchedule`] step. This functionality is enabled by /// adding [`ExtractComponentPlugin`] with the component type. /// @@ -27,42 +25,42 @@ pub use bevy_render_macros::ExtractComponent; /// The marker type `F` is only used as a way to bypass the orphan rules. To /// implement the trait for a foreign type you can use a local type as the /// marker, e.g. the type of the plugin that calls [`ExtractComponentPlugin`]. -pub trait ExtractComponent: SyncComponent { +pub trait ExtractComponent: SyncComponent { /// ECS [`ReadOnlyQueryData`] to fetch the components to extract. type QueryData: ReadOnlyQueryData; /// Filters the entities with additional constraints. type QueryFilter: QueryFilter; /// The output from extraction, i.e. [`ExtractComponent::extract_component`]. /// - /// The output components won't be removed automatically from the render world if the implementing component is removed, + /// The output components won't be removed automatically from the sub world if the implementing component is removed, /// unless you set them in the [`SyncComponent::Target`]. type Out: Bundle; // TODO: https://github.com/rust-lang/rust/issues/29661 // type Out: Bundle = Self; - /// Defines how the component is transferred into the "render world". + /// Defines how the component is transferred into the "sub world". /// /// Returning `None` based on the queried item will remove the [`SyncComponent::Target`] from the entity in - /// the render world. + /// the sub world. fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } -/// This plugin extracts the components into the render world for synced +/// This plugin extracts the components into the sub world for synced /// entities. To do so, it sets up the [`ExtractSchedule`] step for the /// specified [`ExtractComponent`]. /// -/// It also registers [`SyncComponentPlugin`] to ensure the extracted components +/// It also registers [`SyncComponentPlugin`](`crate::sync_component::SyncComponentPlugin`) to ensure the extracted components /// are deleted if the main world components are removed. /// /// The marker type `F` is only used as a way to bypass the orphan rules. To /// implement the trait for a foreign type you can use a local type as the /// marker, e.g. the type of the plugin that calls [`ExtractComponentPlugin`]. -pub struct ExtractComponentPlugin { +pub struct ExtractComponentPlugin { only_extract_visible: bool, - marker: PhantomData (C, F)>, + marker: PhantomData (L, C, F)>, } -impl Default for ExtractComponentPlugin { +impl Default for ExtractComponentPlugin { fn default() -> Self { Self { only_extract_visible: false, @@ -71,7 +69,7 @@ impl Default for ExtractComponentPlugin { } } -impl ExtractComponentPlugin { +impl ExtractComponentPlugin { pub fn extract_visible() -> Self { Self { only_extract_visible: true, @@ -80,25 +78,30 @@ impl ExtractComponentPlugin { } } -impl, F: 'static + Send + Sync> Plugin for ExtractComponentPlugin { +impl< + L: AppLabel + Default + Clone + Copy + Eq, + C: ExtractComponent, + F: 'static + Send + Sync, + > Plugin for ExtractComponentPlugin +{ fn build(&self, app: &mut App) { - app.add_plugins(SyncComponentPlugin::::default()); + app.add_plugins(SyncComponentPlugin::::default()); - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { + if let Some(sub_app) = app.get_sub_app_mut(L::default()) { if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible_components::); + sub_app.add_systems(ExtractSchedule, extract_visible_components::); } else { - render_app.add_systems(ExtractSchedule, extract_components::); + sub_app.add_systems(ExtractSchedule, extract_components::); } } } } -/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_components, F>( +/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToSubWorld`]. +fn extract_components, F>( mut commands: Commands, mut previous_len: Local, - query: Extract>, + query: Extract, C::QueryData), C::QueryFilter>>, ) { let mut values = Vec::with_capacity(*previous_len); for (entity, query_item) in &query { @@ -112,11 +115,11 @@ fn extract_components, F>( commands.try_insert_batch(values); } -/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`crate::sync_world::SyncToRenderWorld`]. -fn extract_visible_components, F>( +/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`crate::sync_world::SyncToSubWorld`]. +fn extract_visible_components, F>( mut commands: Commands, mut previous_len: Local, - query: Extract>, + query: Extract, &ViewVisibility, C::QueryData), C::QueryFilter>>, ) { let mut values = Vec::with_capacity(*previous_len); for (entity, view_visibility, query_item) in &query { diff --git a/crates/bevy_render/src/extract_instances.rs b/crates/bevy_extract/src/extract_instances.rs similarity index 58% rename from crates/bevy_render/src/extract_instances.rs rename to crates/bevy_extract/src/extract_instances.rs index d85f8fa646b34..a259492f3ef02 100644 --- a/crates/bevy_render/src/extract_instances.rs +++ b/crates/bevy_extract/src/extract_instances.rs @@ -1,12 +1,12 @@ //! Convenience logic for turning components from the main world into extracted -//! instances in the render world. +//! instances in the sub world. //! //! This is essentially the same as the `extract_component` module, but //! higher-performance because it avoids the ECS overhead. use core::marker::PhantomData; -use bevy_app::{App, Plugin}; +use bevy_app::{App, AppLabel, Plugin}; use bevy_camera::visibility::ViewVisibility; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -17,62 +17,66 @@ use bevy_ecs::{ }; use crate::sync_world::MainEntityHashMap; -use crate::{Extract, ExtractSchedule, RenderApp}; +use crate::{Extract, ExtractSchedule}; -/// Describes how to extract data needed for rendering from a component or +/// Describes how to extract data needed for processing from a component or /// components. /// -/// Before rendering, any applicable components will be transferred from the -/// main world to the render world in the [`ExtractSchedule`] step. +/// Before processing, any applicable components will be transferred from the +/// main world to the sub world in the [`ExtractSchedule`] step. /// /// This is essentially the same as /// [`ExtractComponent`](crate::extract_component::ExtractComponent), but /// higher-performance because it avoids the ECS overhead. -pub trait ExtractInstance: Send + Sync + Sized + 'static { +pub trait ExtractInstance: Send + Sync + Sized + 'static { /// ECS [`ReadOnlyQueryData`] to fetch the components to extract. type QueryData: ReadOnlyQueryData; /// Filters the entities with additional constraints. type QueryFilter: QueryFilter; - /// Defines how the component is transferred into the "render world". + /// Defines how the component is transferred into the "sub world". fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option; } -/// This plugin extracts one or more components into the "render world" as +/// This plugin extracts one or more components into the "sub world" as /// extracted instances. /// /// Therefore it sets up the [`ExtractSchedule`] step for the specified /// [`ExtractedInstances`]. #[derive(Default)] -pub struct ExtractInstancesPlugin +pub struct ExtractInstancesPlugin where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { only_extract_visible: bool, - marker: PhantomData EI>, + marker: PhantomData (L, EI)>, } -/// Stores all extract instances of a type in the render world. +/// Stores all extract instances of a type in the sub world. #[derive(Resource, Deref, DerefMut)] -pub struct ExtractedInstances(MainEntityHashMap) +pub struct ExtractedInstances(#[deref] MainEntityHashMap, PhantomData) where - EI: ExtractInstance; + L: AppLabel, + EI: ExtractInstance; -impl Default for ExtractedInstances +impl Default for ExtractedInstances where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { fn default() -> Self { - Self(Default::default()) + Self(Default::default(), PhantomData) } } -impl ExtractInstancesPlugin +impl ExtractInstancesPlugin where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { /// Creates a new [`ExtractInstancesPlugin`] that unconditionally extracts to - /// the render world, whether the entity is visible or not. + /// the sub world, whether the entity is visible or not. pub fn new() -> Self { Self { only_extract_visible: false, @@ -80,7 +84,7 @@ where } } - /// Creates a new [`ExtractInstancesPlugin`] that extracts to the render world + /// Creates a new [`ExtractInstancesPlugin`] that extracts to the sub world /// if and only if the entity it's attached to is visible. pub fn extract_visible() -> Self { Self { @@ -90,27 +94,29 @@ where } } -impl Plugin for ExtractInstancesPlugin +impl Plugin for ExtractInstancesPlugin where - EI: ExtractInstance, + L: AppLabel + Default, + EI: ExtractInstance, { fn build(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::>(); + if let Some(sub_app) = app.get_sub_app_mut(L::default()) { + sub_app.init_resource::>(); if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible::); + sub_app.add_systems(ExtractSchedule, extract_visible::); } else { - render_app.add_systems(ExtractSchedule, extract_all::); + sub_app.add_systems(ExtractSchedule, extract_all::); } } } } -fn extract_all( - mut extracted_instances: ResMut>, +fn extract_all( + mut extracted_instances: ResMut>, query: Extract>, ) where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { extracted_instances.clear(); for (entity, other) in &query { @@ -120,11 +126,12 @@ fn extract_all( } } -fn extract_visible( - mut extracted_instances: ResMut>, +fn extract_visible( + mut extracted_instances: ResMut>, query: Extract>, ) where - EI: ExtractInstance, + L: AppLabel, + EI: ExtractInstance, { extracted_instances.clear(); for (entity, view_visibility, other) in &query { diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_extract/src/extract_param.rs similarity index 90% rename from crates/bevy_render/src/extract_param.rs rename to crates/bevy_extract/src/extract_param.rs index 0659cec965403..a31cfcc9acdd1 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_extract/src/extract_param.rs @@ -23,8 +23,8 @@ use core::ops::{Deref, DerefMut}; /// ## Context /// /// [`ExtractSchedule`] 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 +/// sub world. The sub world drives processing each frame (generally to a `Window`). +/// This design is used to allow performing calculations related to processing 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 [`ExtractSchedule`]. @@ -33,12 +33,15 @@ use core::ops::{Deref, DerefMut}; /// /// ``` /// use bevy_ecs::prelude::*; -/// use bevy_render::Extract; -/// use bevy_render::sync_world::RenderEntity; +/// use bevy_extract::Extract; +/// use bevy_extract::sync_world::SubEntity; +/// use bevy_derive::AppLabel; +/// # #[derive(AppLabel, Debug, Hash, PartialEq, Eq, Clone, Default, Copy)] +/// # struct ExtractApp; /// # #[derive(Component)] /// // Do make sure to sync the cloud entities before extracting them. /// # struct Cloud; -/// fn extract_clouds(mut commands: Commands, clouds: Extract>>) { +/// fn extract_clouds(mut commands: Commands, clouds: Extract, With>>) { /// for cloud in &clouds { /// commands.entity(cloud).insert(Cloud); /// } @@ -46,7 +49,7 @@ use core::ops::{Deref, DerefMut}; /// ``` /// /// [`ExtractSchedule`]: crate::ExtractSchedule -/// [Window]: bevy_window::Window +/// [`Window`]: https://docs.rs/bevy/latest/bevy/prelude/struct.Window.html pub struct Extract<'w, 's, P> where P: ReadOnlySystemParam + 'static, diff --git a/crates/bevy_render/src/extract_plugin.rs b/crates/bevy_extract/src/extract_plugin.rs similarity index 57% rename from crates/bevy_render/src/extract_plugin.rs rename to crates/bevy_extract/src/extract_plugin.rs index 40b5f801bf982..dd99132346960 100644 --- a/crates/bevy_render/src/extract_plugin.rs +++ b/crates/bevy_extract/src/extract_plugin.rs @@ -1,100 +1,121 @@ -use crate::{ - sync_world::{despawn_temporary_render_entities, entity_sync_system, SyncWorldPlugin}, - Render, RenderApp, RenderSystems, -}; -use bevy_app::{App, Plugin, SubApp}; +use core::marker::PhantomData; + +use crate::sync_world::{despawn_temporary_entities, entity_sync_system, SyncWorldPlugin}; +use bevy_app::{App, AppLabel, Plugin, SubApp}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ resource::Resource, - schedule::{IntoScheduleConfigs, Schedule, ScheduleBuildSettings, ScheduleLabel, Schedules}, + schedule::{ + InternedScheduleLabel, InternedSystemSet, IntoScheduleConfigs, Schedule, + ScheduleBuildSettings, ScheduleLabel, Schedules, + }, world::{Mut, World}, }; use bevy_utils::default; -/// Plugin that sets up the [`RenderApp`] and handles extracting data from the -/// main world to the render world. -pub struct ExtractPlugin { +/// Plugin that sets up the sub app for the [`AppLabel`] and handles extracting data from the +/// main world to the sub world. +pub struct ExtractPlugin { /// Function that gets run at the beginning of each extraction. /// - /// Gets the main world and render world as arguments (in that order). + /// Gets the main world and sub world as arguments (in that order). pub pre_extract: fn(&mut World, &mut World), + + marker: PhantomData, + + pub base_schedule: fn() -> Schedule, + pub schedule_label: InternedScheduleLabel, + + pub extract_set: InternedSystemSet, + pub despawn_set: InternedSystemSet, } -impl Default for ExtractPlugin { - fn default() -> Self { +impl ExtractPlugin { + pub fn new( + pre_extract: fn(&mut World, &mut World), + base_schedule: fn() -> Schedule, + schedule_label: InternedScheduleLabel, + extract_set: InternedSystemSet, + despawn_set: InternedSystemSet, + ) -> Self { Self { - pre_extract: |_, _| {}, + pre_extract, + marker: PhantomData, + base_schedule, + schedule_label, + extract_set, + despawn_set, } } } -impl Plugin for ExtractPlugin { +impl Plugin for ExtractPlugin { fn build(&self, app: &mut App) { - app.add_plugins(SyncWorldPlugin); + app.add_plugins(SyncWorldPlugin::::default()); app.init_resource::(); - let mut render_app = SubApp::new(); + let mut sub_app = SubApp::new(); let mut extract_schedule = Schedule::new(ExtractSchedule); // We skip applying any commands during the ExtractSchedule - // so commands can be applied on the render thread. + // so commands can be applied on the sub thread. extract_schedule.set_build_settings(ScheduleBuildSettings { auto_insert_apply_deferred: false, ..default() }); extract_schedule.set_apply_final_deferred(false); - render_app - .add_schedule(Render::base_schedule()) + sub_app + .add_schedule((self.base_schedule)()) .add_schedule(extract_schedule) .allow_ambiguous_resource::() .add_systems( - Render, + self.schedule_label, ( - // This set applies the commands from the extract schedule while the render schedule + // This set applies the commands from the extract schedule while the sub schedule // is running in parallel with the main app. - apply_extract_commands.in_set(RenderSystems::ExtractCommands), - despawn_temporary_render_entities.in_set(RenderSystems::PostCleanup), + apply_extract_commands.in_set(self.extract_set), + despawn_temporary_entities::.in_set(self.despawn_set), ), ); let pre_extract = self.pre_extract; - render_app.set_extract(move |main_world, render_world| { - pre_extract(main_world, render_world); + sub_app.set_extract(move |main_world, sub_world| { + pre_extract(main_world, sub_world); { #[cfg(feature = "trace")] let _stage_span = bevy_log::info_span!("entity_sync").entered(); - entity_sync_system(main_world, render_world); + entity_sync_system::(main_world, sub_world); } // run extract schedule - extract(main_world, render_world); + extract(main_world, sub_world); }); - app.insert_sub_app(RenderApp, render_app); + app.insert_sub_app(L::default(), sub_app); } } -/// Schedule in which data from the main world is 'extracted' into the render world. +/// Schedule in which data from the main world is 'extracted' into the sub world. /// /// This step should be kept as short as possible to increase the "pipelining potential" for -/// running the next frame while rendering the current frame. +/// running the next frame while processing the current frame. /// -/// This schedule is run on the render world, but it also has access to the main world. +/// This schedule is run on the sub world, but it also has access to the main world. /// See [`MainWorld`] and [`Extract`](crate::Extract) for details on how to access main world data from this schedule. #[derive(ScheduleLabel, PartialEq, Eq, Debug, Clone, Hash, Default)] pub struct ExtractSchedule; /// Applies the commands from the extract schedule. This happens during -/// the render schedule rather than during extraction to allow the commands to run in parallel with the -/// main app when pipelined rendering is enabled. -fn apply_extract_commands(render_world: &mut World) { - render_world.resource_scope(|render_world, mut schedules: Mut| { +/// the sub schedule rather than during extraction to allow the commands to run in parallel with the +/// main app when pipelined processing is enabled. +fn apply_extract_commands(sub_world: &mut World) { + sub_world.resource_scope(|sub_world, mut schedules: Mut| { schedules .get_mut(ExtractSchedule) .unwrap() - .apply_deferred(render_world); + .apply_deferred(sub_world); }); } /// The simulation [`World`] of the application, stored as a resource. @@ -110,17 +131,17 @@ pub struct MainWorld(World); #[derive(Resource, Default)] struct ScratchMainWorld(World); -/// Executes the [`ExtractSchedule`] step of the renderer. -/// This updates the render world with the extracted ECS data of the current frame. -pub fn extract(main_world: &mut World, render_world: &mut World) { - // temporarily add the app world to the render world as a resource +/// Executes the [`ExtractSchedule`] step of the processor. +/// This updates the sub world with the extracted ECS data of the current frame. +pub fn extract(main_world: &mut World, sub_world: &mut World) { + // temporarily add the app world to the sub world as a resource let scratch_world = main_world.remove_resource::().unwrap(); let inserted_world = core::mem::replace(main_world, scratch_world.0); - render_world.insert_resource(MainWorld(inserted_world)); - render_world.run_schedule(ExtractSchedule); + sub_world.insert_resource(MainWorld(inserted_world)); + sub_world.run_schedule(ExtractSchedule); // move the app world back, as if nothing happened. - let inserted_world = render_world.remove_resource::().unwrap(); + let inserted_world = sub_world.remove_resource::().unwrap(); let scratch_world = core::mem::replace(main_world, inserted_world.0); main_world.insert_resource(ScratchMainWorld(scratch_world)); } @@ -128,6 +149,7 @@ pub fn extract(main_world: &mut World, render_world: &mut World) { #[cfg(test)] mod test { use bevy_app::{App, Startup}; + use bevy_derive::AppLabel; use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; use crate::{ @@ -135,9 +157,35 @@ mod test { extract_plugin::ExtractPlugin, sync_component::SyncComponent, sync_world::MainEntity, - Render, RenderApp, }; + #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] + pub enum MyScheduleSystems { + ExtractCommands, + PostCleanup, + } + + #[derive(AppLabel, Debug, Hash, PartialEq, Eq, Clone, Default, Copy)] + pub struct ExtractApp; + + #[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone, Default)] + pub struct MySchedule; + + impl MySchedule { + /// Sets up the base structure of the processing [`Schedule`]. + /// + /// The sets defined in this enum are configured to run in order. + pub fn base_schedule() -> Schedule { + use MyScheduleSystems::*; + + let mut schedule = Schedule::new(Self); + + schedule.configure_sets((ExtractCommands, PostCleanup).chain()); + + schedule + } + } + #[derive(Component, Clone, Debug)] struct RenderComponent; @@ -145,16 +193,17 @@ mod test { struct RenderComponentExtra; #[derive(Component, Clone, Debug, ExtractComponent)] + #[extract_app(ExtractApp)] struct RenderComponentSeparate; #[derive(Component, Clone, Debug)] struct RenderComponentNoExtract; - impl SyncComponent for RenderComponent { + impl SyncComponent for RenderComponent { type Target = (RenderComponent, RenderComponentExtra); } - impl ExtractComponent for RenderComponent { + impl ExtractComponent for RenderComponent { type QueryData = &'static Self; type QueryFilter = (); type Out = (RenderComponent, RenderComponentExtra); @@ -170,20 +219,26 @@ mod test { fn extraction_works() { let mut app = App::new(); - app.add_plugins(ExtractPlugin::default()); - app.add_plugins(ExtractComponentPlugin::::default()); - app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractPlugin::::new( + |_, _| {}, + MySchedule::base_schedule, + MySchedule.intern(), + MyScheduleSystems::ExtractCommands.intern(), + MyScheduleSystems::PostCleanup.intern(), + )); + app.add_plugins(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); app.add_systems(Startup, |mut commands: Commands| { commands.spawn((RenderComponent, RenderComponentSeparate)); }); - let render_app = app.get_sub_app_mut(RenderApp).unwrap(); + let sub_app = app.get_sub_app_mut(ExtractApp).unwrap(); // Normally RenderPlugin sets the RenderRecovery schedule as update, but for // testing we just use the Render schedule directly. - render_app.update_schedule = Some(Render.intern()); + sub_app.update_schedule = Some(MySchedule.intern()); - render_app.world_mut().add_observer( + sub_app.world_mut().add_observer( |event: On, mut commands: Commands| { // Simulate data that's not extracted commands @@ -196,8 +251,8 @@ mod test { // Check that all components have been extracted { - let render_app = app.get_sub_app_mut(RenderApp).unwrap(); - render_app + let sub_app = app.get_sub_app_mut(ExtractApp).unwrap(); + sub_app .world_mut() .run_system_cached( |entity: Single<( @@ -231,8 +286,8 @@ mod test { // Check that the extracted components have been removed { - let render_app = app.get_sub_app_mut(RenderApp).unwrap(); - render_app + let sub_app = app.get_sub_app_mut(ExtractApp).unwrap(); + sub_app .world_mut() .run_system_cached( |entity: Single<( diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_extract/src/extract_resource.rs similarity index 61% rename from crates/bevy_render/src/extract_resource.rs rename to crates/bevy_extract/src/extract_resource.rs index 619a25cb60321..ddbfc26558b44 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_extract/src/extract_resource.rs @@ -1,28 +1,28 @@ use core::marker::PhantomData; -use bevy_app::{App, Plugin}; +use bevy_app::{App, AppLabel, Plugin}; use bevy_ecs::{component::Mutable, prelude::*}; -pub use bevy_render_macros::ExtractResource; +pub use bevy_extract_macros::ExtractResource; use bevy_utils::once; -use crate::{Extract, ExtractSchedule, RenderApp}; +use crate::{Extract, ExtractSchedule}; -/// Describes how a resource gets extracted for rendering. +/// Describes how a resource gets extracted for processing. /// -/// Therefore the resource is transferred from the "main world" into the "render world" +/// Therefore the resource is transferred from the "main world" into the "sub world" /// in the [`ExtractSchedule`] step. /// /// The marker type `F` is only used as a way to bypass the orphan rules. To /// implement the trait for a foreign type you can use a local type as the /// marker, e.g. the type of the plugin that calls [`ExtractResourcePlugin`]. -pub trait ExtractResource: Resource { +pub trait ExtractResource: Resource { type Source: Resource; - /// Defines how the resource is transferred into the "render world". + /// Defines how the resource is transferred into the "sub world". fn extract_resource(source: &Self::Source) -> Self; } -/// This plugin extracts the resources into the "render world". +/// This plugin extracts the resources into the "sub world". /// /// Therefore it sets up the[`ExtractSchedule`] step /// for the specified [`Resource`]. @@ -30,23 +30,28 @@ pub trait ExtractResource: Resource { /// The marker type `F` is only used as a way to bypass the orphan rules. To /// implement the trait for a foreign type you can use a local type as the /// marker, e.g. the type of the plugin that calls [`ExtractResourcePlugin`]. -pub struct ExtractResourcePlugin, F = ()>(PhantomData<(R, F)>); +pub struct ExtractResourcePlugin, F = ()>( + PhantomData<(L, R, F)>, +); -impl, F> Default for ExtractResourcePlugin { +impl, F> Default for ExtractResourcePlugin { fn default() -> Self { Self(PhantomData) } } -impl, F: 'static + Send + Sync> Plugin - for ExtractResourcePlugin +impl< + L: AppLabel + Default, + R: ExtractResource, + F: 'static + Send + Sync, + > Plugin for ExtractResourcePlugin { fn build(&self, app: &mut App) { - if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.add_systems(ExtractSchedule, extract_resource::); + if let Some(sub_app) = app.get_sub_app_mut(L::default()) { + sub_app.add_systems(ExtractSchedule, extract_resource::); } else { once!(bevy_log::error!( - "Render app did not exist when trying to add `extract_resource` for <{}>.", + "Sub app did not exist when trying to add `extract_resource` for <{}>.", core::any::type_name::() )); } @@ -54,7 +59,7 @@ impl, F: 'static + Send + Sync> Plug } /// This system extracts the resource of the corresponding [`Resource`] type -pub fn extract_resource, F>( +pub fn extract_resource, F>( mut commands: Commands, main_resource: Extract>>, target_resource: Option>, @@ -68,7 +73,7 @@ pub fn extract_resource, F>( #[cfg(debug_assertions)] if !main_resource.is_added() { once!(bevy_log::warn!( - "Removing resource {} from render world not expected, adding using `Commands`. + "Removing resource {} from sub world not expected, adding using `Commands`. This may decrease performance", core::any::type_name::() )); diff --git a/crates/bevy_extract/src/lib.rs b/crates/bevy_extract/src/lib.rs new file mode 100644 index 0000000000000..f5644238bec8f --- /dev/null +++ b/crates/bevy_extract/src/lib.rs @@ -0,0 +1,42 @@ +#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![cfg_attr( + any(docsrs, docsrs_dep), + expect( + internal_features, + reason = "rustdoc_internals is needed for fake_variadic" + ) +)] +#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_cfg, rustdoc_internals))] +#![doc( + html_logo_url = "https://bevy.org/assets/icon.png", + html_favicon_url = "https://bevy.org/assets/icon.png" +)] +#![expect(unsafe_code, reason = "Unsafe code is used to improve performance.")] + +//! This crate is about everything concerning extract. + +extern crate alloc; + +pub mod extract_component; +pub mod extract_instances; +pub mod extract_param; +pub mod extract_plugin; +pub mod extract_resource; +pub mod sync_component; +pub mod sync_world; + +pub use extract_param::Extract; +pub use extract_plugin::*; +pub use extract_plugin::{ExtractSchedule, MainWorld}; +pub use sync_world::*; + +// Required to make proc macros work in bevy itself. +extern crate self as bevy_extract; + +/// The extract prelude. +/// +/// This includes the most common types in this crate, re-exported for your convenience. +pub mod prelude { + // #[doc(hidden)] + // pub use crate::{ExtractPlugin, ExtractSchedule}; +} diff --git a/crates/bevy_render/src/sync_component.rs b/crates/bevy_extract/src/sync_component.rs similarity index 52% rename from crates/bevy_render/src/sync_component.rs rename to crates/bevy_extract/src/sync_component.rs index e2c85d470fbfb..b75b6fe4e1dd3 100644 --- a/crates/bevy_render/src/sync_component.rs +++ b/crates/bevy_extract/src/sync_component.rs @@ -1,14 +1,17 @@ use core::marker::PhantomData; -use bevy_app::{App, Plugin}; +use bevy_app::{App, AppLabel, Plugin}; use bevy_ecs::{ bundle::{Bundle, NoBundleEffect}, component::Component, + lifecycle::Remove, + observer::On, + system::ResMut, }; -use crate::sync_world::{EntityRecord, PendingSyncEntity, SyncToRenderWorld}; +use crate::sync_world::{EntityRecord, PendingSyncEntity, SyncToSubWorld}; -/// Plugin that registers a component for automatic sync to the render world. See [`SyncWorldPlugin`] for more information. +/// Plugin that registers a component for automatic sync to the sub world. See [`SyncWorldPlugin`] for more information. /// /// This plugin is automatically added by [`ExtractComponentPlugin`], and only needs to be added for manual extraction implementations. /// @@ -18,47 +21,53 @@ use crate::sync_world::{EntityRecord, PendingSyncEntity, SyncToRenderWorld}; /// /// # Implementation details /// -/// It adds [`SyncToRenderWorld`] as a required component to make the [`SyncWorldPlugin`] aware of the component, and -/// handles cleanup of the component in the render world when it is removed from an entity. +/// It adds [`SyncToSubWorld`] as a required component to make the [`SyncWorldPlugin`] aware of the component, and +/// handles cleanup of the component in the sub world when it is removed from an entity. /// /// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin /// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin -pub struct SyncComponentPlugin(PhantomData<(C, F)>); +pub struct SyncComponentPlugin(PhantomData<(L, C, F)>); -impl, F> Default for SyncComponentPlugin { +impl, F> Default for SyncComponentPlugin { fn default() -> Self { Self(PhantomData) } } /// Trait that links components from the main world with output components in -/// the render world. It is used by [`SyncComponentPlugin`]. +/// the sub world. It is used by [`SyncComponentPlugin`]. /// /// The marker type `F` is only used as a way to bypass the orphan rules. To /// implement the trait for a foreign type you can use a local type as the /// marker, e.g. the type of the plugin that calls [`SyncComponentPlugin`]. -pub trait SyncComponent: Component { - /// Describes what components should be removed from the render world if the +/// +/// [`ExtractComponent`]: crate::extract_component::ExtractComponent +pub trait SyncComponent: Component { + /// Describes what components should be removed from the sub world if the /// implementing component is removed. type Target: Bundle; // TODO: https://github.com/rust-lang/rust/issues/29661 // type Target: Bundle = Self; } -impl, F: Send + Sync + 'static> Plugin for SyncComponentPlugin { +impl< + L: AppLabel + Default + Clone + Copy + Eq, + C: SyncComponent, + F: Send + Sync + 'static, + > Plugin for SyncComponentPlugin +{ fn build(&self, app: &mut App) { - app.register_required_components::(); + app.register_required_components::>(); - app.world_mut() - .register_component_hooks::() - .on_remove(|mut world, context| { - let mut pending = world.resource_mut::(); - pending.push(EntityRecord::ComponentRemoved( - context.entity, + app.add_observer( + |remove: On, mut pending: ResMut>| { + pending.push(EntityRecord::::ComponentRemoved( + remove.entity, |mut entity| { entity.remove::(); }, )); - }); + }, + ); } } diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_extract/src/sync_world.rs similarity index 67% rename from crates/bevy_render/src/sync_world.rs rename to crates/bevy_extract/src/sync_world.rs index cf92577e53326..71c6e5d88fad1 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_extract/src/sync_world.rs @@ -1,4 +1,6 @@ -use bevy_app::Plugin; +use core::marker::PhantomData; + +use bevy_app::{AppLabel, Plugin}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -14,9 +16,9 @@ use bevy_ecs::{ use bevy_platform::collections::{HashMap, HashSet}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -/// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world. +/// A plugin that synchronizes entities with [`SyncToSubWorld`] between the main world and the sub world. /// -/// All entities with the [`SyncToRenderWorld`] component are kept in sync. It +/// All entities with the [`SyncToSubWorld`] component are kept in sync. It /// is automatically added as a required component by [`ExtractComponentPlugin`] /// and [`SyncComponentPlugin`], so it doesn't need to be added manually when /// spawning or as a required component when either of these plugins are used. @@ -28,25 +30,25 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// This is called "Pipelined Rendering", see [`PipelinedRenderingPlugin`] for more information. /// /// [`SyncWorldPlugin`] is the first thing that runs every frame and it maintains an entity-to-entity mapping -/// 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. -/// The link between synced entities is maintained by the [`RenderEntity`] and [`MainEntity`] components. +/// between the main world and the sub world. +/// It does so by spawning and despawning entities in the sub world, to match spawned and despawned entities in the main world. +/// The link between synced entities is maintained by the [`SubEntity`] and [`MainEntity`] components. /// -/// 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 [`SubEntity`] contains the corresponding sub world entity of a main world entity, while [`MainEntity`] contains +/// the corresponding main world entity of a sub world entity. /// 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`]. +/// and adding [`SubEntity`] will return the corresponding sub 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 -/// to the render world for these entities. +/// to the sub world for these entities. /// /// ```text /// |--------------------------------------------------------------------| /// | | | Main world update | /// | sync | extract |---------------------------------------------------| -/// | | | Render world update | +/// | | | Sub world update | /// |--------------------------------------------------------------------| /// ``` /// @@ -56,11 +58,11 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// |---------------------------Main World------------------------------| /// | Entity | Component | /// |-------------------------------------------------------------------| -/// | ID: 1v1 | PointLight | RenderEntity(ID: 3V1) | SyncToRenderWorld | -/// | ID: 18v1 | PointLight | RenderEntity(ID: 5V1) | SyncToRenderWorld | +/// | ID: 1v1 | PointLight | SubEntity(ID: 3V1) | SyncToSubWorld | +/// | ID: 18v1 | PointLight | SubEntity(ID: 5V1) | SyncToSubWorld | /// |-------------------------------------------------------------------| /// -/// |----------Render World-----------| +/// |----------Sub world-----------| /// | Entity | Component | /// |---------------------------------| /// | ID: 3v1 | MainEntity(ID: 1V1) | @@ -69,46 +71,48 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// /// ``` /// -/// Note that this effectively establishes a link between the main world entity and the render world entity. -/// Not every entity needs to be synchronized, however; only entities with the [`SyncToRenderWorld`] component are synced. -/// Adding [`SyncToRenderWorld`] to a main world component will establish such a link. -/// Once a synchronized main entity is despawned, its corresponding render entity will be automatically +/// Note that this effectively establishes a link between the main world entity and the sub world entity. +/// Not every entity needs to be synchronized, however; only entities with the [`SyncToSubWorld`] component are synced. +/// Adding [`SyncToSubWorld`] to a main world component will establish such a link. +/// Once a synchronized main entity is despawned, its corresponding sub entity will be automatically /// despawned in the next `sync`. /// /// The sync step does not copy any of component data between worlds, since its often not necessary to transfer over all /// the components of a main world entity. -/// The render world probably cares about a `Position` component, but not a `Velocity` component. +/// The sub world probably cares about a `Position` component, but not a `Velocity` component. /// The extraction happens in its own step, independently from, and after synchronization. /// -/// Moreover, [`SyncWorldPlugin`] only synchronizes *entities*. [`RenderAsset`](crate::render_asset::RenderAsset)s like meshes and textures are handled +/// Moreover, [`SyncWorldPlugin`] only synchronizes *entities*. [`RenderAsset`]s like meshes and textures are handled /// differently. /// -/// [`PipelinedRenderingPlugin`]: crate::pipelined_rendering::PipelinedRenderingPlugin +/// [`PipelinedRenderingPlugin`]: https://docs.rs/bevy/latest/bevy/render/pipelined_rendering/struct.PipelinedRenderingPlugin.html /// [`ExtractComponentPlugin`]: crate::extract_component::ExtractComponentPlugin /// [`SyncComponentPlugin`]: crate::sync_component::SyncComponentPlugin +/// [`RenderAsset`]: https://docs.rs/bevy/latest/bevy/render/render_asset/trait.RenderAsset.html #[derive(Default)] -pub struct SyncWorldPlugin; +pub struct SyncWorldPlugin(PhantomData); -impl Plugin for SyncWorldPlugin { +impl Plugin for SyncWorldPlugin { fn build(&self, app: &mut bevy_app::App) { - app.init_resource::(); + app.init_resource::>(); app.add_observer( - |add: On, mut pending: ResMut| { - pending.push(EntityRecord::Added(add.entity)); + |add: On>, mut pending: ResMut>| { + pending.push(EntityRecord::::Added(add.entity)); }, ); app.add_observer( - |remove: On, - mut pending: ResMut, - query: Query<&RenderEntity>| { + |remove: On>, + mut pending: ResMut>, + query: Query<&SubEntity>| { if let Ok(e) = query.get(remove.entity) { - pending.push(EntityRecord::Removed(*e)); + pending.push(EntityRecord::::Removed(*e)); }; }, ); } } -/// Marker component that indicates that its entity needs to be synchronized to the render world. + +/// Marker component that indicates that its entity needs to be synchronized to the sub world. /// /// This component is automatically added as a required component by [`ExtractComponentPlugin`] and [`SyncComponentPlugin`]. /// For more information see [`SyncWorldPlugin`]. @@ -121,38 +125,39 @@ impl Plugin for SyncWorldPlugin { #[derive(Component, Copy, Clone, Debug, Default, Reflect)] #[reflect(Component, Default, Clone)] #[component(storage = "SparseSet")] -pub struct SyncToRenderWorld; +pub struct SyncToSubWorld(PhantomData); -/// Component added on the main world entities that are synced to the Render World in order to keep track of the corresponding render world entity. +/// Component added on the main world entities that are synced to the Sub World in order to keep track of the corresponding sub world entity. /// -/// Can also be used as a newtype wrapper for render world entities. +/// Can also be used as a newtype wrapper for sub world entities. #[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, Reflect)] #[component(clone_behavior = Ignore)] #[reflect(Component, Clone)] -pub struct RenderEntity(Entity); -impl RenderEntity { +pub struct SubEntity(#[deref] Entity, PhantomData); + +impl SubEntity { #[inline] pub fn id(&self) -> Entity { self.0 } } -impl From for RenderEntity { +impl From for SubEntity { fn from(entity: Entity) -> Self { - RenderEntity(entity) + SubEntity(entity, PhantomData) } } -impl ContainsEntity for RenderEntity { +impl ContainsEntity for SubEntity { fn entity(&self) -> Entity { self.id() } } -// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. -unsafe impl EntityEquivalent for RenderEntity {} +// SAFETY: SubEntity is a newtype around Entity that derives its comparison traits. +unsafe impl EntityEquivalent for SubEntity {} -/// Component added on the render world entities to keep track of the corresponding main world entity. +/// Component added on the sub world entities to keep track of the corresponding main world entity. /// /// Can also be used as a newtype wrapper for main world entities. #[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Reflect)] @@ -177,7 +182,7 @@ impl ContainsEntity for MainEntity { } } -// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. +// SAFETY: MainEntity is a newtype around Entity that derives its comparison traits. unsafe impl EntityEquivalent for MainEntity {} /// A [`HashMap`] pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]. @@ -189,58 +194,63 @@ pub type MainEntityHashSet = HashSet; /// Marker component that indicates that its entity needs to be despawned at the end of the frame. #[derive(Component, Copy, Clone, Debug, Default, Reflect)] #[reflect(Component, Default, Clone)] -pub struct TemporaryRenderEntity; +pub struct TemporaryEntity(PhantomData); -/// A record enum to what entities with [`SyncToRenderWorld`] have been added or removed. +/// A record enum to what entities with [`SyncToSubWorld`] have been added or removed. #[derive(Debug)] -pub(crate) enum EntityRecord { - /// When an entity is spawned on the main world, notify the render world so that it can spawn a corresponding +pub(crate) enum EntityRecord { + /// When an entity is spawned on the main world, notify the sub world so that it can spawn a corresponding /// entity. This contains the main world entity. Added(Entity), - /// When an entity is despawned on the main world, notify the render world so that the corresponding entity can be - /// despawned. This contains the render world entity. - Removed(RenderEntity), - /// When a component is removed from an entity, notify the render world so that the corresponding component can be + /// When an entity is despawned on the main world, notify the sub world so that the corresponding entity can be + /// despawned. This contains the sub world entity. + Removed(SubEntity), + /// When a component is removed from an entity, notify the sub world so that the corresponding component can be /// removed. This contains the main world entity. ComponentRemoved(Entity, fn(EntityWorldMut<'_>)), } // Entity Record in MainWorld pending to Sync #[derive(Resource, Default, Deref, DerefMut)] -pub(crate) struct PendingSyncEntity { - records: Vec, +pub(crate) struct PendingSyncEntity { + #[deref] + records: Vec>, + marker: PhantomData, } -pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut World) { - main_world.resource_scope(|world, mut pending: Mut| { +pub(crate) fn entity_sync_system( + main_world: &mut World, + sub_world: &mut World, +) { + main_world.resource_scope(|world, mut pending: Mut>| { // TODO : batching record for record in pending.drain(..) { match record { EntityRecord::Added(e) => { if let Ok(mut main_entity) = world.get_entity_mut(e) { - match main_entity.entry::() { + match main_entity.entry::>() { bevy_ecs::world::ComponentEntry::Occupied(_) => { panic!("Attempting to synchronize an entity that has already been synchronized!"); } bevy_ecs::world::ComponentEntry::Vacant(entry) => { - let id = render_world.spawn(MainEntity(e)).id(); + let id = sub_world.spawn(MainEntity(e)).id(); - entry.insert(RenderEntity(id)); + entry.insert(SubEntity::(id, PhantomData)); } }; } } - EntityRecord::Removed(render_entity) => { - if let Ok(ec) = render_world.get_entity_mut(render_entity.id()) { + EntityRecord::Removed(sub_entity) => { + if let Ok(ec) = sub_world.get_entity_mut(sub_entity.id()) { ec.despawn(); }; } EntityRecord::ComponentRemoved(main_entity, removal_function) => { - let Some(render_entity) = world.get::(main_entity) else { + let Some(sub_entity) = world.get::>(main_entity) else { continue; }; - if let Ok(render_world_entity) = render_world.get_entity_mut(render_entity.id()) { - removal_function(render_world_entity); + if let Ok(sub_world_entity) = sub_world.get_entity_mut(sub_entity.id()) { + removal_function(sub_world_entity); } }, } @@ -248,9 +258,9 @@ pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut Worl }); } -pub(crate) fn despawn_temporary_render_entities( +pub(crate) fn despawn_temporary_entities( world: &mut World, - state: &mut SystemState>>, + state: &mut SystemState>>>, mut local: Local>, ) { let query = state.get(world).unwrap(); @@ -266,11 +276,12 @@ 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, +/// The implementations for both [`MainEntity`] and [`SubEntity`] 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}; +mod sub_entities_world_query_impls { + use super::{MainEntity, SubEntity}; + use bevy_app::AppLabel; use bevy_ecs::{ archetype::Archetype, change_detection::Tick, @@ -284,11 +295,11 @@ mod render_entities_world_query_impls { world::{unsafe_world_cell::UnsafeWorldCell, World}, }; - // SAFETY: defers completely to `&RenderEntity` implementation, + // SAFETY: defers completely to `&SubEntity` implementation, // and then only modifies the output safely. - unsafe impl WorldQuery for RenderEntity { - type Fetch<'w> = <&'static RenderEntity as WorldQuery>::Fetch<'w>; - type State = <&'static RenderEntity as WorldQuery>::State; + unsafe impl WorldQuery for SubEntity { + type Fetch<'w> = <&'static SubEntity as WorldQuery>::Fetch<'w>; + type State = <&'static SubEntity as WorldQuery>::State; fn shrink_fetch<'wlong: 'wshort, 'wshort>( fetch: Self::Fetch<'wlong>, @@ -303,13 +314,13 @@ mod render_entities_world_query_impls { last_run: Tick, this_run: Tick, ) -> Self::Fetch<'w> { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. unsafe { - <&RenderEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) + <&SubEntity as WorldQuery>::init_fetch(world, component_id, last_run, this_run) } } - const IS_DENSE: bool = <&'static RenderEntity as WorldQuery>::IS_DENSE; + const IS_DENSE: bool = <&'static SubEntity as WorldQuery>::IS_DENSE; #[inline] unsafe fn set_archetype<'w, 's>( @@ -318,9 +329,9 @@ mod render_entities_world_query_impls { archetype: &'w Archetype, table: &'w Table, ) { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. unsafe { - <&RenderEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table); + <&SubEntity as WorldQuery>::set_archetype(fetch, component_id, archetype, table); } } @@ -330,36 +341,36 @@ mod render_entities_world_query_impls { &component_id: &'s 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) } + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. + unsafe { <&SubEntity as WorldQuery>::set_table(fetch, &component_id, table) } } fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) { - <&RenderEntity as WorldQuery>::update_component_access(&component_id, access); + <&SubEntity as WorldQuery>::update_component_access(&component_id, access); } fn init_state(world: &mut World) -> ComponentId { - <&RenderEntity as WorldQuery>::init_state(world) + <&SubEntity as WorldQuery>::init_state(world) } fn get_state(components: &Components) -> Option { - <&RenderEntity as WorldQuery>::get_state(components) + <&SubEntity 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) + <&SubEntity 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 { + unsafe impl QueryData for SubEntity { const IS_READ_ONLY: bool = true; const IS_ARCHETYPAL: bool = <&MainEntity as QueryData>::IS_ARCHETYPAL; - type ReadOnly = RenderEntity; + type ReadOnly = SubEntity; type Item<'w, 's> = Entity; fn shrink<'wlong: 'wshort, 'wshort, 's>( @@ -375,37 +386,37 @@ mod render_entities_world_query_impls { entity: Entity, table_row: TableRow, ) -> Option> { - // SAFETY: defers to the `&T` implementation, with T set to `RenderEntity`. + // SAFETY: defers to the `&T` implementation, with T set to `SubEntity`. let component = - unsafe { <&RenderEntity as QueryData>::fetch(state, fetch, entity, table_row) }; - component.map(RenderEntity::id) + unsafe { <&SubEntity as QueryData>::fetch(state, fetch, entity, table_row) }; + component.map(SubEntity::id) } fn iter_access( state: &Self::State, ) -> impl Iterator> { - <&RenderEntity as QueryData>::iter_access(state) + <&SubEntity as QueryData>::iter_access(state) } } /// SAFETY: access is read only and only on the current entity - unsafe impl IterQueryData for RenderEntity {} + unsafe impl IterQueryData for SubEntity {} /// SAFETY: access is read only - unsafe impl ReadOnlyQueryData for RenderEntity {} + unsafe impl ReadOnlyQueryData for SubEntity {} /// SAFETY: access is only on the current entity - unsafe impl SingleEntityQueryData for RenderEntity {} + unsafe impl SingleEntityQueryData for SubEntity {} - impl ArchetypeQueryData for RenderEntity {} + impl ArchetypeQueryData for SubEntity {} - impl ReleaseStateQueryData for RenderEntity { + impl ReleaseStateQueryData for SubEntity { fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static> { item } } - // SAFETY: defers completely to `&RenderEntity` implementation, + // SAFETY: defers completely to `&SubEntity` implementation, // and then only modifies the output safely. unsafe impl WorldQuery for MainEntity { type Fetch<'w> = <&'static MainEntity as WorldQuery>::Fetch<'w>; @@ -529,6 +540,9 @@ mod render_entities_world_query_impls { #[cfg(test)] mod tests { + use core::marker::PhantomData; + + use bevy_derive::AppLabel; use bevy_ecs::{ component::Component, entity::Entity, @@ -540,28 +554,31 @@ mod tests { }; use super::{ - entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, RenderEntity, - SyncToRenderWorld, + entity_sync_system, EntityRecord, MainEntity, PendingSyncEntity, SubEntity, SyncToSubWorld, }; #[derive(Component)] - struct RenderDataComponent; + struct SubDataComponent; + + #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] + struct ExtractApp; #[test] fn sync_world() { let mut main_world = World::new(); - let mut render_world = World::new(); - main_world.init_resource::(); + let mut sub_world = World::new(); + main_world.init_resource::>(); main_world.add_observer( - |add: On, mut pending: ResMut| { + |add: On>, + mut pending: ResMut>| { pending.push(EntityRecord::Added(add.entity)); }, ); main_world.add_observer( - |remove: On, - mut pending: ResMut, - query: Query<&RenderEntity>| { + |remove: On>, + mut pending: ResMut>, + query: Query<&SubEntity>| { if let Ok(e) = query.get(remove.entity) { pending.push(EntityRecord::Removed(*e)); }; @@ -575,25 +592,27 @@ mod tests { // spawn let main_entity = main_world - .spawn(RenderDataComponent) - // indicates that its entity needs to be synchronized to the render world - .insert(SyncToRenderWorld) + .spawn(SubDataComponent) + // indicates that its entity needs to be synchronized to the sub world + .insert(SyncToSubWorld::(PhantomData)) .id(); - entity_sync_system(&mut main_world, &mut render_world); + entity_sync_system::(&mut main_world, &mut sub_world); - let mut q = render_world.query_filtered::>(); + let mut q = sub_world.query_filtered::>(); // Only one synchronized entity - assert!(q.iter(&render_world).count() == 1); + assert!(q.iter(&sub_world).count() == 1); - let render_entity = q.single(&render_world).unwrap(); - let render_entity_component = main_world.get::(main_entity).unwrap(); + let sub_entity = q.single(&sub_world).unwrap(); + let sub_entity_component = main_world + .get::>(main_entity) + .unwrap(); - assert!(render_entity_component.id() == render_entity); + assert!(sub_entity_component.id() == sub_entity); - let main_entity_component = render_world - .get::(render_entity_component.id()) + let main_entity_component = sub_world + .get::(sub_entity_component.id()) .unwrap(); assert!(main_entity_component.id() == main_entity); @@ -601,9 +620,9 @@ mod tests { // despawn main_world.despawn(main_entity); - entity_sync_system(&mut main_world, &mut render_world); + entity_sync_system::(&mut main_world, &mut sub_world); // Only one synchronized entity - assert!(q.iter(&render_world).count() == 0); + assert!(q.iter(&sub_world).count() == 0); } } diff --git a/crates/bevy_gizmos_render/Cargo.toml b/crates/bevy_gizmos_render/Cargo.toml index 41d9197e1880d..3a4b1cc1131e9 100644 --- a/crates/bevy_gizmos_render/Cargo.toml +++ b/crates/bevy_gizmos_render/Cargo.toml @@ -20,6 +20,7 @@ bevy_app = { path = "../bevy_app", version = "0.19.0-dev" } bevy_gizmos = { path = "../bevy_gizmos", version = "0.19.0-dev" } bevy_camera = { path = "../bevy_camera", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.19.0-dev" } bevy_math = { path = "../bevy_math", version = "0.19.0-dev" } diff --git a/crates/bevy_gizmos_render/src/lib.rs b/crates/bevy_gizmos_render/src/lib.rs index 4b7d37324dc5e..98d135d49d8bd 100755 --- a/crates/bevy_gizmos_render/src/lib.rs +++ b/crates/bevy_gizmos_render/src/lib.rs @@ -214,7 +214,7 @@ fn extract_gizmo_data( // The immediate mode API does not have a main world entity to refer to, // but we do need MainEntity on this render entity for the systems to find it. MainEntity::from(Entity::PLACEHOLDER), - TemporaryRenderEntity, + TemporaryRenderEntity::default(), )); } } @@ -621,6 +621,7 @@ fn line_joint_gizmo_vertex_buffer_layouts() -> Vec { /// can be added and therefore three potential entities. #[derive(Clone, Reflect, Resource, ExtractResource)] #[reflect(Clone, Resource)] +#[extract_app(RenderApp)] pub struct LineGizmoEntities { /// An entity that regular line phase items are associated with. pub line_gizmo_renderer: MainEntity, diff --git a/crates/bevy_gizmos_render/src/retained.rs b/crates/bevy_gizmos_render/src/retained.rs index 5d3b837982b90..46a3a837a9f63 100644 --- a/crates/bevy_gizmos_render/src/retained.rs +++ b/crates/bevy_gizmos_render/src/retained.rs @@ -75,7 +75,7 @@ pub(crate) fn extract_linegizmos( handle: gizmo.handle.clone(), }, MainEntity::from(entity), - TemporaryRenderEntity, + TemporaryRenderEntity::default(), )); } *previous_len = values.len(); diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index de709cdb3a104..aa078717bd11a 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -16,6 +16,7 @@ trace = [ "bevy_core_pipeline?/trace", "bevy_anti_alias?/trace", "bevy_ecs/trace", + "bevy_extract/trace", "bevy_log/trace", "bevy_pbr?/trace", "bevy_render?/trace", @@ -226,6 +227,7 @@ morph = ["bevy_mesh?/morph", "bevy_render?/morph"] # Enables bevy_mesh and bevy_animation morph weight support morph_animation = ["morph", "bevy_animation?/bevy_mesh"] +bevy_extract = ["dep:bevy_extract"] bevy_shader = ["dep:bevy_shader"] bevy_image = ["dep:bevy_image", "bevy_color", "bevy_asset"] bevy_sprite = ["dep:bevy_sprite", "bevy_camera"] @@ -248,6 +250,7 @@ bevy_light = ["dep:bevy_light", "bevy_camera"] bevy_render = [ "dep:bevy_render", "bevy_camera", + "bevy_extract", "bevy_shader", "bevy_color/wgpu-types", "bevy_color/encase", @@ -535,6 +538,7 @@ bevy_post_process = { path = "../bevy_post_process", optional = true, version = bevy_ui_widgets = { path = "../bevy_ui_widgets", optional = true, version = "0.19.0-dev" } bevy_anti_alias = { path = "../bevy_anti_alias", optional = true, version = "0.19.0-dev" } bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev", optional = true } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.19.0-dev" } bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.19.0-dev", default-features = false } bevy_gizmos_render = { path = "../bevy_gizmos_render", optional = true, version = "0.19.0-dev", default-features = false } diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index a06f6acabf455..445b145103448 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -45,6 +45,7 @@ bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_gltf = { path = "../bevy_gltf", version = "0.19.0-dev", optional = true } bevy_light = { path = "../bevy_light", version = "0.19.0-dev" } bevy_log = { path = "../bevy_log", version = "0.19.0-dev" } diff --git a/crates/bevy_pbr/src/atmosphere/environment.rs b/crates/bevy_pbr/src/atmosphere/environment.rs index e19f39e15323b..0481bab3f975c 100644 --- a/crates/bevy_pbr/src/atmosphere/environment.rs +++ b/crates/bevy_pbr/src/atmosphere/environment.rs @@ -24,12 +24,14 @@ use bevy_render::{ renderer::{RenderContext, RenderDevice, ViewQuery}, texture::{CachedTexture, GpuImage}, view::{ViewUniform, ViewUniformOffset, ViewUniforms}, + RenderApp, }; use bevy_utils::default; use tracing::warn; // Render world representation of an environment map light for the atmosphere #[derive(Component, ExtractComponent, Clone, FromTemplate)] +#[extract_app(RenderApp)] pub struct AtmosphereEnvironmentMap { pub environment_map: Handle, pub size: UVec2, diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 183c3e69132e5..5df61114f54ca 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -391,7 +391,7 @@ impl From for GpuAtmosphereSettings { } } -impl SyncComponent for AtmosphereSettings { +impl SyncComponent for AtmosphereSettings { type Target = GpuAtmosphereSettings; } diff --git a/crates/bevy_pbr/src/cluster/gpu.rs b/crates/bevy_pbr/src/cluster/gpu.rs index 408e03e82a273..89de0c53dba4a 100644 --- a/crates/bevy_pbr/src/cluster/gpu.rs +++ b/crates/bevy_pbr/src/cluster/gpu.rs @@ -1725,7 +1725,7 @@ pub(crate) fn prepare_clusters_for_gpu_clustering( .retain(|view_main_entity, _| all_view_main_entities.contains(view_main_entity)); } -impl ExtractResource for GlobalClusterSettings { +impl ExtractResource for GlobalClusterSettings { type Source = GlobalClusterSettings; fn extract_resource(source: &Self::Source) -> Self { diff --git a/crates/bevy_pbr/src/contact_shadows.rs b/crates/bevy_pbr/src/contact_shadows.rs index 711c59368dc8f..c55016c1f3597 100644 --- a/crates/bevy_pbr/src/contact_shadows.rs +++ b/crates/bevy_pbr/src/contact_shadows.rs @@ -79,11 +79,11 @@ impl From for ContactShadowsUniform { } } -impl SyncComponent for ContactShadows { +impl SyncComponent for ContactShadows { type Target = (Self, ViewContactShadowsUniformOffset); } -impl ExtractComponent for ContactShadows { +impl ExtractComponent for ContactShadows { type QueryData = &'static ContactShadows; type QueryFilter = (); type Out = Self; diff --git a/crates/bevy_pbr/src/decal/clustered.rs b/crates/bevy_pbr/src/decal/clustered.rs index 31f9f2e436f40..2e0a8719acbff 100644 --- a/crates/bevy_pbr/src/decal/clustered.rs +++ b/crates/bevy_pbr/src/decal/clustered.rs @@ -184,7 +184,7 @@ impl Plugin for ClusteredDecalPlugin { } } -impl SyncComponent for ClusteredDecal { +impl SyncComponent for ClusteredDecal { type Target = Self; } diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index e2b9450774bd9..41f3d66d187cd 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -40,6 +40,7 @@ pub const DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID: u8 = 1; /// /// Will be automatically added to entities with the [`DeferredPrepass`] component that don't already have a [`PbrDeferredLightingDepthId`]. #[derive(Component, Clone, Copy, ExtractComponent, ShaderType)] +#[extract_app(RenderApp)] pub struct PbrDeferredLightingDepthId { depth_id: u32, diff --git a/crates/bevy_pbr/src/fog.rs b/crates/bevy_pbr/src/fog.rs index 3c470b792e88e..a5c19d94fbbd4 100644 --- a/crates/bevy_pbr/src/fog.rs +++ b/crates/bevy_pbr/src/fog.rs @@ -3,7 +3,7 @@ use bevy_color::{Color, ColorToComponents, LinearRgba}; use bevy_ecs::prelude::*; use bevy_math::{ops, Vec3}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::extract_component::ExtractComponent; +use bevy_render::{extract_component::ExtractComponent, RenderApp}; use crate::ViewFogUniformOffset; @@ -52,6 +52,7 @@ use crate::ViewFogUniformOffset; #[extract_component_filter(With)] #[extract_component_sync_target((Self, ViewFogUniformOffset))] #[reflect(Component, Default, Debug, Clone)] +#[extract_app(RenderApp)] pub struct DistanceFog { /// The color of the fog effect. /// diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index c3ee7793dd3bb..d795420c7847b 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -507,7 +507,7 @@ pub fn area_light_luts_placeholder() -> Image { } } -impl SyncComponent for DirectionalLight { +impl SyncComponent for DirectionalLight { type Target = ( Self, ExtractedDirectionalLight, @@ -516,7 +516,7 @@ impl SyncComponent for DirectionalLight { DirectionalLightViewEntities, ); } -impl SyncComponent for PointLight { +impl SyncComponent for PointLight { type Target = ( Self, ExtractedPointLight, @@ -525,7 +525,7 @@ impl SyncComponent for PointLight { PointAndSpotLightViewEntities, ); } -impl SyncComponent for SpotLight { +impl SyncComponent for SpotLight { type Target = ( Self, ExtractedPointLight, @@ -534,12 +534,12 @@ impl SyncComponent for SpotLight { PointAndSpotLightViewEntities, ); } -impl SyncComponent for RectLight { +impl SyncComponent for RectLight { type Target = (Self, ExtractedRectLight); } -impl SyncComponent for AmbientLight { +impl SyncComponent for AmbientLight { type Target = Self; } -impl SyncComponent for ShadowFilteringMethod { +impl SyncComponent for ShadowFilteringMethod { type Target = Self; } diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 3ba44e3247fb4..d7e307970cd74 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -49,11 +49,11 @@ use bevy_ecs::{ query::{QueryData, QueryItem}, system::lifetimeless::Read, }; +use bevy_extract::extract_instances::ExtractInstance; use bevy_image::Image; use bevy_light::{EnvironmentMapLight, ParallaxCorrection}; use bevy_math::{Affine3A, Quat, Vec3}; use bevy_render::{ - extract_instances::ExtractInstance, render_asset::RenderAssets, render_resource::{ binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, TextureSampleType, @@ -61,6 +61,7 @@ use bevy_render::{ }, renderer::{RenderAdapter, RenderDevice}, texture::{FallbackImage, GpuImage}, + RenderApp, }; use core::{num::NonZero, ops::Deref}; @@ -139,7 +140,7 @@ pub struct EnvironmentMapViewLightProbeInfo { pub(crate) rotation: Quat, } -impl ExtractInstance for EnvironmentMapIds { +impl ExtractInstance for EnvironmentMapIds { type QueryData = Read; type QueryFilter = (); diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index dc99a9688a97e..49e4fd4ac03ab 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -1106,6 +1106,6 @@ pub fn generate_environment_map_light( } } -impl SyncComponent for GeneratedEnvironmentMapLight { +impl SyncComponent for GeneratedEnvironmentMapLight { type Target = RenderEnvironmentMap; } diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index c098b5be78d6d..068f501fcf16c 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -12,6 +12,7 @@ use bevy_ecs::{ schedule::IntoScheduleConfigs, system::{Commands, Local, Query, Res, ResMut}, }; +use bevy_extract::extract_instances::ExtractInstancesPlugin; use bevy_image::Image; use bevy_light::{ cluster::ClusterVisibilityClass, EnvironmentMapLight, IrradianceVolume, LightProbe, @@ -19,7 +20,6 @@ use bevy_light::{ use bevy_math::{Affine3A, FloatOrd, Mat4, Quat, Vec3, Vec4}; use bevy_platform::collections::HashMap; use bevy_render::{ - extract_instances::ExtractInstancesPlugin, render_asset::RenderAssets, render_resource::{DynamicUniformBuffer, Sampler, ShaderType, TextureView}, renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper}, @@ -378,7 +378,7 @@ impl Plugin for LightProbePlugin { app.add_plugins(( EnvironmentMapGenerationPlugin, - ExtractInstancesPlugin::::new(), + ExtractInstancesPlugin::::new(), )); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 01549ccdc4e6f..b2f1533a78a6d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -51,7 +51,6 @@ use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingSupport, extract_resource::ExtractResource, mesh::RenderMesh, - prelude::*, render_phase::*, render_resource::*, renderer::RenderDevice, @@ -1423,6 +1422,7 @@ pub fn queue_material_meshes( /// Default render method used for opaque materials. #[derive(Default, Resource, Clone, Debug, ExtractResource, Reflect)] #[reflect(Resource, Default, Debug, Clone)] +#[extract_app(RenderApp)] pub struct DefaultOpaqueRendererMethod(OpaqueRendererMethod); impl DefaultOpaqueRendererMethod { diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index a1767d6c5bec0..2e06d8a918c7d 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -113,6 +113,7 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { #[require(DepthPrepass, NormalPrepass)] #[extract_component_sync_target((Self, ScreenSpaceAmbientOcclusionResources, SsaoPipelineId, SsaoBindGroups))] #[doc(alias = "Ssao")] +#[extract_app(RenderApp)] pub struct ScreenSpaceAmbientOcclusion { /// Quality of the SSAO effect. pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel, diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 1de696857d29d..0b7053d64e551 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -445,7 +445,7 @@ pub fn prepare_ssr_settings( } } -impl SyncComponent for ScreenSpaceReflections { +impl SyncComponent for ScreenSpaceReflections { type Target = ( ScreenSpaceReflectionsUniform, ViewScreenSpaceReflectionsUniformOffset, @@ -453,7 +453,7 @@ impl SyncComponent for ScreenSpaceReflections { ); } -impl ExtractComponent for ScreenSpaceReflections { +impl ExtractComponent for ScreenSpaceReflections { type QueryData = Read; type QueryFilter = (); type Out = ScreenSpaceReflectionsUniform; diff --git a/crates/bevy_pbr/src/transmission/mod.rs b/crates/bevy_pbr/src/transmission/mod.rs index a7e3f056119e7..1463ae5da0cb6 100644 --- a/crates/bevy_pbr/src/transmission/mod.rs +++ b/crates/bevy_pbr/src/transmission/mod.rs @@ -64,6 +64,7 @@ impl Plugin for ScreenSpaceTransmissionPlugin { /// Configures transmission behavior, offering a trade-off between performance and visual fidelity. #[derive(Component, Reflect, Clone, ExtractComponent)] #[reflect(Component, Default, Clone)] +#[extract_app(RenderApp)] pub struct ScreenSpaceTransmission { /// How many individual steps should be performed in the `Transmissive3d` pass. /// diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 661032dd2aab3..fc1f084c278ab 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -107,6 +107,6 @@ impl Plugin for VolumetricFogPlugin { } } -impl SyncComponent for FogVolume { +impl SyncComponent for FogVolume { type Target = Self; } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 3b8581abfbcf6..fcaab8bce2096 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -38,7 +38,6 @@ use bevy_render::{ allocator::{MeshAllocator, MeshAllocatorSettings, MeshSlabs}, RenderMesh, RenderMeshBufferInfo, }, - prelude::*, render_asset::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, @@ -55,7 +54,8 @@ use bevy_render::{ ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, }, - Extract, GpuResourceAppExt, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, + Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderDebugFlags, + RenderStartup, RenderSystems, }; use bevy_shader::Shader; use bytemuck::{Pod, Zeroable}; @@ -882,6 +882,7 @@ pub enum WireframeTopology { #[derive(Resource, Debug, Clone, ExtractResource, Reflect)] #[reflect(Resource, Debug, Default)] +#[extract_app(RenderApp)] pub struct WireframeConfig { /// Whether to show wireframes for all meshes. /// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component. diff --git a/crates/bevy_post_process/Cargo.toml b/crates/bevy_post_process/Cargo.toml index 1b50566a33ae7..64d7f9e355997 100644 --- a/crates/bevy_post_process/Cargo.toml +++ b/crates/bevy_post_process/Cargo.toml @@ -21,6 +21,7 @@ bevy_color = { path = "../bevy_color", version = "0.19.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } bevy_camera = { path = "../bevy_camera", version = "0.19.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev" } diff --git a/crates/bevy_post_process/src/auto_exposure/settings.rs b/crates/bevy_post_process/src/auto_exposure/settings.rs index 95da8bec99304..6169031f6d1b0 100644 --- a/crates/bevy_post_process/src/auto_exposure/settings.rs +++ b/crates/bevy_post_process/src/auto_exposure/settings.rs @@ -6,7 +6,7 @@ use bevy_camera::Hdr; use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_image::Image; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::extract_component::ExtractComponent; +use bevy_render::{extract_component::ExtractComponent, RenderApp}; use bevy_utils::default; /// Component that enables auto exposure for an HDR-enabled 2d or 3d camera. @@ -27,6 +27,7 @@ use bevy_utils::default; #[derive(Component, Clone, Reflect, ExtractComponent)] #[reflect(Component, Default, Clone)] #[require(Hdr)] +#[extract_app(RenderApp)] pub struct AutoExposure { /// The range of exposure values for the histogram. /// diff --git a/crates/bevy_post_process/src/bloom/settings.rs b/crates/bevy_post_process/src/bloom/settings.rs index fc6ada8d82e7e..08aad2401eea7 100644 --- a/crates/bevy_post_process/src/bloom/settings.rs +++ b/crates/bevy_post_process/src/bloom/settings.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ }; use bevy_math::{AspectRatio, URect, UVec4, Vec2, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::{extract_component::ExtractComponent, sync_component::SyncComponent}; +use bevy_render::{extract_component::ExtractComponent, sync_component::SyncComponent, RenderApp}; /// Applies a bloom effect to an HDR-enabled 2d or 3d camera. /// @@ -220,11 +220,11 @@ pub enum BloomCompositeMode { Additive, } -impl SyncComponent for Bloom { +impl SyncComponent for Bloom { type Target = (Self, BloomUniforms); } -impl ExtractComponent for Bloom { +impl ExtractComponent for Bloom { type QueryData = (&'static Self, &'static Camera); type QueryFilter = With; type Out = (Self, BloomUniforms); diff --git a/crates/bevy_post_process/src/dof/mod.rs b/crates/bevy_post_process/src/dof/mod.rs index 0644b7c499188..532cd8450a3ac 100644 --- a/crates/bevy_post_process/src/dof/mod.rs +++ b/crates/bevy_post_process/src/dof/mod.rs @@ -649,7 +649,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline { } } -impl SyncComponent for DepthOfField { +impl SyncComponent for DepthOfField { type Target = ( DepthOfField, DepthOfFieldUniform, @@ -678,7 +678,7 @@ fn extract_depth_of_field_settings( // Depth of field is nonsensical without a perspective projection. let Projection::Perspective(ref perspective_projection) = *projection else { - entity_commands.remove::<::Target>(); + entity_commands.remove::<>::Target>(); continue; }; diff --git a/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs b/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs index c77115d20d3aa..8cbf06e254c1b 100644 --- a/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs +++ b/crates/bevy_post_process/src/effect_stack/chromatic_aberration.rs @@ -10,7 +10,8 @@ use bevy_ecs::{ use bevy_image::Image; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - extract_component::ExtractComponent, render_resource::ShaderType, sync_component::SyncComponent, + extract_component::ExtractComponent, render_resource::ShaderType, + sync_component::SyncComponent, RenderApp, }; /// The raw RGBA data for the default chromatic aberration gradient. @@ -78,11 +79,11 @@ impl Default for ChromaticAberration { } } -impl SyncComponent for ChromaticAberration { +impl SyncComponent for ChromaticAberration { type Target = Self; } -impl ExtractComponent for ChromaticAberration { +impl ExtractComponent for ChromaticAberration { type QueryData = Read; type QueryFilter = With; type Out = Self; diff --git a/crates/bevy_post_process/src/effect_stack/lens_distortion.rs b/crates/bevy_post_process/src/effect_stack/lens_distortion.rs index e676da36b5c3e..93aadb5dd252b 100644 --- a/crates/bevy_post_process/src/effect_stack/lens_distortion.rs +++ b/crates/bevy_post_process/src/effect_stack/lens_distortion.rs @@ -6,7 +6,8 @@ use bevy_ecs::{ use bevy_math::{ops::abs, Vec2}; use bevy_reflect::Reflect; use bevy_render::{ - extract_component::ExtractComponent, render_resource::ShaderType, sync_component::SyncComponent, + extract_component::ExtractComponent, render_resource::ShaderType, + sync_component::SyncComponent, RenderApp, }; /// Simulates the warping of the image caused by real-world camera lenses. @@ -78,11 +79,11 @@ impl Default for LensDistortion { } } -impl SyncComponent for LensDistortion { +impl SyncComponent for LensDistortion { type Target = Self; } -impl ExtractComponent for LensDistortion { +impl ExtractComponent for LensDistortion { type QueryData = Read; type QueryFilter = With; type Out = Self; diff --git a/crates/bevy_post_process/src/effect_stack/vignette.rs b/crates/bevy_post_process/src/effect_stack/vignette.rs index 1ebc913a30741..a83cd21ab1de0 100644 --- a/crates/bevy_post_process/src/effect_stack/vignette.rs +++ b/crates/bevy_post_process/src/effect_stack/vignette.rs @@ -9,7 +9,8 @@ use bevy_ecs::{ use bevy_math::{Vec2, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - extract_component::ExtractComponent, render_resource::ShaderType, sync_component::SyncComponent, + extract_component::ExtractComponent, render_resource::ShaderType, + sync_component::SyncComponent, RenderApp, }; /// Adds a gradual shading effect to the edges of the screen, drawing focus @@ -87,11 +88,11 @@ impl Default for Vignette { } } -impl SyncComponent for Vignette { +impl SyncComponent for Vignette { type Target = Self; } -impl ExtractComponent for Vignette { +impl ExtractComponent for Vignette { type QueryData = Read; type QueryFilter = With; type Out = Self; diff --git a/crates/bevy_post_process/src/motion_blur/mod.rs b/crates/bevy_post_process/src/motion_blur/mod.rs index a5c385df37ff1..ebe6856208668 100644 --- a/crates/bevy_post_process/src/motion_blur/mod.rs +++ b/crates/bevy_post_process/src/motion_blur/mod.rs @@ -115,11 +115,11 @@ impl Default for MotionBlur { } } -impl SyncComponent for MotionBlur { +impl SyncComponent for MotionBlur { type Target = MotionBlurUniform; } -impl ExtractComponent for MotionBlur { +impl ExtractComponent for MotionBlur { type QueryData = &'static Self; type QueryFilter = With; type Out = MotionBlurUniform; diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index c0d5b89d2bc9f..26fefbbf0c4e5 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -67,6 +67,8 @@ bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } +bevy_extract_macros = { path = "../bevy_extract/macros", version = "0.19.0-dev" } bevy_math = { path = "../bevy_math", version = "0.19.0-dev" } bevy_material = { path = "../bevy_material", version = "0.19.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev" } diff --git a/crates/bevy_render/macros/Cargo.toml b/crates/bevy_render/macros/Cargo.toml index 1efab227a6764..f2f962d771b8a 100644 --- a/crates/bevy_render/macros/Cargo.toml +++ b/crates/bevy_render/macros/Cargo.toml @@ -13,6 +13,7 @@ proc-macro = true [dependencies] bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.19.0-dev" } +bevy_extract_macros = { path = "../../bevy_extract/macros", version = "0.19.0-dev" } syn = { version = "2.0", features = ["full"] } proc-macro2 = "1.0" diff --git a/crates/bevy_render/macros/src/extract_resource.rs b/crates/bevy_render/macros/src/extract_resource.rs deleted file mode 100644 index 0c65fa3fc5d12..0000000000000 --- a/crates/bevy_render/macros/src/extract_resource.rs +++ /dev/null @@ -1,27 +0,0 @@ -use bevy_macro_utils::fq_std::FQClone; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, parse_quote, DeriveInput, Path}; - -pub fn derive_extract_resource(input: TokenStream) -> TokenStream { - let mut ast = parse_macro_input!(input as DeriveInput); - let bevy_render_path: Path = crate::bevy_render_path(); - - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { Self: #FQClone }); - - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - - TokenStream::from(quote! { - impl #impl_generics #bevy_render_path::extract_resource::ExtractResource for #struct_name #type_generics #where_clause { - type Source = Self; - - fn extract_resource(source: &Self::Source) -> Self { - source.clone() - } - } - }) -} diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 15115b2157987..922e17eb96ec7 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -2,8 +2,6 @@ #![cfg_attr(docsrs, feature(doc_cfg))] mod as_bind_group; -mod extract_component; -mod extract_resource; mod specializer; use bevy_macro_utils::{derive_label, BevyManifest}; @@ -19,48 +17,6 @@ pub(crate) fn bevy_ecs_path() -> syn::Path { BevyManifest::shared(|manifest| manifest.get_path("bevy_ecs")) } -#[proc_macro_derive(ExtractResource)] -pub fn derive_extract_resource(input: TokenStream) -> TokenStream { - extract_resource::derive_extract_resource(input) -} - -/// Implements `ExtractComponent` trait for a component. -/// -/// The component must implement [`Clone`]. -/// The component will be extracted into the render world via cloning. -/// Note that this only enables extraction of the component, it does not execute the extraction. -/// See `ExtractComponentPlugin` to actually perform the extraction. -/// -/// If you only want to extract a component conditionally, you may use the `extract_component_filter` attribute. -/// To specify `SyncComponent::Target`, you can use the `extract_component_sync_target` attribute. -/// -/// # Example -/// -/// ```no_compile -/// use bevy_ecs::component::Component; -/// use bevy_render_macros::ExtractComponent; -/// -/// #[derive(Component, Clone, ExtractComponent)] -/// #[extract_component_filter(With)] -/// #[extract_component_sync_target((Self, OtherNeedsCleanup))] -/// pub struct Foo { -/// pub should_foo: bool, -/// } -/// -/// // Without a filter (unconditional). -/// #[derive(Component, Clone, ExtractComponent)] -/// pub struct Bar { -/// pub should_bar: bool, -/// } -/// ``` -#[proc_macro_derive( - ExtractComponent, - attributes(extract_component_filter, extract_component_sync_target) -)] -pub fn derive_extract_component(input: TokenStream) -> TokenStream { - extract_component::derive_extract_component(input) -} - #[proc_macro_derive( AsBindGroup, attributes( diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 29d15a00b0b97..8ac5e672dabe8 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -102,7 +102,8 @@ impl Plugin for CameraPlugin { .add_systems( ExtractSchedule, ( - extract_cameras.after(extract_resource::), + extract_cameras + .after(extract_resource::), clear_dirty_specializations.in_set(DirtySpecializationSystems::Clear), clear_dirty_wireframe_specializations .in_set(DirtySpecializationSystems::Clear), @@ -122,7 +123,7 @@ fn warn_on_no_render_graph(world: DeferredWorld, HookContext { entity, caller, . } } -impl ExtractResource for ClearColor { +impl ExtractResource for ClearColor { type Source = Self; fn extract_resource(source: &Self::Source) -> Self { @@ -130,11 +131,11 @@ impl ExtractResource for ClearColor { } } -impl SyncComponent for CameraMainTextureUsages { +impl SyncComponent for CameraMainTextureUsages { type Target = Self; } -impl ExtractComponent for CameraMainTextureUsages { +impl ExtractComponent for CameraMainTextureUsages { type QueryData = &'static Self; type QueryFilter = (); type Out = Self; @@ -144,11 +145,11 @@ impl ExtractComponent for CameraMainTextureUsages { } } -impl SyncComponent for Camera2d { +impl SyncComponent for Camera2d { type Target = Self; } -impl ExtractComponent for Camera2d { +impl ExtractComponent for Camera2d { type QueryData = &'static Self; type QueryFilter = With; type Out = Self; @@ -158,11 +159,11 @@ impl ExtractComponent for Camera2d { } } -impl SyncComponent for Camera3d { +impl SyncComponent for Camera3d { type Target = Self; } -impl ExtractComponent for Camera3d { +impl ExtractComponent for Camera3d { type QueryData = &'static Self; type QueryFilter = With; type Out = Self; diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index 6a11bc429b4d7..6acf3ff83cd99 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -41,6 +41,7 @@ fn extract_time(mut commands: Commands, time: Extract>) { /// Currently only contains values related to time. #[derive(Default, Clone, Resource, ExtractResource, Reflect, ShaderType)] #[reflect(Resource, Default, Clone)] +#[extract_app(RenderApp)] pub struct GlobalsUniform { /// The time since startup in seconds. /// Wraps to 0 after 1 hour. diff --git a/crates/bevy_render/src/gpu_readback.rs b/crates/bevy_render/src/gpu_readback.rs index bfa6a9ac64b3e..aaa22bfe8820c 100644 --- a/crates/bevy_render/src/gpu_readback.rs +++ b/crates/bevy_render/src/gpu_readback.rs @@ -25,11 +25,11 @@ use bevy_ecs::{ system::{Commands, Query, Res}, }; use bevy_ecs::{schedule::IntoScheduleConfigs, template::FromTemplate}; +use bevy_extract_macros::ExtractComponent; use bevy_image::{Image, TextureFormatPixelInfo}; use bevy_log::warn; use bevy_platform::collections::HashMap; use bevy_reflect::Reflect; -use bevy_render_macros::ExtractComponent; use encase::internal::ReadFrom; use encase::private::Reader; use encase::ShaderType; @@ -81,6 +81,7 @@ impl Plugin for GpuReadbackPlugin { /// Data is read asynchronously and will be triggered on the entity via the [`ReadbackComplete`] event /// when complete. If this component is not removed, the readback will be attempted every frame #[derive(Component, ExtractComponent, Clone, Debug, FromTemplate)] +#[extract_app(RenderApp)] pub enum Readback { #[default] Texture(Handle), diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 036f647e37ac8..899997fde178d 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -41,11 +41,25 @@ pub mod camera; pub mod diagnostic; pub mod erased_render_asset; pub mod error_handler; -pub mod extract_component; -pub mod extract_instances; -mod extract_param; -pub mod extract_plugin; -pub mod extract_resource; +pub mod extract_component { + pub type ExtractComponentPlugin = + bevy_extract::extract_component::ExtractComponentPlugin; + + pub use crate::uniform::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}; + + pub use bevy_extract::extract_component::ExtractComponent; +} +// pub mod extract_instances; +// mod extract_param; +pub mod extract_plugin { + pub use bevy_extract::extract_plugin::ExtractPlugin; +} +pub mod extract_resource { + pub type ExtractResourcePlugin = + bevy_extract::extract_resource::ExtractResourcePlugin; + + pub use bevy_extract::extract_resource::{extract_resource, ExtractResource}; +} pub mod globals; pub mod gpu_component_array_buffer; pub mod gpu_readback; @@ -60,8 +74,21 @@ pub mod renderer; pub mod settings; pub mod slab_allocator; pub mod storage; -pub mod sync_component; -pub mod sync_world; +pub mod sync_component { + pub type SyncComponentPlugin = + bevy_extract::sync_component::SyncComponentPlugin; + + pub use bevy_extract::sync_component::SyncComponent; +} +pub mod sync_world { + pub type SyncToRenderWorld = bevy_extract::sync_world::SyncToSubWorld; + + pub type RenderEntity = bevy_extract::sync_world::SubEntity; + + pub type TemporaryRenderEntity = bevy_extract::sync_world::TemporaryEntity; + + pub use bevy_extract::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet}; +} pub mod texture; pub mod uniform; pub mod view; @@ -76,14 +103,14 @@ pub mod prelude { view::Msaa, ExtractSchedule, }; } - -pub use extract_param::Extract; -pub use extract_plugin::{ExtractSchedule, MainWorld}; +pub use bevy_extract::{ + extract_param::Extract, + extract_plugin::{ExtractSchedule, MainWorld}, +}; use crate::{ camera::CameraPlugin, error_handler::{RenderErrorHandler, RenderState}, - extract_plugin::ExtractPlugin, gpu_readback::GpuReadbackPlugin, mesh::{MeshRenderAssetPlugin, RenderMesh}, render_asset::prepare_assets, @@ -103,6 +130,7 @@ use bevy_ecs::{ prelude::*, schedule::{InternedScheduleLabel, ScheduleLabel}, }; +use bevy_extract::ExtractPlugin; use bevy_platform::time::Instant; use bevy_shader::{load_shader_library, Shader, ShaderLoader}; use bevy_time::TimeSender; @@ -342,7 +370,7 @@ impl Render { pub(crate) struct FutureRenderResources(Arc>>); /// A label for the rendering sub-app. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel, Default)] pub struct RenderApp; impl Plugin for RenderPlugin { @@ -357,9 +385,13 @@ impl Plugin for RenderPlugin { if insert_future_resources(&self.render_creation, app.world_mut()) { // We only create the render world and set up extraction if we // have a rendering backend available. - app.add_plugins(ExtractPlugin { - pre_extract: error_handler::update_state, - }); + app.add_plugins(ExtractPlugin::::new( + error_handler::update_state, + Render::base_schedule, + Render.intern(), + RenderSystems::ExtractCommands.intern(), + RenderSystems::PostCleanup.intern(), + )); }; app.add_plugins(( diff --git a/crates/bevy_render/src/occlusion_culling/mod.rs b/crates/bevy_render/src/occlusion_culling/mod.rs index 531a93930e78b..5803750bccab4 100644 --- a/crates/bevy_render/src/occlusion_culling/mod.rs +++ b/crates/bevy_render/src/occlusion_culling/mod.rs @@ -8,7 +8,7 @@ use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_shader::load_shader_library; -use crate::{extract_component::ExtractComponent, render_resource::TextureView}; +use crate::{extract_component::ExtractComponent, render_resource::TextureView, RenderApp}; /// Enables GPU occlusion culling. /// @@ -69,6 +69,7 @@ impl Plugin for OcclusionCullingPlugin { /// https://medium.com/@mil_kru/two-pass-occlusion-culling-4100edcad501 #[derive(Component, ExtractComponent, Clone, Copy, Default, Reflect)] #[reflect(Component, Default, Clone)] +#[extract_app(RenderApp)] pub struct OcclusionCulling; /// A render-world component that contains resources necessary to perform diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index 9806ff45015cd..005a3d3a8854c 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -1,6 +1,7 @@ use async_channel::{Receiver, Sender}; -use bevy_app::{App, AppExit, AppLabel, Plugin, SubApp}; +use bevy_app::{App, AppExit, Plugin, SubApp}; +use bevy_derive::AppLabel; use bevy_ecs::{ resource::Resource, schedule::MainThreadExecutor, @@ -14,7 +15,7 @@ use crate::RenderApp; /// /// The Main schedule of this app can be used to run logic after the render schedule starts, but /// before I/O processing. This can be useful for something like frame pacing. -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel, Default)] pub struct RenderExtractApp; /// Channels used by the main app to send and receive the render app. @@ -104,7 +105,7 @@ impl Drop for RenderAppChannels { /// - And finally the `main app schedule` is run. /// - Once both the `main app schedule` and the `render schedule` are finished running, `extract` is run again. /// -/// [`SyncWorldPlugin`]: crate::sync_world::SyncWorldPlugin +/// [`SyncWorldPlugin`]: bevy_extract::sync_world::SyncWorldPlugin #[derive(Default)] pub struct PipelinedRenderingPlugin; diff --git a/crates/bevy_render/src/texture/manual_texture_view.rs b/crates/bevy_render/src/texture/manual_texture_view.rs index 2202071a51168..67b7ade119b65 100644 --- a/crates/bevy_render/src/texture/manual_texture_view.rs +++ b/crates/bevy_render/src/texture/manual_texture_view.rs @@ -1,11 +1,11 @@ use bevy_camera::ManualTextureViewHandle; use bevy_ecs::resource::Resource; +use bevy_extract_macros::ExtractResource; use bevy_math::UVec2; use bevy_platform::collections::HashMap; -use bevy_render_macros::ExtractResource; use wgpu::TextureFormat; -use crate::render_resource::TextureView; +use crate::{render_resource::TextureView, RenderApp}; /// A manually managed [`TextureView`] for use as a [`bevy_camera::RenderTarget`]. #[derive(Debug, Clone)] @@ -50,6 +50,7 @@ impl ManualTextureView { /// ``` /// Bevy will then use the `ManualTextureViews` resource to find your texture view and render to it. #[derive(Default, Clone, Resource, ExtractResource)] +#[extract_app(RenderApp)] pub struct ManualTextureViews(HashMap); impl core::ops::Deref for ManualTextureViews { diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index ddabb60e7d408..477488d52bcb6 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -29,11 +29,11 @@ use bevy_app::{App, Plugin}; use bevy_color::{LinearRgba, Oklaba, Srgba}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{prelude::*, VariantDefaults}; +use bevy_extract_macros::ExtractComponent; use bevy_image::ToExtents; use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles}; use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render_macros::ExtractComponent; use bevy_shader::load_shader_library; use bevy_transform::components::GlobalTransform; use core::{ @@ -237,6 +237,7 @@ impl Plugin for ViewPlugin { Debug, )] #[reflect(Component, Default, PartialEq, Hash, Debug)] +#[extract_app(RenderApp)] pub enum Msaa { Off = 1, Sample2 = 2, diff --git a/crates/bevy_solari/src/scene/extract.rs b/crates/bevy_solari/src/scene/extract.rs index deb59e804317a..bf0b629a76bd4 100644 --- a/crates/bevy_solari/src/scene/extract.rs +++ b/crates/bevy_solari/src/scene/extract.rs @@ -7,7 +7,9 @@ use bevy_ecs::{ }; use bevy_pbr::{MeshMaterial3d, PreviousGlobalTransform, StandardMaterial}; use bevy_platform::collections::HashMap; -use bevy_render::{extract_resource::ExtractResource, sync_world::RenderEntity, Extract}; +use bevy_render::{ + extract_resource::ExtractResource, sync_world::RenderEntity, Extract, RenderApp, +}; use bevy_transform::components::GlobalTransform; pub fn extract_raytracing_scene( @@ -40,7 +42,7 @@ pub fn extract_raytracing_scene( #[derive(Resource, Deref, Default)] pub struct StandardMaterialAssets(HashMap, StandardMaterial>); -impl ExtractResource for StandardMaterialAssets { +impl ExtractResource for StandardMaterialAssets { type Source = Assets; fn extract_resource(source: &Self::Source) -> Self { diff --git a/crates/bevy_sprite_render/Cargo.toml b/crates/bevy_sprite_render/Cargo.toml index bbec3d8dc8f01..560f236a2657c 100644 --- a/crates/bevy_sprite_render/Cargo.toml +++ b/crates/bevy_sprite_render/Cargo.toml @@ -20,6 +20,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.19.0-dev" } bevy_color = { path = "../bevy_color", version = "0.19.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } bevy_camera = { path = "../bevy_camera", version = "0.19.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.19.0-dev" } diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index ec844c40e4ae5..5c6ac319faa5b 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -33,7 +33,6 @@ use bevy_render::{ allocator::{MeshAllocator, MeshSlabId, MeshSlabs}, RenderMesh, }, - prelude::*, render_asset::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, @@ -49,7 +48,8 @@ use bevy_render::{ view::{ ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, }, - Extract, GpuResourceAppExt, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, + Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderDebugFlags, + RenderStartup, RenderSystems, }; use bevy_shader::Shader; use core::{hash::Hash, ops::Range}; @@ -421,6 +421,7 @@ pub struct NoWireframe2d; #[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] #[reflect(Resource, Debug, Default)] +#[extract_app(RenderApp)] pub struct Wireframe2dConfig { /// Whether to show wireframes for all meshes. /// Can be overridden for individual meshes by adding a [`Wireframe2d`] or [`NoWireframe2d`] component. diff --git a/crates/bevy_sprite_render/src/text2d/mod.rs b/crates/bevy_sprite_render/src/text2d/mod.rs index 3d441cc74a6a1..574d11392c7d2 100644 --- a/crates/bevy_sprite_render/src/text2d/mod.rs +++ b/crates/bevy_sprite_render/src/text2d/mod.rs @@ -86,7 +86,7 @@ pub fn extract_text2d_sprite( let Ok(text_background_color) = text_background_colors_query.get(section_entity) else { continue; }; - let render_entity = commands.spawn(TemporaryRenderEntity).id(); + let render_entity = commands.spawn(TemporaryRenderEntity::default()).id(); let offset = run.bounds.center(); let transform = *global_transform * GlobalTransform::from_translation(top_left.extend(0.)) @@ -135,7 +135,7 @@ pub fn extract_text2d_sprite( .get(i + 1) .is_none_or(|info| info.atlas_info.texture != atlas_info.texture) { - let render_entity = commands.spawn(TemporaryRenderEntity).id(); + let render_entity = commands.spawn(TemporaryRenderEntity::default()).id(); extracted_sprites.sprites.push(ExtractedSprite { main_entity, render_entity, @@ -163,7 +163,7 @@ pub fn extract_text2d_sprite( }; if has_strikethrough { - let render_entity = commands.spawn(TemporaryRenderEntity).id(); + let render_entity = commands.spawn(TemporaryRenderEntity::default()).id(); let offset = run.strikethrough_position(); let transform = shadow_transform * GlobalTransform::from_translation(offset.extend(0.)); @@ -185,7 +185,7 @@ pub fn extract_text2d_sprite( } if has_underline { - let render_entity = commands.spawn(TemporaryRenderEntity).id(); + let render_entity = commands.spawn(TemporaryRenderEntity::default()).id(); let offset = run.underline_position(); let transform = shadow_transform * GlobalTransform::from_translation(offset.extend(0.)); @@ -246,7 +246,7 @@ pub fn extract_text2d_sprite( info.section_index != current_section || info.atlas_info.texture != atlas_info.texture }) { - let render_entity = commands.spawn(TemporaryRenderEntity).id(); + let render_entity = commands.spawn(TemporaryRenderEntity::default()).id(); extracted_sprites.sprites.push(ExtractedSprite { main_entity, render_entity, @@ -282,7 +282,7 @@ pub fn extract_text2d_sprite( .map(|c| c.0) .unwrap_or(text_color.0) .to_linear(); - let render_entity = commands.spawn(TemporaryRenderEntity).id(); + let render_entity = commands.spawn(TemporaryRenderEntity::default()).id(); let offset = run.strikethrough_position(); let transform = *global_transform * GlobalTransform::from_translation(top_left.extend(0.)) @@ -310,7 +310,7 @@ pub fn extract_text2d_sprite( .map(|c| c.0) .unwrap_or(text_color.0) .to_linear(); - let render_entity = commands.spawn(TemporaryRenderEntity).id(); + let render_entity = commands.spawn(TemporaryRenderEntity::default()).id(); let offset = run.underline_position(); let transform = *global_transform * GlobalTransform::from_translation(top_left.extend(0.)) diff --git a/crates/bevy_ui_render/Cargo.toml b/crates/bevy_ui_render/Cargo.toml index 88ec27815fb98..29c7523e42797 100644 --- a/crates/bevy_ui_render/Cargo.toml +++ b/crates/bevy_ui_render/Cargo.toml @@ -17,6 +17,7 @@ bevy_color = { path = "../bevy_color", version = "0.19.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } +bevy_extract = { path = "../bevy_extract", version = "0.19.0-dev" } bevy_image = { path = "../bevy_image", version = "0.19.0-dev" } bevy_input_focus = { path = "../bevy_input_focus", version = "0.19.0-dev" } bevy_math = { path = "../bevy_math", version = "0.19.0-dev" } diff --git a/crates/bevy_ui_render/src/box_shadow.rs b/crates/bevy_ui_render/src/box_shadow.rs index 4d6d2419cab99..030ae69669a3b 100644 --- a/crates/bevy_ui_render/src/box_shadow.rs +++ b/crates/bevy_ui_render/src/box_shadow.rs @@ -274,7 +274,7 @@ pub fn extract_shadows( }; extracted_box_shadows.box_shadows.push(ExtractedBoxShadow { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), stack_index: stack_index.0, transform: Affine2::from(transform) * Affine2::from_translation(offset), color: drop_shadow.color.into(), diff --git a/crates/bevy_ui_render/src/debug_overlay.rs b/crates/bevy_ui_render/src/debug_overlay.rs index 304b36e496571..73138c4f7ed3d 100644 --- a/crates/bevy_ui_render/src/debug_overlay.rs +++ b/crates/bevy_ui_render/src/debug_overlay.rs @@ -218,7 +218,7 @@ pub fn extract_debug_overlay( } extracted_uinodes.uinodes.push(ExtractedUiNode { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), // Keep all overlays above UI, and nudge each type slightly in Z so ordering is stable. z_order, clip: maybe_clip diff --git a/crates/bevy_ui_render/src/gradient.rs b/crates/bevy_ui_render/src/gradient.rs index 0642e99769bdf..13fc7155e4b4d 100644 --- a/crates/bevy_ui_render/src/gradient.rs +++ b/crates/bevy_ui_render/src/gradient.rs @@ -416,7 +416,7 @@ pub fn extract_gradients( node_type, }, main_entity: entity.into(), - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), }); continue; } @@ -440,7 +440,7 @@ pub fn extract_gradients( ); extracted_gradients.items.push(ExtractedGradient { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), stack_index: stack_index.0, transform: transform.into(), stops_range: range_start..extracted_color_stops.0.len(), @@ -490,7 +490,7 @@ pub fn extract_gradients( ); extracted_gradients.items.push(ExtractedGradient { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), stack_index: stack_index.0, transform: transform.into(), stops_range: range_start..extracted_color_stops.0.len(), @@ -546,7 +546,7 @@ pub fn extract_gradients( ); extracted_gradients.items.push(ExtractedGradient { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), stack_index: stack_index.0, transform: transform.into(), stops_range: range_start..extracted_color_stops.0.len(), diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs index 3cd969e578a97..eb9c84f938f0c 100644 --- a/crates/bevy_ui_render/src/lib.rs +++ b/crates/bevy_ui_render/src/lib.rs @@ -433,7 +433,7 @@ pub fn extract_uinode_background_colors( if !background_color.is_fully_transparent() { extracted_uinodes.uinodes.push(ExtractedUiNode { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), z_order: stack_index.0 as f32 + stack_z_offsets::BACKGROUND_COLOR, clip: clip.map(|clip| clip.clip), image: AssetId::default(), @@ -460,7 +460,7 @@ pub fn extract_uinode_background_colors( && !outer_color.0.is_fully_transparent() { extracted_uinodes.uinodes.push(ExtractedUiNode { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), z_order: stack_index.0 as f32 + stack_z_offsets::BACKGROUND_COLOR, clip: clip.map(|clip| clip.clip), image: AssetId::default(), @@ -578,7 +578,7 @@ pub fn extract_uinode_images( extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::IMAGE, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), clip: clip.map(|clip| clip.clip), image: image.image.id(), extracted_camera_entity, @@ -697,7 +697,7 @@ pub fn extract_uinode_borders( node_type: NodeType::Border(border_flags), }, main_entity: entity.into(), - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), }); } } @@ -711,7 +711,7 @@ pub fn extract_uinode_borders( let outline_size = computed_node.outlined_node_size(); extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::BORDER, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), image, clip: maybe_clip.map(|clip| clip.clip), extracted_camera_entity, @@ -853,7 +853,7 @@ pub fn extract_ui_camera_view( }, // Link to the main camera view. UiViewTarget(render_entity), - TemporaryRenderEntity, + TemporaryRenderEntity::default(), )) .id(); @@ -929,7 +929,7 @@ pub fn extract_viewport_nodes( extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::IMAGE, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), clip: clip.map(|clip| clip.clip), image: image.id(), extracted_camera_entity, @@ -1079,7 +1079,7 @@ pub fn extract_text_sections( { extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::TEXT, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), image: atlas_info.texture, clip, extracted_camera_entity, @@ -1183,7 +1183,7 @@ pub fn extract_text_shadows( extracted_uinodes.uinodes.push(ExtractedUiNode { transform: node_transform, z_order: stack_index.0 as f32 + stack_z_offsets::TEXT, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), image: atlas_info.texture, clip, extracted_camera_entity, @@ -1212,7 +1212,7 @@ pub fn extract_text_shadows( if has_strikethrough { extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::TEXT, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), clip, image: AssetId::default(), extracted_camera_entity, @@ -1238,7 +1238,7 @@ pub fn extract_text_shadows( if has_underline { extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::TEXT, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), clip, image: AssetId::default(), extracted_camera_entity, @@ -1350,7 +1350,7 @@ pub fn extract_text_decorations( if let Some(text_background_color) = text_background_color { extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::TEXT, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), clip, image: AssetId::default(), extracted_camera_entity, @@ -1380,7 +1380,7 @@ pub fn extract_text_decorations( extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::TEXT_STRIKETHROUGH, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), clip, image: AssetId::default(), extracted_camera_entity, @@ -1410,7 +1410,7 @@ pub fn extract_text_decorations( extracted_uinodes.uinodes.push(ExtractedUiNode { z_order: stack_index.0 as f32 + stack_z_offsets::TEXT_STRIKETHROUGH, - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), clip, image: AssetId::default(), extracted_camera_entity, diff --git a/crates/bevy_ui_render/src/text.rs b/crates/bevy_ui_render/src/text.rs index 989f08d2a0949..50735ef30cb27 100644 --- a/crates/bevy_ui_render/src/text.rs +++ b/crates/bevy_ui_render/src/text.rs @@ -137,7 +137,7 @@ pub fn extract_text_cursor( } extracted_uinodes.uinodes.push(ExtractedUiNode { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), z_order: stack_index.0 as f32 + stack_z_offsets::TEXT_SELECTION, clip, image: AssetId::default(), @@ -166,7 +166,7 @@ pub fn extract_text_cursor( && !cursor_style.color.is_fully_transparent() { extracted_uinodes.uinodes.push(ExtractedUiNode { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), z_order: stack_index.0 as f32 + stack_z_offsets::TEXT_CURSOR, clip, image: AssetId::default(), @@ -259,7 +259,7 @@ pub fn extract_preedit_underlines( for rect in text_layout_info.preedit_underline_rects.iter() { extracted_uinodes.uinodes.push(ExtractedUiNode { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), z_order: stack_index.0 as f32 + stack_z_offsets::TEXT_STRIKETHROUGH, clip, image: AssetId::default(), diff --git a/crates/bevy_ui_render/src/ui_material.rs b/crates/bevy_ui_render/src/ui_material.rs index e716a600c8c02..7fa6d8b8b7a9c 100644 --- a/crates/bevy_ui_render/src/ui_material.rs +++ b/crates/bevy_ui_render/src/ui_material.rs @@ -6,6 +6,7 @@ use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ extract_component::ExtractComponent, render_resource::{AsBindGroup, RenderPipelineDescriptor, TextureFormat}, + RenderApp, }; use bevy_shader::ShaderRef; use derive_more::derive::From; @@ -177,6 +178,7 @@ where )] #[reflect(Component, Default)] #[require(Node)] +#[extract_app(RenderApp)] pub struct MaterialNode(pub Handle); impl Default for MaterialNode { diff --git a/crates/bevy_ui_render/src/ui_material_pipeline.rs b/crates/bevy_ui_render/src/ui_material_pipeline.rs index e0f17d64fb262..d9cbbb520c333 100644 --- a/crates/bevy_ui_render/src/ui_material_pipeline.rs +++ b/crates/bevy_ui_render/src/ui_material_pipeline.rs @@ -364,7 +364,7 @@ pub fn extract_ui_material_nodes( }; extracted_uinodes.uinodes.push(ExtractedUiMaterialNode { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), stack_index: stack_index.0, transform: transform.into(), material: handle.id(), diff --git a/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs b/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs index 7f8240686574d..fa6f4f3aff48b 100644 --- a/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui_render/src/ui_texture_slice_pipeline.rs @@ -286,7 +286,7 @@ pub fn extract_ui_texture_slices( }; extracted_ui_slicers.slices.push(ExtractedUiTextureSlice { - render_entity: commands.spawn(TemporaryRenderEntity).id(), + render_entity: commands.spawn(TemporaryRenderEntity::default()).id(), stack_index: stack_index.0, transform: Affine2::from(*transform) * Affine2::from_translation(visual_box.center()), color: image.color.into(), diff --git a/docs/cargo_features.md b/docs/cargo_features.md index f2438b311c3c9..75b1a2cf8e3e1 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -79,6 +79,7 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio |bevy_core_pipeline|Provides cameras and other basic render pipeline features| |bevy_debug_stepping|Enable stepping-based debugging of Bevy systems| |bevy_dev_tools|Provides a collection of developer tools| +|bevy_extract|Adds extract| |bevy_feathers|Feathers widget collection.| |bevy_gilrs|Adds gamepad support| |bevy_gizmos|Adds support for gizmos| diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 4ffcad69a6ba1..39c6d0c51f16c 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -125,7 +125,7 @@ fn star( #[derive(Component, Default)] pub struct ColoredMesh2d; -impl SyncComponent for ColoredMesh2d { +impl SyncComponent for ColoredMesh2d { type Target = Self; } diff --git a/examples/app/render_recovery.rs b/examples/app/render_recovery.rs index 25ad2b6dfeac1..ed63e4a469617 100644 --- a/examples/app/render_recovery.rs +++ b/examples/app/render_recovery.rs @@ -94,6 +94,7 @@ fn update_camera(mut camera: Query<&mut Transform, With>, time: Res