diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f03573372277..a658d35e7f0ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,7 +103,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: nightly-2020-12-07 components: rustfmt, clippy override: true @@ -114,7 +114,7 @@ jobs: run: sudo apt-get update; sudo apt-get install --no-install-recommends libudev-dev - name: Check the format - run: cargo +nightly fmt --all -- --check + run: cargo +nightly-2020-12-07 fmt --all -- --check # type complexity must be ignored because we use huge templates for queries # -A clippy::manual-strip: strip_prefix support was added in 1.45. we want to support earlier rust versions diff --git a/.gitignore b/.gitignore index e690fffaad245..48bbc7e0e1c91 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ crates/*/target **/*.rs.bk Cargo.lock .cargo/config +.cargo/config.toml /.idea /.vscode -/benches/target \ No newline at end of file +/benches/target diff --git a/CHANGELOG.md b/CHANGELOG.md index 39da8e5e61283..2b6ae7a4aa7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,217 @@ While we try to keep the `Unreleased` changes updated, it is often behind and do all merged pull requests. To see a list of all changes since the latest release, you may compare current changes on git with [previous release tags][git_tag_comparison]. -[git_tag_comparison]: https://github.com/bevyengine/bevy/compare/v0.3.0...master +[git_tag_comparison]: https://github.com/bevyengine/bevy/compare/v0.4.0...master -## Unreleased +## Version 0.4.0 (2020-12-19) ### Added +- [add bevymark benchmark example][273] +- [gltf: support camera and fix hierarchy][772] +- [Add tracing spans to schedules, stages, systems][789] +- [add example that represents contributors as bevy icons][801] +- [Add received character][805] +- [Add bevy_dylib to force dynamic linking of bevy][808] +- [Added RenderPass::set_scissor_rect][815] +- [`bevy_log`][836] + - Adds logging functionality as a Plugin. + - Changes internal logging to work with the new implementation. +- [cross-platform main function][847] +- [Controllable ambient light color][852] + - Added a resource to change the current ambient light color for PBR. +- [Added more basic color constants][859] +- [Add box shape][883] +- [Expose an EventId for events][894] +- [System Inputs, Outputs, and Chaining][876] +- [Expose an `EventId` for events][894] +- [Added `set_cursor_position` to `Window`][917] +- [Added new Bevy reflection system][926] + - Replaces the properties system +- [Add support for Apple Silicon][928] +- [Live reloading of shaders][937] +- [ Store mouse cursor position in Window][940] +- [Add removal_detection example][945] +- [Additional vertex attribute value types][946] +- [Added WindowFocused event][956] +- [Tracing chrome span names][979] +- [Allow windows to be maximized][1004] +- [GLTF: load default material][1016] +- [can spawn a scene from a ChildBuilder, or directly set its parent when spawning it][1026] +- [add ability to load `.dds`, `.tga`, and `.jpeg` texture formats][1038] +- [add ability to provide custom a `AssetIo` implementation][1037] ### Changed +- [delegate layout reflection to RenderResourceContext][691] +- [Fall back to remove components one by one when failing to remove a bundle][719] +- [Port hecs derive macro improvements][761] +- [Use glyph_brush_layout and add text alignment support][765] +- [upgrade glam and hexasphere][791] +- [Flexible ECS Params][798] +- [Make Timer.tick return &Self][820] +- [FileAssetIo includes full path on error][821] +- [Removed ECS query APIs that could easily violate safety from the public interface][829] +- [Changed Query filter API to be easier to understand][834] +- [bevy_render: delegate buffer aligning to render_resource_context][842] +- [wasm32: non-spirv shader specialization][843] +- [Renamed XComponents to XBundle][863] +- [Check for conflicting system resource parameters][864] +- [Tweaks to TextureAtlasBuilder.finish()][887] +- [do not spend time drawing text with is_visible = false][893] +- [Extend the Texture asset type to support 3D data][903] +- [Breaking changes to timer API][914] + - Created getters and setters rather than exposing struct members. +- [Removed timer auto-ticking system][931] + - Added an example of how to tick timers manually. +- [When a task scope produces <= 1 task to run, run it on the calling thread immediately][932] +- [Breaking changes to Time API][934] + - Created getters to get `Time` state and made members private. + - Modifying `Time`'s values directly is no longer possible outside of bevy. +- [Use `mailbox` instead of `fifo` for vsync on supported systems][920] +- [switch winit size to logical to be dpi independent][947] +- [Change bevy_input::Touch API to match similar APIs][952] +- [Run parent-update and transform-propagation during the "post-startup" stage (instead of "startup")][955] +- [Renderer Optimization Round 1][958] +- [Change`TextureAtlasBuilder` into expected Builder conventions][969] +- [Optimize Text rendering / SharedBuffers][972] +- [hidpi swap chains][973] +- [optimize asset gpu data transfer][987] +- [naming coherence for cameras][995] +- [Schedule v2][1021] +- [Use shaderc for aarch64-apple-darwin][1027] +- [update `Window`'s `width` & `height` methods to return `f32`][1033] +- [Break out Visible component from Draw][1034] + - Users setting `Draw::is_visible` or `Draw::is_transparent` should now set `Visible::is_visible` and `Visible::is_transparent` +- [`winit` upgraded from version 0.23 to version 0.24][1043] +- [set is_transparent to true by default for UI bundles][1071] + ### Fixed +- [Fixed typos in KeyCode identifiers][857] +- [Remove redundant texture copies in TextureCopyNode][871] +- [Fix a deadlock that can occur when using scope() on ComputeTaskPool from within a system][892] +- [Don't draw text that isn't visible][893] +- [Use `instant::Instant` for WASM compatibility][895] +- [Fix pixel format conversion in bevy_gltf][897] +- [Fixed duplicated children when spawning a Scene][904] +- [Corrected behaviour of the UI depth system][905] +- [Allow despawning of hierarchies in threadlocal systems][908] +- [Fix `RenderResources` index slicing][948] +- [Run parent-update and transform-propagation during the "post-startup" stage][955] +- [Fix collision detection by comparing abs() penetration depth][966] +- [deal with rounding issue when creating the swap chain][997] +- [only update components for entities in map][1023] +- [Don't panic when attempting to set shader defs from an asset that hasn't loaded yet][1035] + +[273]: https://github.com/bevyengine/bevy/pull/273 +[691]: https://github.com/bevyengine/bevy/pull/691 +[719]: https://github.com/bevyengine/bevy/pull/719 +[761]: https://github.com/bevyengine/bevy/pull/761 +[761]: https://github.com/bevyengine/bevy/pull/761 +[765]: https://github.com/bevyengine/bevy/pull/765 +[772]: https://github.com/bevyengine/bevy/pull/772 +[772]: https://github.com/bevyengine/bevy/pull/772 +[789]: https://github.com/bevyengine/bevy/pull/789 +[791]: https://github.com/bevyengine/bevy/pull/791 +[798]: https://github.com/bevyengine/bevy/pull/798 +[801]: https://github.com/bevyengine/bevy/pull/801 +[801]: https://github.com/bevyengine/bevy/pull/801 +[805]: https://github.com/bevyengine/bevy/pull/805 +[808]: https://github.com/bevyengine/bevy/pull/808 +[815]: https://github.com/bevyengine/bevy/pull/815 +[820]: https://github.com/bevyengine/bevy/pull/820 +[821]: https://github.com/bevyengine/bevy/pull/821 +[821]: https://github.com/bevyengine/bevy/pull/821 +[829]: https://github.com/bevyengine/bevy/pull/829 +[829]: https://github.com/bevyengine/bevy/pull/829 +[834]: https://github.com/bevyengine/bevy/pull/834 +[834]: https://github.com/bevyengine/bevy/pull/834 +[836]: https://github.com/bevyengine/bevy/pull/836 +[836]: https://github.com/bevyengine/bevy/pull/836 +[842]: https://github.com/bevyengine/bevy/pull/842 +[843]: https://github.com/bevyengine/bevy/pull/843 +[847]: https://github.com/bevyengine/bevy/pull/847 +[852]: https://github.com/bevyengine/bevy/pull/852 +[852]: https://github.com/bevyengine/bevy/pull/852 +[857]: https://github.com/bevyengine/bevy/pull/857 +[857]: https://github.com/bevyengine/bevy/pull/857 +[859]: https://github.com/bevyengine/bevy/pull/859 +[859]: https://github.com/bevyengine/bevy/pull/859 +[863]: https://github.com/bevyengine/bevy/pull/863 +[864]: https://github.com/bevyengine/bevy/pull/864 +[871]: https://github.com/bevyengine/bevy/pull/871 +[876]: https://github.com/bevyengine/bevy/pull/876 +[876]: https://github.com/bevyengine/bevy/pull/876 +[883]: https://github.com/bevyengine/bevy/pull/883 +[887]: https://github.com/bevyengine/bevy/pull/887 +[892]: https://github.com/bevyengine/bevy/pull/892 +[893]: https://github.com/bevyengine/bevy/pull/893 +[893]: https://github.com/bevyengine/bevy/pull/893 +[893]: https://github.com/bevyengine/bevy/pull/893 +[894]: https://github.com/bevyengine/bevy/pull/894 +[894]: https://github.com/bevyengine/bevy/pull/894 +[894]: https://github.com/bevyengine/bevy/pull/894 +[895]: https://github.com/bevyengine/bevy/pull/895 +[895]: https://github.com/bevyengine/bevy/pull/895 +[897]: https://github.com/bevyengine/bevy/pull/897 +[903]: https://github.com/bevyengine/bevy/pull/903 +[904]: https://github.com/bevyengine/bevy/pull/904 +[904]: https://github.com/bevyengine/bevy/pull/904 +[905]: https://github.com/bevyengine/bevy/pull/905 +[905]: https://github.com/bevyengine/bevy/pull/905 +[908]: https://github.com/bevyengine/bevy/pull/908 +[914]: https://github.com/bevyengine/bevy/pull/914 +[914]: https://github.com/bevyengine/bevy/pull/914 +[917]: https://github.com/bevyengine/bevy/pull/917 +[917]: https://github.com/bevyengine/bevy/pull/917 +[920]: https://github.com/bevyengine/bevy/pull/920 +[920]: https://github.com/bevyengine/bevy/pull/920 +[926]: https://github.com/bevyengine/bevy/pull/926 +[926]: https://github.com/bevyengine/bevy/pull/926 +[928]: https://github.com/bevyengine/bevy/pull/928 +[928]: https://github.com/bevyengine/bevy/pull/928 +[931]: https://github.com/bevyengine/bevy/pull/931 +[931]: https://github.com/bevyengine/bevy/pull/931 +[932]: https://github.com/bevyengine/bevy/pull/932 +[934]: https://github.com/bevyengine/bevy/pull/934 +[934]: https://github.com/bevyengine/bevy/pull/934 +[937]: https://github.com/bevyengine/bevy/pull/937 +[940]: https://github.com/bevyengine/bevy/pull/940 +[945]: https://github.com/bevyengine/bevy/pull/945 +[945]: https://github.com/bevyengine/bevy/pull/945 +[946]: https://github.com/bevyengine/bevy/pull/946 +[947]: https://github.com/bevyengine/bevy/pull/947 +[948]: https://github.com/bevyengine/bevy/pull/948 +[952]: https://github.com/bevyengine/bevy/pull/952 +[955]: https://github.com/bevyengine/bevy/pull/955 +[955]: https://github.com/bevyengine/bevy/pull/955 +[955]: https://github.com/bevyengine/bevy/pull/955 +[956]: https://github.com/bevyengine/bevy/pull/956 +[958]: https://github.com/bevyengine/bevy/pull/958 +[966]: https://github.com/bevyengine/bevy/pull/966 +[969]: https://github.com/bevyengine/bevy/pull/969 +[972]: https://github.com/bevyengine/bevy/pull/972 +[973]: https://github.com/bevyengine/bevy/pull/973 +[979]: https://github.com/bevyengine/bevy/pull/979 +[987]: https://github.com/bevyengine/bevy/pull/987 +[995]: https://github.com/bevyengine/bevy/pull/995 +[997]: https://github.com/bevyengine/bevy/pull/997 +[1004]: https://github.com/bevyengine/bevy/pull/1004 +[1016]: https://github.com/bevyengine/bevy/pull/1016 +[1021]: https://github.com/bevyengine/bevy/pull/1021 +[1023]: https://github.com/bevyengine/bevy/pull/1023 +[1026]: https://github.com/bevyengine/bevy/pull/1026 +[1027]: https://github.com/bevyengine/bevy/pull/1027 +[1033]: https://github.com/bevyengine/bevy/pull/1033 +[1034]: https://github.com/bevyengine/bevy/pull/1034 +[1034]: https://github.com/bevyengine/bevy/pull/1034 +[1035]: https://github.com/bevyengine/bevy/pull/1035 +[1037]: https://github.com/bevyengine/bevy/pull/1037 +[1038]: https://github.com/bevyengine/bevy/pull/1038 +[1043]: https://github.com/bevyengine/bevy/pull/1043 +[1043]: https://github.com/bevyengine/bevy/pull/1043 +[1071]: https://github.com/bevyengine/bevy/pull/1071 ## Version 0.3.0 (2020-11-03) diff --git a/Cargo.toml b/Cargo.toml index 0e43e7daba87a..207cd12341165 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -55,6 +55,9 @@ wgpu_trace = ["bevy_internal/wgpu_trace"] # Image format support for texture loading (PNG and HDR are enabled by default) hdr = ["bevy_internal/hdr"] png = ["bevy_internal/png"] +dds = ["bevy_internal/dds"] +tga = ["bevy_internal/tga"] +jpeg = ["bevy_internal/jpeg"] # Audio format support (MP3 is enabled by default) flac = ["bevy_internal/flac"] @@ -69,13 +72,13 @@ wayland = ["bevy_internal/wayland"] x11 = ["bevy_internal/x11"] [dependencies] -bevy_dylib = {path = "crates/bevy_dylib", version = "0.3.0", default-features = false, optional = true} -bevy_internal = {path = "crates/bevy_internal", version = "0.3.0", default-features = false} +bevy_dylib = {path = "crates/bevy_dylib", version = "0.4.0", default-features = false, optional = true} +bevy_internal = {path = "crates/bevy_internal", version = "0.4.0", default-features = false} [dev-dependencies] anyhow = "1.0" rand = "0.7.3" -ron = "0.6" +ron = "0.6.2" serde = {version = "1", features = ["derive"]} [[example]] @@ -174,6 +177,10 @@ path = "examples/asset/asset_loading.rs" name = "custom_asset" path = "examples/asset/custom_asset.rs" +[[example]] +name = "custom_asset_io" +path = "examples/asset/custom_asset_io.rs" + [[example]] name = "audio" path = "examples/audio/audio.rs" @@ -190,14 +197,26 @@ path = "examples/diagnostics/print_diagnostics.rs" name = "event" path = "examples/ecs/event.rs" +[[example]] +name = "fixed_timestep" +path = "examples/ecs/fixed_timestep.rs" + [[example]] name = "startup_system" path = "examples/ecs/startup_system.rs" +[[example]] +name = "state" +path = "examples/ecs/state.rs" + [[example]] name = "system_chaining" path = "examples/ecs/system_chaining.rs" +[[example]] +name = "timers" +path = "examples/ecs/timers.rs" + [[example]] name = "ecs_guide" path = "examples/ecs/ecs_guide.rs" @@ -250,13 +269,29 @@ path = "examples/input/touch_input.rs" name = "touch_input_events" path = "examples/input/touch_input_events.rs" +[[example]] +name = "reflection" +path = "examples/reflection/reflection.rs" + +[[example]] +name = "reflection_types" +path = "examples/reflection/reflection_types.rs" + +[[example]] +name = "generic_reflection" +path = "examples/reflection/generic_reflection.rs" + +[[example]] +name = "trait_reflection" +path = "examples/reflection/trait_reflection.rs" + [[example]] name = "scene" path = "examples/scene/scene.rs" [[example]] -name = "properties" -path = "examples/scene/properties.rs" +name = "hot_shader_reloading" +path = "examples/shader/hot_shader_reloading.rs" [[example]] name = "mesh_custom_attribute" @@ -266,6 +301,10 @@ path = "examples/shader/mesh_custom_attribute.rs" name = "shader_custom_material" path = "examples/shader/shader_custom_material.rs" +[[example]] +name = "array_texture" +path = "examples/shader/array_texture.rs" + [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" @@ -306,6 +345,10 @@ path = "examples/window/multiple_windows.rs" name = "window_settings" path = "examples/window/window_settings.rs" +[[example]] +name = "removal_detection" +path = "examples/ecs/removal_detection.rs" + [[example]] name = "hello_wasm" path = "examples/wasm/hello_wasm.rs" diff --git a/assets/scenes/load_scene_example.scn b/assets/scenes/load_scene_example.scn index a68262197272b..5b3672715b2f4 100644 --- a/assets/scenes/load_scene_example.scn +++ b/assets/scenes/load_scene_example.scn @@ -3,16 +3,42 @@ entity: 0, components: [ { - "type": "ComponentB", - "map": { - "value": "hello", + "type": "bevy_transform::components::transform::Transform", + "struct": { + "translation": { + "type": "glam::f32::vec3::Vec3", + "value": (0.0, 0.0, 0.0), + }, + "rotation": { + "type": "glam::f32::quat::Quat", + "value": (0.0, 0.0, 0.0, 1.0), + }, + "scale": { + "type": "glam::f32::vec3::Vec3", + "value": (1.0, 1.0, 1.0), + }, }, }, { - "type": "ComponentA", - "map": { - "x": 1.0, - "y": 2.0, + "type": "scene::ComponentB", + "struct": { + "value": { + "type": "alloc::string::String", + "value": "hello", + }, + }, + }, + { + "type": "scene::ComponentA", + "struct": { + "x": { + "type": "f32", + "value": 1.0, + }, + "y": { + "type": "f32", + "value": 2.0, + }, }, }, ], @@ -21,10 +47,16 @@ entity: 1, components: [ { - "type": "ComponentA", - "map": { - "x": 3.0, - "y": 4.0, + "type": "scene::ComponentA", + "struct": { + "x": { + "type": "f32", + "value": 3.0, + }, + "y": { + "type": "f32", + "value": 4.0, + }, }, }, ], diff --git a/assets/shaders/hot.frag b/assets/shaders/hot.frag new file mode 100644 index 0000000000000..18c41c8cd5c04 --- /dev/null +++ b/assets/shaders/hot.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) out vec4 o_Target; + +layout(set = 2, binding = 0) uniform MyMaterial_color { + vec4 color; +}; + +void main() { + o_Target = color * 0.5; +} diff --git a/assets/shaders/hot.vert b/assets/shaders/hot.vert new file mode 100644 index 0000000000000..71a610e6e8463 --- /dev/null +++ b/assets/shaders/hot.vert @@ -0,0 +1,15 @@ +#version 450 + +layout(location = 0) in vec3 Vertex_Position; + +layout(set = 0, binding = 0) uniform Camera { + mat4 ViewProj; +}; + +layout(set = 1, binding = 0) uniform Transform { + mat4 Model; +}; + +void main() { + gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0); +} diff --git a/assets/textures/array_texture.png b/assets/textures/array_texture.png new file mode 100644 index 0000000000000..ab2c144e11247 Binary files /dev/null and b/assets/textures/array_texture.png differ diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index 47f4baf18144d..52a5b3030c1f0 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_app" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -17,9 +17,9 @@ trace = [] [dependencies] # bevy -bevy_derive = { path = "../bevy_derive", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_derive = { path = "../bevy_derive", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other serde = { version = "1.0", features = ["derive"] } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 61d447fd286f2..adf2091ef2932 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,5 +1,5 @@ use crate::app_builder::AppBuilder; -use bevy_ecs::{ParallelExecutor, Resources, Schedule, World}; +use bevy_ecs::{Resources, Schedule, World}; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -30,9 +30,6 @@ pub struct App { pub resources: Resources, pub runner: Box, pub schedule: Schedule, - pub executor: ParallelExecutor, - pub startup_schedule: Schedule, - pub startup_executor: ParallelExecutor, } impl Default for App { @@ -41,16 +38,12 @@ impl Default for App { world: Default::default(), resources: Default::default(), schedule: Default::default(), - executor: Default::default(), - startup_schedule: Default::default(), - startup_executor: ParallelExecutor::without_tracker_clears(), runner: Box::new(run_once), } } } fn run_once(mut app: App) { - app.initialize(); app.update(); } @@ -61,34 +54,15 @@ impl App { pub fn update(&mut self) { self.schedule - .initialize(&mut self.world, &mut self.resources); - self.executor - .run(&mut self.schedule, &mut self.world, &mut self.resources); - } - - pub fn initialize(&mut self) { - #[cfg(feature = "trace")] - let startup_schedule_span = info_span!("startup_schedule"); - #[cfg(feature = "trace")] - let _startup_schedule_guard = startup_schedule_span.enter(); - self.startup_schedule - .initialize(&mut self.world, &mut self.resources); - self.startup_executor.initialize(&mut self.resources); - self.startup_executor.run( - &mut self.startup_schedule, - &mut self.world, - &mut self.resources, - ); + .initialize_and_run(&mut self.world, &mut self.resources); } pub fn run(mut self) { #[cfg(feature = "trace")] - let bevy_app_run_span = info_span!("bevy_app_run"); + let bevy_app_run_span = info_span!("bevy_app"); #[cfg(feature = "trace")] let _bevy_app_run_guard = bevy_app_run_span.enter(); - self.executor.initialize(&mut self.resources); - let runner = std::mem::replace(&mut self.runner, Box::new(run_once)); (runner)(self); } diff --git a/crates/bevy_app/src/app_builder.rs b/crates/bevy_app/src/app_builder.rs index 68e0063da74c7..fb69222b9b5ef 100644 --- a/crates/bevy_app/src/app_builder.rs +++ b/crates/bevy_app/src/app_builder.rs @@ -4,7 +4,10 @@ use crate::{ plugin::Plugin, stage, startup_stage, PluginGroup, PluginGroupBuilder, }; -use bevy_ecs::{FromResources, IntoSystem, Resources, System, World}; +use bevy_ecs::{ + clear_trackers_system, FromResources, IntoSystem, Resource, Resources, RunOnce, Schedule, + Stage, StateStage, System, SystemStage, World, +}; use bevy_utils::tracing::debug; /// Configure [App]s using the builder pattern @@ -18,8 +21,10 @@ impl Default for AppBuilder { app: App::default(), }; - app_builder.add_default_stages(); - app_builder.add_event::(); + app_builder + .add_default_stages() + .add_event::() + .add_system_to_stage(stage::LAST, clear_trackers_system.system()); app_builder } } @@ -49,124 +54,155 @@ impl AppBuilder { self } - pub fn add_stage(&mut self, stage_name: &'static str) -> &mut Self { - self.app.schedule.add_stage(stage_name); + pub fn add_stage(&mut self, name: &'static str, stage: S) -> &mut Self { + self.app.schedule.add_stage(name, stage); self } - pub fn add_stage_after(&mut self, target: &'static str, stage_name: &'static str) -> &mut Self { - self.app.schedule.add_stage_after(target, stage_name); + pub fn add_stage_after( + &mut self, + target: &'static str, + name: &'static str, + stage: S, + ) -> &mut Self { + self.app.schedule.add_stage_after(target, name, stage); self } - pub fn add_stage_before( + pub fn add_stage_before( &mut self, target: &'static str, - stage_name: &'static str, + name: &'static str, + stage: S, ) -> &mut Self { - self.app.schedule.add_stage_before(target, stage_name); + self.app.schedule.add_stage_before(target, name, stage); self } - pub fn add_startup_stage(&mut self, stage_name: &'static str) -> &mut Self { - self.app.startup_schedule.add_stage(stage_name); + pub fn add_startup_stage(&mut self, name: &'static str, stage: S) -> &mut Self { + self.app + .schedule + .stage(stage::STARTUP, |schedule: &mut Schedule| { + schedule.add_stage(name, stage) + }); self } - pub fn add_startup_stage_after( + pub fn add_startup_stage_after( &mut self, target: &'static str, - stage_name: &'static str, + name: &'static str, + stage: S, ) -> &mut Self { self.app - .startup_schedule - .add_stage_after(target, stage_name); + .schedule + .stage(stage::STARTUP, |schedule: &mut Schedule| { + schedule.add_stage_after(target, name, stage) + }); self } - pub fn add_startup_stage_before( + pub fn add_startup_stage_before( &mut self, target: &'static str, - stage_name: &'static str, + name: &'static str, + stage: S, ) -> &mut Self { self.app - .startup_schedule - .add_stage_before(target, stage_name); + .schedule + .stage(stage::STARTUP, |schedule: &mut Schedule| { + schedule.add_stage_before(target, name, stage) + }); self } - pub fn add_system(&mut self, system: IntoS) -> &mut Self - where - S: System, - IntoS: IntoSystem, - { + pub fn stage &mut T>( + &mut self, + name: &str, + func: F, + ) -> &mut Self { + self.app.schedule.stage(name, func); + self + } + + pub fn add_system>(&mut self, system: S) -> &mut Self { self.add_system_to_stage(stage::UPDATE, system) } - pub fn add_startup_system_to_stage( + pub fn on_state_enter>( + &mut self, + stage: &str, + state: T, + system: S, + ) -> &mut Self { + self.stage(stage, |stage: &mut StateStage| { + stage.on_state_enter(state, system) + }) + } + + pub fn on_state_update>( + &mut self, + stage: &str, + state: T, + system: S, + ) -> &mut Self { + self.stage(stage, |stage: &mut StateStage| { + stage.on_state_update(state, system) + }) + } + + pub fn on_state_exit>( + &mut self, + stage: &str, + state: T, + system: S, + ) -> &mut Self { + self.stage(stage, |stage: &mut StateStage| { + stage.on_state_exit(state, system) + }) + } + + pub fn add_startup_system_to_stage>( &mut self, stage_name: &'static str, - system: IntoS, - ) -> &mut Self - where - S: System, - IntoS: IntoSystem, - { + system: S, + ) -> &mut Self { self.app - .startup_schedule - .add_system_to_stage(stage_name, system); + .schedule + .stage(stage::STARTUP, |schedule: &mut Schedule| { + schedule.add_system_to_stage(stage_name, system) + }); self } - pub fn add_startup_system(&mut self, system: IntoS) -> &mut Self - where - S: System, - IntoS: IntoSystem, - { - self.app - .startup_schedule - .add_system_to_stage(startup_stage::STARTUP, system); - self + pub fn add_startup_system>(&mut self, system: S) -> &mut Self { + self.add_startup_system_to_stage(startup_stage::STARTUP, system) } pub fn add_default_stages(&mut self) -> &mut Self { - self.add_startup_stage(startup_stage::PRE_STARTUP) - .add_startup_stage(startup_stage::STARTUP) - .add_startup_stage(startup_stage::POST_STARTUP) - .add_stage(stage::FIRST) - .add_stage(stage::PRE_EVENT) - .add_stage(stage::EVENT) - .add_stage(stage::PRE_UPDATE) - .add_stage(stage::UPDATE) - .add_stage(stage::POST_UPDATE) - .add_stage(stage::LAST) - } - - pub fn add_system_to_stage( - &mut self, - stage_name: &'static str, - system: IntoS, - ) -> &mut Self - where - S: System, - IntoS: IntoSystem, - { - self.app.schedule.add_system_to_stage(stage_name, system); - self + self.add_stage( + stage::STARTUP, + Schedule::default() + .with_run_criteria(RunOnce::default()) + .with_stage(startup_stage::PRE_STARTUP, SystemStage::parallel()) + .with_stage(startup_stage::STARTUP, SystemStage::parallel()) + .with_stage(startup_stage::POST_STARTUP, SystemStage::parallel()), + ) + .add_stage(stage::FIRST, SystemStage::parallel()) + .add_stage(stage::PRE_EVENT, SystemStage::parallel()) + .add_stage(stage::EVENT, SystemStage::parallel()) + .add_stage(stage::PRE_UPDATE, SystemStage::parallel()) + .add_stage(stage::UPDATE, SystemStage::parallel()) + .add_stage(stage::POST_UPDATE, SystemStage::parallel()) + .add_stage(stage::LAST, SystemStage::parallel()) } - pub fn add_system_to_stage_front( + pub fn add_system_to_stage>( &mut self, stage_name: &'static str, - system: IntoS, - ) -> &mut Self - where - S: System, - IntoS: IntoSystem, - { - self.app - .schedule - .add_system_to_stage_front(stage_name, system.system()); + system: S, + ) -> &mut Self { + self.app.schedule.add_system_to_stage(stage_name, system); self } @@ -175,7 +211,7 @@ impl AppBuilder { T: Send + Sync + 'static, { self.add_resource(Events::::default()) - .add_system_to_stage(stage::EVENT, Events::::update_system) + .add_system_to_stage(stage::EVENT, Events::::update_system.system()) } /// Adds a resource to the current [App] and overwrites any resource previously added of the same type. diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index 3fadf24c40fe3..074a2d3083cd2 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -38,7 +38,10 @@ impl PluginGroupBuilder { .find(|(_i, ty)| **ty == TypeId::of::()) .map(|(i, _)| i) .unwrap_or_else(|| { - panic!("Plugin does not exist: {}", std::any::type_name::()) + panic!( + "Plugin does not exist: {}.", + std::any::type_name::() + ) }); self.order.insert(target_index, TypeId::of::()); self.plugins.insert( @@ -59,7 +62,10 @@ impl PluginGroupBuilder { .find(|(_i, ty)| **ty == TypeId::of::()) .map(|(i, _)| i) .unwrap_or_else(|| { - panic!("Plugin does not exist: {}", std::any::type_name::()) + panic!( + "Plugin does not exist: {}.", + std::any::type_name::() + ) }); self.order.insert(target_index + 1, TypeId::of::()); self.plugins.insert( @@ -76,7 +82,7 @@ impl PluginGroupBuilder { let mut plugin_entry = self .plugins .get_mut(&TypeId::of::()) - .expect("Cannot enable a plugin that does not exist"); + .expect("Cannot enable a plugin that does not exist."); plugin_entry.enabled = true; self } @@ -85,7 +91,7 @@ impl PluginGroupBuilder { let mut plugin_entry = self .plugins .get_mut(&TypeId::of::()) - .expect("Cannot disable a plugin that does not exist"); + .expect("Cannot disable a plugin that does not exist."); plugin_entry.enabled = false; self } diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index 3faeabf974383..c533720406001 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -56,8 +56,6 @@ impl Plugin for ScheduleRunnerPlugin { .get_or_insert_with(ScheduleRunnerSettings::default) .to_owned(); app.set_runner(move |mut app: App| { - app.initialize(); - let mut app_exit_event_reader = EventReader::::default(); match settings.run_mode { RunMode::Once => { @@ -113,7 +111,7 @@ impl Plugin for ScheduleRunnerPlugin { f.as_ref().unchecked_ref(), dur.as_millis() as i32, ) - .expect("should register `setTimeout`"); + .expect("Should register `setTimeout`."); } let asap = Duration::from_millis(1); diff --git a/crates/bevy_app/src/stage.rs b/crates/bevy_app/src/stage.rs index 1e4fa590528b7..fb8eb416307ee 100644 --- a/crates/bevy_app/src/stage.rs +++ b/crates/bevy_app/src/stage.rs @@ -1,11 +1,14 @@ +/// Name of the app stage that runs once at the beginning of the app +pub const STARTUP: &str = "startup"; + /// Name of app stage that runs before all other app stages pub const FIRST: &str = "first"; /// Name of app stage that runs before EVENT -pub const PRE_EVENT: &str = "pre_events"; +pub const PRE_EVENT: &str = "pre_event"; /// Name of app stage that updates events. Runs before UPDATE -pub const EVENT: &str = "events"; +pub const EVENT: &str = "event"; /// Name of app stage responsible for performing setup before an update. Runs before UPDATE. pub const PRE_UPDATE: &str = "pre_update"; diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index d0ef595e9434d..e5644853e567b 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_asset" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -18,15 +18,13 @@ filesystem_watcher = ["notify"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_tasks = { path = "../bevy_tasks", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_property = { path = "../bevy_property", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_tasks = { path = "../bevy_tasks", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other -uuid = { version = "0.8", features = ["v4", "serde"] } serde = { version = "1", features = ["derive"] } ron = "0.6.2" crossbeam-channel = "0.4.4" diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index e40ca0b88a984..fafff56008346 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -7,25 +7,24 @@ use crate::{ use anyhow::Result; use bevy_ecs::Res; use bevy_tasks::TaskPool; -use bevy_utils::HashMap; +use bevy_utils::{HashMap, Uuid}; use crossbeam_channel::TryRecvError; use parking_lot::RwLock; use std::{collections::hash_map::Entry, path::Path, sync::Arc}; use thiserror::Error; -use uuid::Uuid; /// Errors that occur while loading assets with an AssetServer #[derive(Error, Debug)] pub enum AssetServerError { - #[error("Asset folder path is not a directory.")] + #[error("asset folder path is not a directory")] AssetFolderNotADirectory(String), - #[error("No AssetLoader found for the given extension.")] + #[error("no AssetLoader found for the given extension")] MissingAssetLoader(Option), - #[error("The given type does not match the type of the loaded asset.")] + #[error("the given type does not match the type of the loaded asset")] IncorrectHandleType, - #[error("Encountered an error while loading an asset.")] + #[error("encountered an error while loading an asset")] AssetLoaderError(anyhow::Error), - #[error("PathLoader encountered an error")] + #[error("`PathLoader` encountered an error")] PathLoaderError(#[from] AssetIoError), } @@ -61,6 +60,10 @@ impl Clone for AssetServer { impl AssetServer { pub fn new(source_io: T, task_pool: TaskPool) -> Self { + Self::with_boxed_io(Box::new(source_io), task_pool) + } + + pub fn with_boxed_io(asset_io: Box, task_pool: TaskPool) -> Self { AssetServer { server: Arc::new(AssetServerInternal { loaders: Default::default(), @@ -70,7 +73,7 @@ impl AssetServer { handle_to_path: Default::default(), asset_lifecycles: Default::default(), task_pool, - asset_io: Box::new(source_io), + asset_io, }), } } @@ -239,7 +242,7 @@ impl AssetServer { let mut asset_sources = self.server.asset_sources.write(); let source_info = asset_sources .get_mut(&asset_path_id.source_path_id()) - .expect("AssetSource should exist at this point"); + .expect("`AssetSource` should exist at this point."); if version != source_info.version { return Ok(asset_path_id); } @@ -318,7 +321,7 @@ impl AssetServer { continue; } let handle = - self.load_untyped(child_path.to_str().expect("Path should be a valid string")); + self.load_untyped(child_path.to_str().expect("Path should be a valid string.")); handles.push(handle); } } @@ -335,7 +338,7 @@ impl AssetServer { let ref_change = match receiver.try_recv() { Ok(ref_change) => ref_change, Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => panic!("RefChange channel disconnected"), + Err(TryRecvError::Disconnected) => panic!("RefChange channel disconnected."), }; match ref_change { RefChange::Increment(handle_id) => *ref_counts.entry(handle_id).or_insert(0) += 1, @@ -378,7 +381,7 @@ impl AssetServer { let asset_value = asset .value .take() - .expect("Asset should exist at this point"); + .expect("Asset should exist at this point."); if let Some(asset_lifecycle) = asset_lifecycles.get(&asset_value.type_uuid()) { let asset_path = AssetPath::new_ref(&load_context.path, label.as_ref().map(|l| l.as_str())); @@ -392,7 +395,7 @@ impl AssetServer { pub(crate) fn update_asset_storage(&self, assets: &mut Assets) { let asset_lifecycles = self.server.asset_lifecycles.read(); let asset_lifecycle = asset_lifecycles.get(&T::TYPE_UUID).unwrap(); - let mut asset_sources = self.server.asset_sources.write(); + let mut asset_sources_guard = None; let channel = asset_lifecycle .downcast_ref::>() .unwrap(); @@ -402,6 +405,8 @@ impl AssetServer { Ok(AssetLifecycleEvent::Create(result)) => { // update SourceInfo if this asset was loaded from an AssetPath if let HandleId::AssetPathId(id) = result.id { + let asset_sources = asset_sources_guard + .get_or_insert_with(|| self.server.asset_sources.write()); if let Some(source_info) = asset_sources.get_mut(&id.source_path_id()) { if source_info.version == result.version { source_info.committed_assets.insert(id.label_id()); @@ -416,6 +421,8 @@ impl AssetServer { } Ok(AssetLifecycleEvent::Free(handle_id)) => { if let HandleId::AssetPathId(id) = handle_id { + let asset_sources = asset_sources_guard + .get_or_insert_with(|| self.server.asset_sources.write()); if let Some(source_info) = asset_sources.get_mut(&id.source_path_id()) { source_info.committed_assets.remove(&id.label_id()); if source_info.is_loaded() { @@ -428,7 +435,7 @@ impl AssetServer { Err(TryRecvError::Empty) => { break; } - Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected"), + Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected."), } } } diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index e3fd86c20a478..1ae9ab5f48cae 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -2,8 +2,8 @@ use crate::{ update_asset_storage_system, Asset, AssetLoader, AssetServer, Handle, HandleId, RefChange, }; use bevy_app::{prelude::Events, AppBuilder}; -use bevy_ecs::{FromResources, ResMut}; -use bevy_type_registry::RegisterType; +use bevy_ecs::{FromResources, IntoSystem, ResMut}; +use bevy_reflect::RegisterTypeBuilder; use bevy_utils::HashMap; use crossbeam_channel::Sender; use std::fmt::Debug; @@ -218,9 +218,15 @@ impl AddAsset for AppBuilder { }; self.add_resource(assets) - .register_component::>() - .add_system_to_stage(super::stage::ASSET_EVENTS, Assets::::asset_event_system) - .add_system_to_stage(crate::stage::LOAD_ASSETS, update_asset_storage_system::) + .add_system_to_stage( + super::stage::ASSET_EVENTS, + Assets::::asset_event_system.system(), + ) + .add_system_to_stage( + crate::stage::LOAD_ASSETS, + update_asset_storage_system::.system(), + ) + .register_type::>() .add_event::>() } diff --git a/crates/bevy_asset/src/filesystem_watcher.rs b/crates/bevy_asset/src/filesystem_watcher.rs index 2b05cf85e31d7..00a3487e593f7 100644 --- a/crates/bevy_asset/src/filesystem_watcher.rs +++ b/crates/bevy_asset/src/filesystem_watcher.rs @@ -12,9 +12,9 @@ impl Default for FilesystemWatcher { fn default() -> Self { let (sender, receiver) = crossbeam_channel::unbounded(); let watcher: RecommendedWatcher = Watcher::new_immediate(move |res| { - sender.send(res).expect("Watch event send failure"); + sender.send(res).expect("Watch event send failure."); }) - .expect("Failed to create filesystem watcher"); + .expect("Failed to create filesystem watcher."); FilesystemWatcher { watcher, receiver } } } diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index f028309197d37..ad39e3438a69b 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -5,20 +5,20 @@ use std::{ marker::PhantomData, }; -use bevy_property::{Properties, Property}; -use crossbeam_channel::{Receiver, Sender}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - use crate::{ path::{AssetPath, AssetPathId}, Asset, Assets, }; +use bevy_reflect::{Reflect, ReflectComponent, ReflectDeserialize}; +use bevy_utils::Uuid; +use crossbeam_channel::{Receiver, Sender}; +use serde::{Deserialize, Serialize}; /// A unique, stable asset id #[derive( - Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, )] +#[reflect_value(Serialize, Deserialize, PartialEq, Hash)] pub enum HandleId { Id(Uuid, u64), AssetPathId(AssetPathId), @@ -56,15 +56,16 @@ impl HandleId { /// A handle into a specific Asset of type `T` /// /// Handles contain a unique id that corresponds to a specific asset in the [Assets](crate::Assets) collection. -#[derive(Properties)] +#[derive(Reflect)] +#[reflect(Component)] pub struct Handle where - T: 'static, + T: Asset, { pub id: HandleId, - #[property(ignore)] + #[reflect(ignore)] handle_type: HandleType, - #[property(ignore)] + #[reflect(ignore)] marker: PhantomData, } @@ -82,17 +83,6 @@ impl Debug for HandleType { } } -impl Handle { - // TODO: remove "uuid" parameter whenever rust support type constraints in const fns - pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self { - Self { - id: HandleId::new(uuid, id), - handle_type: HandleType::Weak, - marker: PhantomData, - } - } -} - impl Handle { pub(crate) fn strong(id: HandleId, ref_change_sender: Sender) -> Self { ref_change_sender.send(RefChange::Increment(id)).unwrap(); @@ -111,7 +101,7 @@ impl Handle { } } - pub fn as_weak(&self) -> Handle { + pub fn as_weak(&self) -> Handle { Handle { id: self.id, handle_type: HandleType::Weak, @@ -152,7 +142,7 @@ impl Handle { } } -impl Drop for Handle { +impl Drop for Handle { fn drop(&mut self) { match self.handle_type { HandleType::Strong(ref sender) => { @@ -164,45 +154,51 @@ impl Drop for Handle { } } -impl From> for HandleId { +impl From> for HandleId { fn from(value: Handle) -> Self { value.id } } +impl From for HandleId { + fn from(value: HandleUntyped) -> Self { + value.id + } +} + impl From<&str> for HandleId { fn from(value: &str) -> Self { AssetPathId::from(value).into() } } -impl From<&Handle> for HandleId { +impl From<&Handle> for HandleId { fn from(value: &Handle) -> Self { value.id } } -impl Hash for Handle { +impl Hash for Handle { fn hash(&self, state: &mut H) { - self.id.hash(state); + Hash::hash(&self.id, state); } } -impl PartialEq for Handle { +impl PartialEq for Handle { fn eq(&self, other: &Self) -> bool { self.id == other.id } } -impl Eq for Handle {} +impl Eq for Handle {} -impl PartialOrd for Handle { +impl PartialOrd for Handle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.id.cmp(&other.id)) } } -impl Ord for Handle { +impl Ord for Handle { fn cmp(&self, other: &Self) -> Ordering { self.id.cmp(&other.id) } @@ -214,7 +210,7 @@ impl Default for Handle { } } -impl Debug for Handle { +impl Debug for Handle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { let name = std::any::type_name::().split("::").last().unwrap(); write!(f, "{:?}Handle<{}>({:?})", self.handle_type, name, self.id) @@ -231,8 +227,8 @@ impl Clone for Handle { } // SAFE: T is phantom data and Handle::id is an integer -unsafe impl Send for Handle {} -unsafe impl Sync for Handle {} +unsafe impl Send for Handle {} +unsafe impl Sync for Handle {} /// A non-generic version of [Handle] /// @@ -244,6 +240,13 @@ pub struct HandleUntyped { } impl HandleUntyped { + pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self { + Self { + id: HandleId::new(uuid, id), + handle_type: HandleType::Weak, + } + } + pub(crate) fn strong(id: HandleId, ref_change_sender: Sender) -> Self { ref_change_sender.send(RefChange::Increment(id)).unwrap(); Self { @@ -274,7 +277,7 @@ impl HandleUntyped { pub fn typed(mut self) -> Handle { if let HandleId::Id(type_uuid, _) = self.id { if T::TYPE_UUID != type_uuid { - panic!("attempted to convert handle to invalid type"); + panic!("Attempted to convert handle to invalid type."); } } let handle_type = match &self.handle_type { @@ -311,7 +314,7 @@ impl From<&HandleUntyped> for HandleId { impl Hash for HandleUntyped { fn hash(&self, state: &mut H) { - self.id.hash(state); + Hash::hash(&self.id, state); } } diff --git a/crates/bevy_asset/src/info.rs b/crates/bevy_asset/src/info.rs index 506f09dbfe4f1..f781fa0eea66a 100644 --- a/crates/bevy_asset/src/info.rs +++ b/crates/bevy_asset/src/info.rs @@ -1,8 +1,7 @@ use crate::{path::AssetPath, LabelId}; -use bevy_utils::{HashMap, HashSet}; +use bevy_utils::{HashMap, HashSet, Uuid}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use uuid::Uuid; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SourceMeta { diff --git a/crates/bevy_asset/src/io/file_asset_io.rs b/crates/bevy_asset/src/io/file_asset_io.rs index 5f2ede7ba988f..0a06d66888e2d 100644 --- a/crates/bevy_asset/src/io/file_asset_io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -122,7 +122,7 @@ pub fn filesystem_watcher_system(asset_server: Res) { let event = match watcher.receiver.try_recv() { Ok(result) => result.unwrap(), Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected"), + Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected."), }; if let notify::event::Event { kind: notify::event::EventKind::Modify(_), diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index 58ac8fa8febdb..927c315b39388 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -24,11 +24,11 @@ use thiserror::Error; /// Errors that occur while loading assets #[derive(Error, Debug)] pub enum AssetIoError { - #[error("Path not found")] + #[error("path not found")] NotFound(PathBuf), - #[error("Encountered an io error while loading asset.")] + #[error("encountered an io error while loading asset")] Io(#[from] io::Error), - #[error("Failed to watch path")] + #[error("failed to watch path")] PathWatchError(PathBuf), } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index f97abe5e1826e..5c9b4133b9d81 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -13,6 +13,8 @@ mod path; pub use asset_server::*; pub use assets::*; +use bevy_ecs::{IntoSystem, SystemStage}; +use bevy_reflect::RegisterTypeBuilder; use bevy_tasks::IoTaskPool; pub use handle::*; pub use info::*; @@ -31,7 +33,6 @@ pub mod prelude { } use bevy_app::{prelude::Plugin, AppBuilder}; -use bevy_type_registry::RegisterType; /// Adds support for Assets to an App. Assets are typed collections with change tracking, which are added as App Resources. /// Examples of assets: textures, sounds, 3d models, maps, scenes @@ -50,42 +51,62 @@ impl Default for AssetServerSettings { } } +/// Create an instance of the platform default `AssetIo` +/// +/// This is useful when providing a custom `AssetIo` instance that needs to +/// delegate to the default `AssetIo` for the platform. +pub fn create_platform_default_asset_io(app: &mut AppBuilder) -> Box { + let settings = app + .resources_mut() + .get_or_insert_with(AssetServerSettings::default); + + #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] + let source = FileAssetIo::new(&settings.asset_folder); + #[cfg(target_arch = "wasm32")] + let source = WasmAssetIo::new(&settings.asset_folder); + #[cfg(target_os = "android")] + let source = AndroidAssetIo::new(&settings.asset_folder); + + Box::new(source) +} + impl Plugin for AssetPlugin { fn build(&self, app: &mut AppBuilder) { - let task_pool = app - .resources() - .get::() - .expect("IoTaskPool resource not found") - .0 - .clone(); - - let asset_server = { - let settings = app - .resources_mut() - .get_or_insert_with(AssetServerSettings::default); - - #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] - let source = FileAssetIo::new(&settings.asset_folder); - #[cfg(target_arch = "wasm32")] - let source = WasmAssetIo::new(&settings.asset_folder); - #[cfg(target_os = "android")] - let source = AndroidAssetIo::new(&settings.asset_folder); - AssetServer::new(source, task_pool) - }; - - app.add_stage_before(bevy_app::stage::PRE_UPDATE, stage::LOAD_ASSETS) - .add_stage_after(bevy_app::stage::POST_UPDATE, stage::ASSET_EVENTS) - .add_resource(asset_server) - .register_property::() - .add_system_to_stage( - bevy_app::stage::PRE_UPDATE, - asset_server::free_unused_assets_system, - ); + if app.resources().get::().is_none() { + let task_pool = app + .resources() + .get::() + .expect("`IoTaskPool` resource not found.") + .0 + .clone(); + + let source = create_platform_default_asset_io(app); + + let asset_server = AssetServer::with_boxed_io(source, task_pool); + + app.add_resource(asset_server); + } + + app.add_stage_before( + bevy_app::stage::PRE_UPDATE, + stage::LOAD_ASSETS, + SystemStage::parallel(), + ) + .add_stage_after( + bevy_app::stage::POST_UPDATE, + stage::ASSET_EVENTS, + SystemStage::parallel(), + ) + .register_type::() + .add_system_to_stage( + bevy_app::stage::PRE_UPDATE, + asset_server::free_unused_assets_system.system(), + ); #[cfg(all( feature = "filesystem_watcher", all(not(target_arch = "wasm32"), not(target_os = "android")) ))] - app.add_system_to_stage(stage::LOAD_ASSETS, io::filesystem_watcher_system); + app.add_system_to_stage(stage::LOAD_ASSETS, io::filesystem_watcher_system.system()); } } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 903f5efca9ec5..bd87b177d5eff 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -4,7 +4,7 @@ use crate::{ }; use anyhow::Result; use bevy_ecs::{Res, ResMut, Resource}; -use bevy_type_registry::{TypeUuid, TypeUuidDynamic}; +use bevy_reflect::{TypeUuid, TypeUuidDynamic}; use bevy_utils::{BoxedFuture, HashMap}; use crossbeam_channel::{Receiver, Sender}; use downcast_rs::{impl_downcast, Downcast}; @@ -152,7 +152,10 @@ impl AssetLifecycle for AssetLifecycleChannel { })) .unwrap() } else { - panic!("failed to downcast asset to {}", std::any::type_name::()); + panic!( + "Failed to downcast asset to {}.", + std::any::type_name::() + ); } } diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index 89abed1547a6d..22f4c2f79cfdf 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -1,4 +1,4 @@ -use bevy_property::Property; +use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_utils::AHasher; use serde::{Deserialize, Serialize}; use std::{ @@ -58,18 +58,21 @@ impl<'a> AssetPath<'a> { } #[derive( - Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, )] +#[reflect_value(PartialEq, Hash, Serialize, Deserialize)] pub struct AssetPathId(SourcePathId, LabelId); #[derive( - Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, )] +#[reflect_value(PartialEq, Hash, Serialize, Deserialize)] pub struct SourcePathId(u64); #[derive( - Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, )] +#[reflect_value(PartialEq, Hash, Serialize, Deserialize)] pub struct LabelId(u64); impl<'a> From<&'a Path> for SourcePathId { @@ -140,7 +143,7 @@ impl<'a, 'b> From<&'a AssetPath<'b>> for AssetPathId { impl<'a> From<&'a str> for AssetPath<'a> { fn from(asset_path: &'a str) -> Self { let mut parts = asset_path.split('#'); - let path = Path::new(parts.next().expect("path must be set")); + let path = Path::new(parts.next().expect("Path must be set.")); let label = parts.next(); AssetPath { path: Cow::Borrowed(path), diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 5dac2083c43b5..ca56db3a80805 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_audio" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,11 +14,11 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other anyhow = "1.0" diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 0c6d0ef80e6be..7a7fe12b3f99f 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -1,17 +1,17 @@ use crate::{AudioSource, Decodable}; -use bevy_asset::Handle; +use bevy_asset::{Asset, Handle}; use parking_lot::RwLock; use std::{collections::VecDeque, fmt}; /// The external struct used to play audio pub struct Audio

where - P: Decodable, + P: Asset + Decodable, { pub queue: RwLock>>, } -impl

fmt::Debug for Audio

+impl fmt::Debug for Audio

where P: Decodable, { @@ -22,7 +22,7 @@ where impl

Default for Audio

where - P: Decodable, + P: Asset + Decodable, { fn default() -> Self { Self { @@ -33,7 +33,7 @@ where impl

Audio

where - P: Decodable, + P: Asset + Decodable,

::Decoder: rodio::Source + Send + Sync, <

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, { diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 7f36276a0001b..087640afc4543 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -1,6 +1,6 @@ use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; -use bevy_type_registry::TypeUuid; +use bevy_reflect::TypeUuid; use bevy_utils::BoxedFuture; use std::{io::Cursor, sync::Arc}; diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index e2d6e4ef412d4..52b7a56dea06e 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -12,6 +12,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; +use bevy_ecs::IntoSystem; /// Adds support for audio playback to an App #[derive(Default)] @@ -23,6 +24,9 @@ impl Plugin for AudioPlugin { .add_asset::() .init_asset_loader::() .init_resource::>() - .add_system_to_stage(stage::POST_UPDATE, play_queued_audio_system::); + .add_system_to_stage( + stage::POST_UPDATE, + play_queued_audio_system::.system(), + ); } } diff --git a/crates/bevy_core/Cargo.toml b/crates/bevy_core/Cargo.toml index 57d677473a418..7986e54f4ba55 100644 --- a/crates/bevy_core/Cargo.toml +++ b/crates/bevy_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_core" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,11 +14,10 @@ keywords = ["bevy"] [dependencies] -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_derive = { path = "../bevy_derive", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_property = { path = "../bevy_property", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } -bevy_tasks = { path = "../bevy_tasks", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_derive = { path = "../bevy_derive", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_tasks = { path = "../bevy_tasks", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } diff --git a/crates/bevy_core/src/label.rs b/crates/bevy_core/src/label.rs index e01aa170a536d..26309477e7843 100644 --- a/crates/bevy_core/src/label.rs +++ b/crates/bevy_core/src/label.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::*; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use bevy_utils::{HashMap, HashSet}; use std::{ borrow::Cow, @@ -8,7 +8,8 @@ use std::{ }; /// A collection of labels -#[derive(Default, Properties)] +#[derive(Default, Reflect)] +#[reflect(Component)] pub struct Labels { labels: HashSet>, } diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 3a5cb4f3c2429..468879e2363dc 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -4,6 +4,10 @@ mod label; mod task_pool_options; mod time; +use std::ops::Range; + +use bevy_ecs::IntoSystem; +use bevy_reflect::RegisterTypeBuilder; pub use bytes::*; pub use float_ord::*; pub use label::*; @@ -15,8 +19,6 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_math::{Mat3, Mat4, Quat, Vec2, Vec3}; -use bevy_type_registry::RegisterType; /// Adds core functionality to Apps. #[derive(Default)] @@ -32,15 +34,11 @@ impl Plugin for CorePlugin { app.init_resource::

PropertiesVal for P -where - P: Properties, -{ - #[inline] - fn prop_val(&self, name: &str) -> Option<&T> { - self.prop(name).and_then(|p| p.any().downcast_ref::()) - } - - #[inline] - fn set_prop_val(&mut self, name: &str, value: T) { - if let Some(prop) = self.prop_mut(name) { - prop.set_val(value); - } else { - panic!("prop does not exist or is incorrect type: {}", name); - } - } -} diff --git a/crates/bevy_property/src/property.rs b/crates/bevy_property/src/property.rs deleted file mode 100644 index 6385531f751de..0000000000000 --- a/crates/bevy_property/src/property.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::{property_serde::Serializable, Properties, PropertyTypeRegistry}; -use erased_serde::Deserializer; -use std::any::Any; - -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] -pub enum PropertyType { - Map, - Seq, - Value, -} - -// TODO: consider removing send + sync requirements -pub trait Property: Send + Sync + Any + 'static { - fn type_name(&self) -> &str; - fn any(&self) -> &dyn Any; - fn any_mut(&mut self) -> &mut dyn Any; - fn clone_prop(&self) -> Box; - fn set(&mut self, value: &dyn Property); - fn apply(&mut self, value: &dyn Property); - fn property_type(&self) -> PropertyType { - PropertyType::Value - } - fn as_properties(&self) -> Option<&dyn Properties> { - None - } - fn serializable<'a>(&'a self, registry: &'a PropertyTypeRegistry) -> Serializable<'a>; -} - -pub trait DeserializeProperty { - fn deserialize( - deserializer: &mut dyn Deserializer, - property_type_registry: &PropertyTypeRegistry, - ) -> Result, erased_serde::Error>; -} - -pub trait PropertyVal { - fn val(&self) -> Option<&T>; - fn set_val(&mut self, value: T); -} - -impl PropertyVal for dyn Property { - #[inline] - fn val(&self) -> Option<&T> { - self.any().downcast_ref::() - } - - #[inline] - fn set_val(&mut self, value: T) { - if let Some(prop) = self.any_mut().downcast_mut::() { - *prop = value; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } -} diff --git a/crates/bevy_property/src/property_serde.rs b/crates/bevy_property/src/property_serde.rs deleted file mode 100644 index 93921b4689033..0000000000000 --- a/crates/bevy_property/src/property_serde.rs +++ /dev/null @@ -1,545 +0,0 @@ -use crate::{DynamicProperties, Properties, Property, PropertyType, PropertyTypeRegistry}; -use de::SeqAccess; -use serde::{ - de::{self, DeserializeSeed, MapAccess, Visitor}, - ser::{SerializeMap, SerializeSeq}, - Serialize, -}; - -pub const TYPE_FIELD: &str = "type"; -pub const MAP_FIELD: &str = "map"; -pub const SEQ_FIELD: &str = "seq"; -pub const VALUE_FIELD: &str = "value"; - -pub enum Serializable<'a> { - Owned(Box), - Borrowed(&'a dyn erased_serde::Serialize), -} - -impl<'a> Serializable<'a> { - #[allow(clippy::should_implement_trait)] - pub fn borrow(&self) -> &dyn erased_serde::Serialize { - match self { - Serializable::Borrowed(serialize) => serialize, - Serializable::Owned(serialize) => serialize, - } - } -} - -pub struct PropertyValueSerializer<'a, T> -where - T: Property + Serialize, -{ - pub property: &'a T, - pub registry: &'a PropertyTypeRegistry, -} - -impl<'a, T> PropertyValueSerializer<'a, T> -where - T: Property + Serialize, -{ - pub fn new(property: &'a T, registry: &'a PropertyTypeRegistry) -> Self { - PropertyValueSerializer { property, registry } - } -} - -impl<'a, T> Serialize for PropertyValueSerializer<'a, T> -where - T: Property + Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(2))?; - state.serialize_entry( - TYPE_FIELD, - format_type_name(self.registry, self.property.type_name()), - )?; - state.serialize_entry(VALUE_FIELD, self.property)?; - state.end() - } -} - -pub struct DynamicPropertiesSerializer<'a> { - pub dynamic_properties: &'a DynamicProperties, - pub registry: &'a PropertyTypeRegistry, -} - -impl<'a> DynamicPropertiesSerializer<'a> { - pub fn new( - dynamic_properties: &'a DynamicProperties, - registry: &'a PropertyTypeRegistry, - ) -> Self { - DynamicPropertiesSerializer { - dynamic_properties, - registry, - } - } -} - -impl<'a> Serialize for DynamicPropertiesSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self.dynamic_properties.property_type { - PropertyType::Map => { - MapSerializer::new(self.dynamic_properties, self.registry).serialize(serializer) - } - PropertyType::Seq => { - SeqSerializer::new(self.dynamic_properties, self.registry).serialize(serializer) - } - _ => Err(serde::ser::Error::custom( - "DynamicProperties cannot be Value type", - )), - } - } -} - -pub struct MapSerializer<'a> { - pub properties: &'a dyn Properties, - pub registry: &'a PropertyTypeRegistry, -} - -impl<'a> MapSerializer<'a> { - pub fn new(properties: &'a dyn Properties, registry: &'a PropertyTypeRegistry) -> Self { - MapSerializer { - properties, - registry, - } - } -} - -fn format_type_name<'a>(registry: &'a PropertyTypeRegistry, type_name: &'a str) -> &'a str { - registry.format_type_name(type_name).unwrap_or(type_name) -} - -impl<'a> Serialize for MapSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(2))?; - - state.serialize_entry( - TYPE_FIELD, - format_type_name(self.registry, self.properties.type_name()), - )?; - state.serialize_entry( - MAP_FIELD, - &MapValueSerializer { - properties: self.properties, - registry: self.registry, - }, - )?; - state.end() - } -} - -pub struct MapValueSerializer<'a> { - pub properties: &'a dyn Properties, - pub registry: &'a PropertyTypeRegistry, -} - -impl<'a> Serialize for MapValueSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(self.properties.prop_len()))?; - for (index, property) in self.properties.iter_props().enumerate() { - let name = self.properties.prop_name(index).unwrap(); - state.serialize_entry(name, property.serializable(self.registry).borrow())?; - } - state.end() - } -} - -pub struct SeqSerializer<'a> { - pub properties: &'a dyn Properties, - pub registry: &'a PropertyTypeRegistry, -} - -impl<'a> SeqSerializer<'a> { - pub fn new(properties: &'a dyn Properties, registry: &'a PropertyTypeRegistry) -> Self { - SeqSerializer { - properties, - registry, - } - } -} - -impl<'a> Serialize for SeqSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(2))?; - state.serialize_entry( - TYPE_FIELD, - format_type_name(self.registry, self.properties.type_name()), - )?; - state.serialize_entry( - SEQ_FIELD, - &SeqValueSerializer { - properties: self.properties, - registry: self.registry, - }, - )?; - state.end() - } -} - -pub struct SeqValueSerializer<'a> { - pub properties: &'a dyn Properties, - pub registry: &'a PropertyTypeRegistry, -} - -impl<'a> Serialize for SeqValueSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_seq(Some(self.properties.prop_len()))?; - for prop in self.properties.iter_props() { - state.serialize_element(prop.serializable(self.registry).borrow())?; - } - state.end() - } -} - -pub struct DynamicPropertiesDeserializer<'a> { - registry: &'a PropertyTypeRegistry, -} - -impl<'a> DynamicPropertiesDeserializer<'a> { - pub fn new(registry: &'a PropertyTypeRegistry) -> Self { - DynamicPropertiesDeserializer { registry } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for DynamicPropertiesDeserializer<'a> { - type Value = DynamicProperties; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_map(DynamicPropertiesVisitor { - registry: self.registry, - }) - } -} - -struct DynamicPropertiesVisitor<'a> { - registry: &'a PropertyTypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for DynamicPropertiesVisitor<'a> { - type Value = DynamicProperties; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("dynamic property") - } - - fn visit_map(self, map: V) -> Result - where - V: MapAccess<'de>, - { - match visit_map(map, self.registry)? { - DynamicPropertiesOrProperty::DynamicProperties(value) => Ok(value), - _ => Err(de::Error::custom("Expected DynamicProperties")), - } - } -} - -pub struct PropertyDeserializer<'a> { - type_name: Option<&'a str>, - registry: &'a PropertyTypeRegistry, -} - -impl<'a, 'de> DeserializeSeed<'de> for PropertyDeserializer<'a> { - type Value = Box; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if let Some(type_name) = self.type_name { - let registration = self.registry.get(type_name).ok_or_else(|| { - de::Error::custom(format!("TypeRegistration is missing for {}", type_name)) - })?; - registration.deserialize(deserializer, self.registry) - } else { - deserializer.deserialize_any(AnyPropVisitor { - registry: self.registry, - }) - } - } -} - -pub struct SeqPropertyDeserializer<'a> { - registry: &'a PropertyTypeRegistry, -} - -impl<'a, 'de> DeserializeSeed<'de> for SeqPropertyDeserializer<'a> { - type Value = DynamicProperties; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_seq(SeqPropertyVisitor { - registry: self.registry, - }) - } -} - -pub struct SeqPropertyVisitor<'a> { - registry: &'a PropertyTypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for SeqPropertyVisitor<'a> { - type Value = DynamicProperties; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("property value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - let mut dynamic_properties = DynamicProperties::seq(); - while let Some(prop) = seq.next_element_seed(PropertyDeserializer { - registry: self.registry, - type_name: None, - })? { - dynamic_properties.push(prop, None); - } - Ok(dynamic_properties) - } -} - -pub struct MapPropertyDeserializer<'a> { - registry: &'a PropertyTypeRegistry, -} - -impl<'a> MapPropertyDeserializer<'a> { - pub fn new(registry: &'a PropertyTypeRegistry) -> Self { - MapPropertyDeserializer { registry } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for MapPropertyDeserializer<'a> { - type Value = DynamicProperties; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_map(MapPropertyVisitor { - registry: self.registry, - }) - } -} - -struct MapPropertyVisitor<'a> { - registry: &'a PropertyTypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for MapPropertyVisitor<'a> { - type Value = DynamicProperties; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("map value") - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut dynamic_properties = DynamicProperties::map(); - while let Some(key) = map.next_key::()? { - let property = map.next_value_seed(PropertyDeserializer { - registry: self.registry, - type_name: None, - })?; - dynamic_properties.set_box(&key, property); - } - - Ok(dynamic_properties) - } -} - -struct AnyPropVisitor<'a> { - registry: &'a PropertyTypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for AnyPropVisitor<'a> { - type Value = Box; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("property value") - } - - fn visit_u8(self, v: u8) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_bool(self, v: bool) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_u16(self, v: u16) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_u32(self, v: u32) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_u64(self, v: u64) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_i8(self, v: i8) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_i16(self, v: i16) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_i32(self, v: i32) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_i64(self, v: i64) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_f32(self, v: f32) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_f64(self, v: f64) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_string(self, v: String) -> Result - where - E: de::Error, - { - Ok(Box::new(v)) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - Ok(Box::new(v.to_string())) - } - - fn visit_map(self, map: V) -> Result - where - V: MapAccess<'de>, - { - Ok(match visit_map(map, self.registry)? { - DynamicPropertiesOrProperty::DynamicProperties(value) => Box::new(value), - DynamicPropertiesOrProperty::Property(value) => value, - }) - } -} - -enum DynamicPropertiesOrProperty { - DynamicProperties(DynamicProperties), - Property(Box), -} - -fn visit_map<'de, V>( - mut map: V, - registry: &PropertyTypeRegistry, -) -> Result -where - V: MapAccess<'de>, -{ - let mut type_name: Option = None; - while let Some(key) = map.next_key::()? { - match key.as_str() { - TYPE_FIELD => { - type_name = Some(map.next_value()?); - } - MAP_FIELD => { - let type_name = type_name - .take() - .ok_or_else(|| de::Error::missing_field(TYPE_FIELD))?; - let mut dynamic_properties = - map.next_value_seed(MapPropertyDeserializer { registry })?; - dynamic_properties.type_name = type_name; - return Ok(DynamicPropertiesOrProperty::DynamicProperties( - dynamic_properties, - )); - } - SEQ_FIELD => { - let type_name = type_name - .take() - .ok_or_else(|| de::Error::missing_field(TYPE_FIELD))?; - let mut dynamic_properties = - map.next_value_seed(SeqPropertyDeserializer { registry })?; - dynamic_properties.type_name = type_name; - return Ok(DynamicPropertiesOrProperty::DynamicProperties( - dynamic_properties, - )); - } - VALUE_FIELD => { - let type_name = type_name - .take() - .ok_or_else(|| de::Error::missing_field(TYPE_FIELD))?; - return Ok(DynamicPropertiesOrProperty::Property(map.next_value_seed( - PropertyDeserializer { - registry, - type_name: Some(&type_name), - }, - )?)); - } - _ => return Err(de::Error::unknown_field(key.as_str(), &[])), - } - } - - Err(de::Error::custom("Maps in this location must have the \'type\' field and one of the following fields: \'map\', \'seq\', \'value\'")) -} diff --git a/crates/bevy_property/src/ron.rs b/crates/bevy_property/src/ron.rs deleted file mode 100644 index d5b472c11e33d..0000000000000 --- a/crates/bevy_property/src/ron.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::{ - property_serde::DynamicPropertiesDeserializer, DynamicProperties, PropertyTypeRegistry, -}; -use ron::de::Deserializer; -use serde::de::DeserializeSeed; - -pub fn deserialize_dynamic_properties( - ron_string: &str, - property_type_registry: &PropertyTypeRegistry, -) -> Result { - let mut deserializer = Deserializer::from_str(&ron_string).unwrap(); - let dynamic_properties_deserializer = - DynamicPropertiesDeserializer::new(&property_type_registry); - dynamic_properties_deserializer.deserialize(&mut deserializer) -} diff --git a/crates/bevy_property/src/type_registry.rs b/crates/bevy_property/src/type_registry.rs deleted file mode 100644 index 19d958b533689..0000000000000 --- a/crates/bevy_property/src/type_registry.rs +++ /dev/null @@ -1,235 +0,0 @@ -use crate::{DeserializeProperty, Property}; -use bevy_utils::{HashMap, HashSet}; -use std::{any::TypeId, fmt}; - -#[derive(Debug, Default)] -pub struct PropertyTypeRegistry { - registrations: HashMap, - short_names: HashMap, - ambiguous_names: HashSet, -} - -impl PropertyTypeRegistry { - pub fn register(&mut self) - where - T: Property + DeserializeProperty, - { - let registration = PropertyTypeRegistration::of::(); - self.add_registration(registration); - } - - fn add_registration(&mut self, registration: PropertyTypeRegistration) { - let short_name = registration.short_name.to_string(); - if self.short_names.contains_key(&short_name) || self.ambiguous_names.contains(&short_name) - { - // name is ambiguous. fall back to long names for all ambiguous types - self.short_names.remove(&short_name); - self.ambiguous_names.insert(short_name); - } else { - self.short_names - .insert(short_name, registration.name.to_string()); - } - self.registrations - .insert(registration.name.to_string(), registration); - } - - pub fn get(&self, type_name: &str) -> Option<&PropertyTypeRegistration> { - if let Some(long_name) = self.short_names.get(type_name) { - self.registrations.get(long_name) - } else { - self.registrations.get(type_name) - } - } - - pub fn format_type_name(&self, type_name: &str) -> Option<&str> { - self.get(type_name).map(|registration| { - if self.short_names.contains_key(®istration.short_name) { - ®istration.short_name - } else { - registration.name - } - }) - } - - pub fn get_with_short_name(&self, short_type_name: &str) -> Option<&PropertyTypeRegistration> { - self.short_names - .get(short_type_name) - .and_then(|name| self.registrations.get(name)) - } - - pub fn get_with_full_name(&self, type_name: &str) -> Option<&PropertyTypeRegistration> { - self.registrations.get(type_name) - } -} - -#[derive(Clone)] -pub struct PropertyTypeRegistration { - pub ty: TypeId, - deserialize_fn: fn( - deserializer: &mut dyn erased_serde::Deserializer, - property_type_registry: &PropertyTypeRegistry, - ) -> Result, erased_serde::Error>, - pub short_name: String, - pub name: &'static str, -} - -impl fmt::Debug for PropertyTypeRegistration { - fn fmt<'a>(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("PropertyTypeRegistration") - .field("ty", &self.ty) - .field( - "deserialize_fn", - &(self.deserialize_fn - as fn( - deserializer: &'a mut dyn erased_serde::Deserializer<'a>, - property_type_registry: &'a PropertyTypeRegistry, - ) -> Result, erased_serde::Error>), - ) - .field("short_name", &self.short_name) - .field("name", &self.name) - .finish() - } -} - -impl PropertyTypeRegistration { - pub fn of() -> Self { - let ty = TypeId::of::(); - let type_name = std::any::type_name::(); - Self { - ty, - deserialize_fn: - |deserializer: &mut dyn erased_serde::Deserializer, - property_type_registry: &PropertyTypeRegistry| { - T::deserialize(deserializer, property_type_registry) - }, - name: type_name, - short_name: Self::get_short_name(type_name), - } - } - - pub fn get_short_name(full_name: &str) -> String { - let mut short_name = String::new(); - - { - // A typename may be a composition of several other type names (e.g. generic parameters) - // separated by the characters that we try to find below. - // Then, each individual typename is shortened to its last path component. - // - // Note: Instead of `find`, `split_inclusive` would be nice but it's still unstable... - let mut remainder = full_name; - while let Some(index) = remainder.find(&['<', '>', '(', ')', '[', ']', ',', ';'][..]) { - let (path, new_remainder) = remainder.split_at(index); - // Push the shortened path in front of the found character - short_name.push_str(path.rsplit(':').next().unwrap()); - // Push the character that was found - let character = new_remainder.chars().next().unwrap(); - short_name.push(character); - // Advance the remainder - if character == ',' || character == ';' { - // A comma or semicolon is always followed by a space - short_name.push(' '); - remainder = &new_remainder[2..]; - } else { - remainder = &new_remainder[1..]; - } - } - - // The remainder will only be non-empty if there were no matches at all - if !remainder.is_empty() { - // Then, the full typename is a path that has to be shortened - short_name.push_str(remainder.rsplit(':').next().unwrap()); - } - } - - short_name - } - - pub fn deserialize<'de, D>( - &self, - deserializer: D, - registry: &PropertyTypeRegistry, - ) -> Result, D::Error> - where - D: serde::Deserializer<'de>, - { - let mut erased = erased_serde::Deserializer::erase(deserializer); - (self.deserialize_fn)(&mut erased, registry) - .map_err(<>::Error as serde::de::Error>::custom) - } -} - -#[cfg(test)] -mod test { - use crate::PropertyTypeRegistration; - use std::collections::HashMap; - - #[test] - fn test_get_short_name() { - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::()), - "f64" - ); - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::()), - "String" - ); - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::<(u32, f64)>()), - "(u32, f64)" - ); - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::<(String, String)>()), - "(String, String)" - ); - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::<[f64]>()), - "[f64]" - ); - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::<[String]>()), - "[String]" - ); - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::<[f64; 16]>()), - "[f64; 16]" - ); - assert_eq!( - PropertyTypeRegistration::get_short_name(std::any::type_name::<[String; 16]>()), - "[String; 16]" - ); - } - - #[test] - fn test_property_type_registration() { - assert_eq!( - PropertyTypeRegistration::of::>().short_name, - "Option" - ); - assert_eq!( - PropertyTypeRegistration::of::>().short_name, - "HashMap" - ); - assert_eq!( - PropertyTypeRegistration::of::>>().short_name, - "Option>" - ); - assert_eq!( - PropertyTypeRegistration::of::>>>().short_name, - "Option>>" - ); - assert_eq!( - PropertyTypeRegistration::of::>>>().short_name, - "Option>>" - ); - assert_eq!( - PropertyTypeRegistration::of::, Option>>>() - .short_name, - "Option, Option>>" - ); - assert_eq!( - PropertyTypeRegistration::of::, (String, Option)>>>() - .short_name, - "Option, (String, Option)>>" - ); - } -} diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml new file mode 100644 index 0000000000000..35ca0f69fcffc --- /dev/null +++ b/crates/bevy_reflect/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "bevy_reflect" +version = "0.4.0" +edition = "2018" +authors = [ + "Bevy Contributors ", + "Carter Anderson ", +] +description = "Dynamically interact with rust types" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT" +keywords = ["bevy"] +readme = "README.md" + +[features] +bevy = ["bevy_ecs", "bevy_app", "glam", "smallvec"] + +[dependencies] +# bevy +bevy_reflect_derive = { path = "bevy_reflect_derive", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0", optional = true } +bevy_app = { path = "../bevy_app", version = "0.4.0", optional = true } + +# other +erased-serde = "0.3" +downcast-rs = "1.2" +parking_lot = "0.11.0" +thiserror = "1.0" +serde = "1" +smallvec = { version = "1.4", features = ["serde"], optional = true } +glam = { version = "0.11.0", features = ["serde"], optional = true } + +[dev-dependencies] +ron = "0.6.2" diff --git a/crates/bevy_reflect/README.md b/crates/bevy_reflect/README.md new file mode 100644 index 0000000000000..39c9048205021 --- /dev/null +++ b/crates/bevy_reflect/README.md @@ -0,0 +1,166 @@ +# Bevy Reflect + +This crate enables you to dynamically interact with Rust types: + +* Derive the Reflect traits +* Interact with fields using their names (for named structs) or indices (for tuple structs) +* "Patch" your types with new values +* Look up nested fields using "path strings" +* Iterate over struct fields +* Automatically serialize and deserialize via Serde (without explicit serde impls) +* Trait "reflection" + +## Features + +### Derive the Reflect traits + +```rust +// this will automatically implement the Reflect trait and the Struct trait (because the type is a struct) +#[derive(Reflect)] +struct Foo { + a: u32, + b: Bar, + c: Vec, + d: Vec, +} + +// this will automatically implement the Reflect trait and the TupleStruct trait (because the type is a tuple struct) +#[derive(Reflect)] +struct Bar(String); + +#[derive(Reflect)] +struct Baz { + value: f32, +} + +// We will use this value to illustrate `bevy_reflect` features +let mut foo = Foo { + a: 1, + b: Bar("hello".to_string()), + c: vec![1, 2], + d: vec![Baz { value: 3.14 }], +}; +``` + +### Interact with fields using their names + +```rust +assert_eq!(*foo.get_field::("a").unwrap(), 1); + +*foo.get_field_mut::("a").unwrap() = 2; + +assert_eq!(foo.a, 2); +``` + +### "Patch" your types with new values + +```rust +let mut dynamic_struct = DynamicStruct::default(); +dynamic_struct.insert("a", 42u32); +dynamic_struct.insert("c", vec![3, 4, 5]); + +foo.apply(&dynamic_struct); + +assert_eq!(foo.a, 42); +assert_eq!(foo.c, vec![3, 4, 5]); +``` + +### Look up nested fields using "path strings" + +```rust +let value = *foo.get_path::("d[0].value").unwrap(); +assert_eq!(value, 3.14); +``` + +### Iterate over struct fields + +```rust +for (i, value: &Reflect) in foo.iter_fields().enumerate() { + let field_name = foo.name_at(i).unwrap(); + if let Ok(value) = value.downcast_ref::() { + println!("{} is a u32 with the value: {}", field_name, *value); + } +} +``` + +### Automatically serialize and deserialize via Serde (without explicit serde impls) + +```rust +let mut registry = TypeRegistry::default(); +registry.register::(); +registry.register::(); +registry.register::(); +registry.register::(); +registry.register::(); +registry.register::(); + +let serializer = ReflectSerializer::new(&foo, ®istry); +let serialized = ron::ser::to_string_pretty(&serializer, ron::ser::PrettyConfig::default()).unwrap(); + +let mut deserializer = ron::de::Deserializer::from_str(&serialized).unwrap(); +let reflect_deserializer = ReflectDeserializer::new(®istry); +let value = reflect_deserializer.deserialize(&mut deserializer).unwrap(); +let dynamic_struct = value.take::().unwrap(); + +assert!(foo.reflect_partial_eq(&dynamic_struct).unwrap()); +``` + +### Trait "reflection" + +Call a trait on a given &dyn Reflect reference without knowing the underlying type! + +```rust +#[derive(Reflect)] +#[reflect(DoThing)] +struct MyType { + value: String, +} + +impl DoThing for MyType { + fn do_thing(&self) -> String { + format!("{} World!", self.value) + } +} + +#[reflect_trait] +pub trait DoThing { + fn do_thing(&self) -> String; +} + +// First, lets box our type as a Box +let reflect_value: Box = Box::new(MyType { + value: "Hello".to_string(), +}); + +// This means we no longer have direct access to MyType or its methods. We can only call Reflect methods on reflect_value. +// What if we want to call `do_thing` on our type? We could downcast using reflect_value.downcast_ref::(), but what if we +// don't know the type at compile time? + +// Normally in rust we would be out of luck at this point. Lets use our new reflection powers to do something cool! +let mut type_registry = TypeRegistry::default() +type_registry.register::(); + +// The #[reflect] attribute we put on our DoThing trait generated a new `ReflectDoThing` struct, which implements TypeData. +// This was added to MyType's TypeRegistration. +let reflect_do_thing = type_registry + .get_type_data::(reflect_value.type_id()) + .unwrap(); + +// We can use this generated type to convert our `&dyn Reflect` reference to a `&dyn DoThing` reference +let my_trait: &dyn DoThing = reflect_do_thing.get(&*reflect_value).unwrap(); + +// Which means we can now call do_thing(). Magic! +println!("{}", my_trait.do_thing()); + +// This works because the #[reflect(MyTrait)] we put on MyType informed the Reflect derive to insert a new instance +// of ReflectDoThing into MyType's registration. The instance knows how to cast &dyn Reflect to &dyn MyType, because it +// knows that &dyn Reflect should first be downcasted to &MyType, which can then be safely casted to &dyn MyType +``` + +## Why make this? + +The whole point of Rust is static safety! Why build something that makes it easy to throw it all away? + +* Some problems are inherently dynamic (scripting, some types of serialization / deserialization) +* Sometimes the dynamic way is easier +* Sometimes the dynamic way puts less burden on your users to derive a bunch of traits (this was a big motivator for the Bevy project) diff --git a/crates/bevy_property/bevy_property_derive/Cargo.toml b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml similarity index 65% rename from crates/bevy_property/bevy_property_derive/Cargo.toml rename to crates/bevy_reflect/bevy_reflect_derive/Cargo.toml index 0468aea134eca..d9e338082c96e 100644 --- a/crates/bevy_property/bevy_property_derive/Cargo.toml +++ b/crates/bevy_reflect/bevy_reflect_derive/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "bevy_property_derive" -version = "0.3.0" +name = "bevy_reflect_derive" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", "Carter Anderson ", ] -description = "Derive implementations for bevy_property" +description = "Derive implementations for bevy_reflect" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" license = "MIT" @@ -19,4 +19,5 @@ proc-macro = true syn = "1.0" proc-macro2 = "1.0" quote = "1.0" -find-crate = "0.5" +find-crate = "0.6" +uuid = { version = "0.8", features = ["v4", "serde"] } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs new file mode 100644 index 0000000000000..73371602f1b6a --- /dev/null +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -0,0 +1,751 @@ +extern crate proc_macro; + +mod modules; +mod reflect_trait; +mod type_uuid; + +use find_crate::Manifest; +use modules::{get_modules, get_path}; +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{ + parenthesized, + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + token::{Comma, Paren, Where}, + Data, DataStruct, DeriveInput, Field, Fields, Generics, Ident, Index, Member, Meta, NestedMeta, + Path, +}; + +#[derive(Default)] +struct PropAttributeArgs { + pub ignore: Option, +} + +#[derive(Clone)] +enum TraitImpl { + NotImplemented, + Implemented, + Custom(Ident), +} + +impl Default for TraitImpl { + fn default() -> Self { + Self::NotImplemented + } +} + +enum DeriveType { + Struct, + TupleStruct, + UnitStruct, + Value, +} + +static REFLECT_ATTRIBUTE_NAME: &str = "reflect"; +static REFLECT_VALUE_ATTRIBUTE_NAME: &str = "reflect_value"; + +#[proc_macro_derive(Reflect, attributes(reflect, reflect_value, module))] +pub fn derive_reflect(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let unit_struct_punctuated = Punctuated::new(); + let (fields, mut derive_type) = match &ast.data { + Data::Struct(DataStruct { + fields: Fields::Named(fields), + .. + }) => (&fields.named, DeriveType::Struct), + Data::Struct(DataStruct { + fields: Fields::Unnamed(fields), + .. + }) => (&fields.unnamed, DeriveType::TupleStruct), + Data::Struct(DataStruct { + fields: Fields::Unit, + .. + }) => (&unit_struct_punctuated, DeriveType::UnitStruct), + _ => (&unit_struct_punctuated, DeriveType::Value), + }; + + let fields_and_args = fields + .iter() + .enumerate() + .map(|(i, f)| { + ( + f, + f.attrs + .iter() + .find(|a| *a.path.get_ident().as_ref().unwrap() == REFLECT_ATTRIBUTE_NAME) + .map(|a| { + syn::custom_keyword!(ignore); + let mut attribute_args = PropAttributeArgs { ignore: None }; + a.parse_args_with(|input: ParseStream| { + if input.parse::>()?.is_some() { + attribute_args.ignore = Some(true); + return Ok(()); + } + Ok(()) + }) + .expect("Invalid 'property' attribute format."); + + attribute_args + }), + i, + ) + }) + .collect::, usize)>>(); + let active_fields = fields_and_args + .iter() + .filter(|(_field, attrs, _i)| { + attrs.is_none() + || match attrs.as_ref().unwrap().ignore { + Some(ignore) => !ignore, + None => true, + } + }) + .map(|(f, _attr, i)| (*f, *i)) + .collect::>(); + + let modules = get_modules(); + let bevy_reflect_path = get_path(&modules.bevy_reflect); + let type_name = &ast.ident; + + let mut reflect_attrs = ReflectAttrs::default(); + for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { + let meta_list = if let Meta::List(meta_list) = attribute { + meta_list + } else { + continue; + }; + + if let Some(ident) = meta_list.path.get_ident() { + if ident == REFLECT_ATTRIBUTE_NAME { + reflect_attrs = ReflectAttrs::from_nested_metas(&meta_list.nested); + } else if ident == REFLECT_VALUE_ATTRIBUTE_NAME { + derive_type = DeriveType::Value; + reflect_attrs = ReflectAttrs::from_nested_metas(&meta_list.nested); + } + } + } + + let registration_data = &reflect_attrs.data; + let get_type_registration_impl = impl_get_type_registration( + type_name, + &bevy_reflect_path, + registration_data, + &ast.generics, + ); + + match derive_type { + DeriveType::Struct | DeriveType::UnitStruct => impl_struct( + type_name, + &ast.generics, + get_type_registration_impl, + &bevy_reflect_path, + &reflect_attrs, + &active_fields, + ), + DeriveType::TupleStruct => impl_tuple_struct( + type_name, + &ast.generics, + get_type_registration_impl, + &bevy_reflect_path, + &reflect_attrs, + &active_fields, + ), + DeriveType::Value => impl_value( + type_name, + &ast.generics, + get_type_registration_impl, + &bevy_reflect_path, + &reflect_attrs, + ), + } +} + +fn impl_struct( + struct_name: &Ident, + generics: &Generics, + get_type_registration_impl: proc_macro2::TokenStream, + bevy_reflect_path: &Path, + reflect_attrs: &ReflectAttrs, + active_fields: &[(&Field, usize)], +) -> TokenStream { + let field_names = active_fields + .iter() + .map(|(field, index)| { + field + .ident + .as_ref() + .map(|i| i.to_string()) + .unwrap_or_else(|| index.to_string()) + }) + .collect::>(); + let field_idents = active_fields + .iter() + .map(|(field, index)| { + field + .ident + .as_ref() + .map(|ident| Member::Named(ident.clone())) + .unwrap_or_else(|| Member::Unnamed(Index::from(*index))) + }) + .collect::>(); + let field_count = active_fields.len(); + let field_indices = (0..field_count).collect::>(); + + let hash_fn = reflect_attrs.get_hash_impl(&bevy_reflect_path); + let serialize_fn = reflect_attrs.get_serialize_impl(&bevy_reflect_path); + let partial_eq_fn = match reflect_attrs.reflect_partial_eq { + TraitImpl::NotImplemented => quote! { + use #bevy_reflect_path::Struct; + #bevy_reflect_path::struct_partial_eq(self, value) + }, + TraitImpl::Implemented | TraitImpl::Custom(_) => reflect_attrs.get_partial_eq_impl(), + }; + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + TokenStream::from(quote! { + #get_type_registration_impl + + impl #impl_generics #bevy_reflect_path::Struct for #struct_name#ty_generics #where_clause { + fn field(&self, name: &str) -> Option<&dyn #bevy_reflect_path::Reflect> { + match name { + #(#field_names => Some(&self.#field_idents),)* + _ => None, + } + } + + fn field_mut(&mut self, name: &str) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + match name { + #(#field_names => Some(&mut self.#field_idents),)* + _ => None, + } + } + + fn field_at(&self, index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> { + match index { + #(#field_indices => Some(&self.#field_idents),)* + _ => None, + } + } + + fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + match index { + #(#field_indices => Some(&mut self.#field_idents),)* + _ => None, + } + } + + fn name_at(&self, index: usize) -> Option<&str> { + match index { + #(#field_indices => Some(#field_names),)* + _ => None, + } + } + + fn field_len(&self) -> usize { + #field_count + } + + fn iter_fields(&self) -> #bevy_reflect_path::FieldIter { + #bevy_reflect_path::FieldIter::new(self) + } + + fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicStruct { + let mut dynamic = #bevy_reflect_path::DynamicStruct::default(); + dynamic.set_name(self.type_name().to_string()); + #(dynamic.insert_boxed(#field_names, self.#field_idents.clone_value());)* + dynamic + } + } + + impl #impl_generics #bevy_reflect_path::Reflect for #struct_name#ty_generics #where_clause { + #[inline] + fn type_name(&self) -> &str { + std::any::type_name::() + } + + #[inline] + fn any(&self) -> &dyn std::any::Any { + self + } + #[inline] + fn any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + #[inline] + fn clone_value(&self) -> Box { + use #bevy_reflect_path::Struct; + Box::new(self.clone_dynamic()) + } + #[inline] + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + #[inline] + fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) { + use #bevy_reflect_path::Struct; + if let #bevy_reflect_path::ReflectRef::Struct(struct_value) = value.reflect_ref() { + for (i, value) in struct_value.iter_fields().enumerate() { + let name = struct_value.name_at(i).unwrap(); + self.field_mut(name).map(|v| v.apply(value)); + } + } else { + panic!("Attempted to apply non-struct type to struct type."); + } + } + + fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef { + #bevy_reflect_path::ReflectRef::Struct(self) + } + + fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut { + #bevy_reflect_path::ReflectMut::Struct(self) + } + + fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> { + #serialize_fn + } + + fn reflect_hash(&self) -> Option { + #hash_fn + } + + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { + #partial_eq_fn + } + } + }) +} + +fn impl_tuple_struct( + struct_name: &Ident, + generics: &Generics, + get_type_registration_impl: proc_macro2::TokenStream, + bevy_reflect_path: &Path, + reflect_attrs: &ReflectAttrs, + active_fields: &[(&Field, usize)], +) -> TokenStream { + let field_idents = active_fields + .iter() + .map(|(_field, index)| Member::Unnamed(Index::from(*index))) + .collect::>(); + let field_count = active_fields.len(); + let field_indices = (0..field_count).collect::>(); + + let hash_fn = reflect_attrs.get_hash_impl(&bevy_reflect_path); + let serialize_fn = reflect_attrs.get_serialize_impl(&bevy_reflect_path); + let partial_eq_fn = match reflect_attrs.reflect_partial_eq { + TraitImpl::NotImplemented => quote! { + use #bevy_reflect_path::TupleStruct; + #bevy_reflect_path::tuple_struct_partial_eq(self, value) + }, + TraitImpl::Implemented | TraitImpl::Custom(_) => reflect_attrs.get_partial_eq_impl(), + }; + + let (impl_generics, ty_generics, _where_clause) = generics.split_for_impl(); + TokenStream::from(quote! { + #get_type_registration_impl + + impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name#ty_generics { + fn field(&self, index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> { + match index { + #(#field_indices => Some(&self.#field_idents),)* + _ => None, + } + } + + fn field_mut(&mut self, index: usize) -> Option<&mut dyn #bevy_reflect_path::Reflect> { + match index { + #(#field_indices => Some(&mut self.#field_idents),)* + _ => None, + } + } + + fn field_len(&self) -> usize { + #field_count + } + + fn iter_fields(&self) -> #bevy_reflect_path::TupleStructFieldIter { + #bevy_reflect_path::TupleStructFieldIter::new(self) + } + + fn clone_dynamic(&self) -> #bevy_reflect_path::DynamicTupleStruct { + let mut dynamic = #bevy_reflect_path::DynamicTupleStruct::default(); + #(dynamic.insert_boxed(self.#field_idents.clone_value());)* + dynamic + } + } + + impl #impl_generics #bevy_reflect_path::Reflect for #struct_name#ty_generics { + #[inline] + fn type_name(&self) -> &str { + std::any::type_name::() + } + + #[inline] + fn any(&self) -> &dyn std::any::Any { + self + } + #[inline] + fn any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + #[inline] + fn clone_value(&self) -> Box { + use #bevy_reflect_path::TupleStruct; + Box::new(self.clone_dynamic()) + } + #[inline] + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + #[inline] + fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) { + use #bevy_reflect_path::TupleStruct; + if let #bevy_reflect_path::ReflectRef::TupleStruct(struct_value) = value.reflect_ref() { + for (i, value) in struct_value.iter_fields().enumerate() { + self.field_mut(i).map(|v| v.apply(value)); + } + } else { + panic!("Attempted to apply non-TupleStruct type to TupleStruct type."); + } + } + + fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef { + #bevy_reflect_path::ReflectRef::TupleStruct(self) + } + + fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut { + #bevy_reflect_path::ReflectMut::TupleStruct(self) + } + + fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> { + #serialize_fn + } + + fn reflect_hash(&self) -> Option { + #hash_fn + } + + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { + #partial_eq_fn + } + } + }) +} + +fn impl_value( + type_name: &Ident, + generics: &Generics, + get_type_registration_impl: proc_macro2::TokenStream, + bevy_reflect_path: &Path, + reflect_attrs: &ReflectAttrs, +) -> TokenStream { + let hash_fn = reflect_attrs.get_hash_impl(&bevy_reflect_path); + let partial_eq_fn = reflect_attrs.get_partial_eq_impl(); + let serialize_fn = reflect_attrs.get_serialize_impl(&bevy_reflect_path); + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + TokenStream::from(quote! { + #get_type_registration_impl + + impl #impl_generics #bevy_reflect_path::Reflect for #type_name#ty_generics #where_clause { + #[inline] + fn type_name(&self) -> &str { + std::any::type_name::() + } + + #[inline] + fn any(&self) -> &dyn std::any::Any { + self + } + + #[inline] + fn any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + #[inline] + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } + + #[inline] + fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) { + let value = value.any(); + if let Some(value) = value.downcast_ref::() { + *self = value.clone(); + } else { + panic!("Value is not {}.", std::any::type_name::()); + } + } + + #[inline] + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef { + #bevy_reflect_path::ReflectRef::Value(self) + } + + fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut { + #bevy_reflect_path::ReflectMut::Value(self) + } + + fn reflect_hash(&self) -> Option { + #hash_fn + } + + fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> Option { + #partial_eq_fn + } + + fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> { + #serialize_fn + } + } + }) +} +struct ReflectDef { + type_name: Ident, + generics: Generics, + attrs: Option, +} + +impl Parse for ReflectDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let type_ident = input.parse::()?; + let generics = input.parse::()?; + let mut lookahead = input.lookahead1(); + let mut where_clause = None; + if lookahead.peek(Where) { + where_clause = Some(input.parse()?); + lookahead = input.lookahead1(); + } + + let mut attrs = None; + if lookahead.peek(Paren) { + let content; + parenthesized!(content in input); + attrs = Some(content.parse::()?); + } + + Ok(ReflectDef { + type_name: type_ident, + generics: Generics { + where_clause, + ..generics + }, + attrs, + }) + } +} + +#[proc_macro] +pub fn impl_reflect_value(input: TokenStream) -> TokenStream { + let reflect_value_def = parse_macro_input!(input as ReflectDef); + + let manifest = Manifest::new().unwrap(); + let crate_path = if let Some(package) = manifest.find(|name| name == "bevy") { + format!("{}::reflect", package.name) + } else if let Some(package) = manifest.find(|name| name == "bevy_reflect") { + package.name + } else { + "crate".to_string() + }; + let bevy_reflect_path = get_path(&crate_path); + let ty = &reflect_value_def.type_name; + let reflect_attrs = reflect_value_def + .attrs + .unwrap_or_else(ReflectAttrs::default); + let registration_data = &reflect_attrs.data; + let get_type_registration_impl = impl_get_type_registration( + ty, + &bevy_reflect_path, + registration_data, + &reflect_value_def.generics, + ); + impl_value( + ty, + &reflect_value_def.generics, + get_type_registration_impl, + &bevy_reflect_path, + &reflect_attrs, + ) +} + +#[derive(Default)] +struct ReflectAttrs { + reflect_hash: TraitImpl, + reflect_partial_eq: TraitImpl, + serialize: TraitImpl, + data: Vec, +} + +impl ReflectAttrs { + fn from_nested_metas(nested_metas: &Punctuated) -> Self { + let mut attrs = ReflectAttrs::default(); + for nested_meta in nested_metas.iter() { + match nested_meta { + NestedMeta::Lit(_) => {} + NestedMeta::Meta(meta) => match meta { + Meta::Path(path) => { + if let Some(segment) = path.segments.iter().next() { + let ident = segment.ident.to_string(); + match ident.as_str() { + "PartialEq" => attrs.reflect_partial_eq = TraitImpl::Implemented, + "Hash" => attrs.reflect_hash = TraitImpl::Implemented, + "Serialize" => attrs.serialize = TraitImpl::Implemented, + _ => attrs.data.push(Ident::new( + &format!("Reflect{}", segment.ident), + Span::call_site(), + )), + } + } + } + Meta::List(list) => { + let ident = if let Some(segment) = list.path.segments.iter().next() { + segment.ident.to_string() + } else { + continue; + }; + + if let Some(list_nested) = list.nested.iter().next() { + match list_nested { + NestedMeta::Meta(list_nested_meta) => match list_nested_meta { + Meta::Path(path) => { + if let Some(segment) = path.segments.iter().next() { + match ident.as_str() { + "PartialEq" => { + attrs.reflect_partial_eq = + TraitImpl::Custom(segment.ident.clone()) + } + "Hash" => { + attrs.reflect_hash = + TraitImpl::Custom(segment.ident.clone()) + } + "Serialize" => { + attrs.serialize = + TraitImpl::Custom(segment.ident.clone()) + } + _ => {} + } + } + } + Meta::List(_) => {} + Meta::NameValue(_) => {} + }, + NestedMeta::Lit(_) => {} + } + } + } + Meta::NameValue(_) => {} + }, + } + } + + attrs + } + + fn get_hash_impl(&self, path: &Path) -> proc_macro2::TokenStream { + match &self.reflect_hash { + TraitImpl::Implemented => quote! { + use std::hash::{Hash, Hasher}; + let mut hasher = #path::ReflectHasher::default(); + Hash::hash(&std::any::Any::type_id(self), &mut hasher); + Hash::hash(self, &mut hasher); + Some(hasher.finish()) + }, + TraitImpl::Custom(impl_fn) => quote! { + Some(#impl_fn(self)) + }, + TraitImpl::NotImplemented => quote! { + None + }, + } + } + + fn get_partial_eq_impl(&self) -> proc_macro2::TokenStream { + match &self.reflect_partial_eq { + TraitImpl::Implemented => quote! { + let value = value.any(); + if let Some(value) = value.downcast_ref::() { + Some(std::cmp::PartialEq::eq(self, value)) + } else { + Some(false) + } + }, + TraitImpl::Custom(impl_fn) => quote! { + Some(#impl_fn(self, value)) + }, + TraitImpl::NotImplemented => quote! { + None + }, + } + } + + fn get_serialize_impl(&self, path: &Path) -> proc_macro2::TokenStream { + match &self.serialize { + TraitImpl::Implemented => quote! { + Some(#path::serde::Serializable::Borrowed(self)) + }, + TraitImpl::Custom(impl_fn) => quote! { + Some(#impl_fn(self)) + }, + TraitImpl::NotImplemented => quote! { + None + }, + } + } +} + +impl Parse for ReflectAttrs { + fn parse(input: ParseStream) -> syn::Result { + let result = Punctuated::::parse_terminated(input)?; + Ok(ReflectAttrs::from_nested_metas(&result)) + } +} + +fn impl_get_type_registration( + type_name: &Ident, + bevy_reflect_path: &Path, + registration_data: &[Ident], + generics: &Generics, +) -> proc_macro2::TokenStream { + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + quote! { + #[allow(unused_mut)] + impl #impl_generics #bevy_reflect_path::GetTypeRegistration for #type_name#ty_generics #where_clause { + fn get_type_registration() -> #bevy_reflect_path::TypeRegistration { + let mut registration = #bevy_reflect_path::TypeRegistration::of::<#type_name#ty_generics>(); + #(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<#type_name#ty_generics>::from_type());)* + registration + } + } + } +} + +// From https://github.com/randomPoison/type-uuid +#[proc_macro_derive(TypeUuid, attributes(uuid))] +pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + type_uuid::type_uuid_derive(input) +} + +#[proc_macro] +pub fn external_type_uuid(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + type_uuid::external_type_uuid(tokens) +} + +#[proc_macro_attribute] +pub fn reflect_trait(args: TokenStream, input: TokenStream) -> TokenStream { + reflect_trait::reflect_trait(args, input) +} diff --git a/crates/bevy_property/bevy_property_derive/src/modules.rs b/crates/bevy_reflect/bevy_reflect_derive/src/modules.rs similarity index 57% rename from crates/bevy_property/bevy_property_derive/src/modules.rs rename to crates/bevy_reflect/bevy_reflect_derive/src/modules.rs index a549f9b90a2b9..e0a5109124c2a 100644 --- a/crates/bevy_property/bevy_property_derive/src/modules.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/modules.rs @@ -4,38 +4,42 @@ use syn::Path; #[derive(Debug)] pub struct Modules { - pub bevy_property: String, + pub bevy_reflect: String, } impl Modules { pub fn meta(name: &str) -> Modules { Modules { - bevy_property: format!("{}::property", name), + bevy_reflect: format!("{}::reflect", name), } } pub fn external() -> Modules { Modules { - bevy_property: "bevy_property".to_string(), + bevy_reflect: "bevy_reflect".to_string(), + } + } + + pub fn internal() -> Modules { + Modules { + bevy_reflect: "crate".to_string(), } } } -fn get_meta() -> Option { +pub fn get_modules() -> Modules { let manifest = Manifest::new().unwrap(); if let Some(package) = manifest.find(|name| name == "bevy") { - Some(Modules::meta(&package.name)) + Modules::meta(&package.name) } else if let Some(package) = manifest.find(|name| name == "bevy_internal") { - Some(Modules::meta(&package.name)) + Modules::meta(&package.name) + } else if let Some(_package) = manifest.find(|name| name == "bevy_reflect") { + Modules::external() } else { - None + Modules::internal() } } -pub fn get_modules() -> Modules { - get_meta().unwrap_or_else(Modules::external) -} - pub fn get_path(path_str: &str) -> Path { syn::parse(path_str.parse::().unwrap()).unwrap() } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/reflect_trait.rs b/crates/bevy_reflect/bevy_reflect_derive/src/reflect_trait.rs new file mode 100644 index 0000000000000..d4378d664d874 --- /dev/null +++ b/crates/bevy_reflect/bevy_reflect_derive/src/reflect_trait.rs @@ -0,0 +1,66 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{parse::Parse, parse_macro_input, Attribute, Ident, ItemTrait, Token}; + +use crate::modules::{get_modules, get_path}; + +pub struct TraitInfo { + item_trait: ItemTrait, +} + +impl Parse for TraitInfo { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; + let lookahead = input.lookahead1(); + if lookahead.peek(Token![pub]) || lookahead.peek(Token![trait]) { + let mut item_trait: ItemTrait = input.parse()?; + item_trait.attrs = attrs; + Ok(TraitInfo { item_trait }) + } else { + Err(lookahead.error()) + } + } +} + +pub fn reflect_trait(_args: TokenStream, input: TokenStream) -> TokenStream { + let trait_info = parse_macro_input!(input as TraitInfo); + let item_trait = &trait_info.item_trait; + let trait_ident = &item_trait.ident; + let reflect_trait_ident = + Ident::new(&format!("Reflect{}", item_trait.ident), Span::call_site()); + let modules = get_modules(); + let bevy_reflect_path = get_path(&modules.bevy_reflect); + TokenStream::from(quote! { + #item_trait + + #[derive(Clone)] + pub struct #reflect_trait_ident { + get_func: fn(&dyn #bevy_reflect_path::Reflect) -> Option<&dyn #trait_ident>, + get_mut_func: fn(&mut dyn #bevy_reflect_path::Reflect) -> Option<&mut dyn #trait_ident>, + } + + impl #reflect_trait_ident { + fn get<'a>(&self, reflect_value: &'a dyn #bevy_reflect_path::Reflect) -> Option<&'a dyn #trait_ident> { + (self.get_func)(reflect_value) + } + + fn get_mut<'a>(&self, reflect_value: &'a mut dyn #bevy_reflect_path::Reflect) -> Option<&'a mut dyn #trait_ident> { + (self.get_mut_func)(reflect_value) + } + } + + impl #bevy_reflect_path::FromType for #reflect_trait_ident { + fn from_type() -> Self { + Self { + get_func: |reflect_value| { + reflect_value.downcast_ref::().map(|value| value as &dyn #trait_ident) + }, + get_mut_func: |reflect_value| { + reflect_value.downcast_mut::().map(|value| value as &mut dyn #trait_ident) + } + } + } + } + }) +} diff --git a/crates/bevy_derive/src/type_uuid.rs b/crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs similarity index 83% rename from crates/bevy_derive/src/type_uuid.rs rename to crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs index 9078ff3be1353..800de584f7107 100644 --- a/crates/bevy_derive/src/type_uuid.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/type_uuid.rs @@ -10,8 +10,8 @@ pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre // Construct a representation of Rust code as a syntax tree // that we can manipulate let ast: DeriveInput = syn::parse(input).unwrap(); - let modules = get_modules(&ast.attrs); - let bevy_type_registry_path: Path = get_path(&modules.bevy_type_registry); + let modules = get_modules(); + let bevy_reflect_path: Path = get_path(&modules.bevy_reflect); // Build the trait implementation let name = &ast.ident; @@ -35,17 +35,17 @@ pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre let uuid_str = match name_value.lit { Lit::Str(lit_str) => lit_str, - _ => panic!("uuid attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`"), + _ => panic!("`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`."), }; uuid = Some( Uuid::parse_str(&uuid_str.value()) - .expect("Value specified to `#[uuid]` attribute is not a valid UUID"), + .expect("Value specified to `#[uuid]` attribute is not a valid UUID."), ); } let uuid = - uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found"); + uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found."); let bytes = uuid .as_bytes() .iter() @@ -53,8 +53,8 @@ pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre .map(|byte_str| syn::parse_str::(&byte_str).unwrap()); let gen = quote! { - impl #bevy_type_registry_path::TypeUuid for #name { - const TYPE_UUID: #bevy_type_registry_path::Uuid = #bevy_type_registry_path::Uuid::from_bytes([ + impl #bevy_reflect_path::TypeUuid for #name { + const TYPE_UUID: #bevy_reflect_path::Uuid = #bevy_reflect_path::Uuid::from_bytes([ #( #bytes ),* ]); } @@ -79,7 +79,7 @@ impl Parse for ExternalDeriveInput { pub fn external_type_uuid(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { let ExternalDeriveInput { path, uuid_str } = parse_macro_input!(tokens as ExternalDeriveInput); - let uuid = Uuid::parse_str(&uuid_str.value()).expect("Value was not a valid UUID"); + let uuid = Uuid::parse_str(&uuid_str.value()).expect("Value was not a valid UUID."); let bytes = uuid .as_bytes() diff --git a/crates/bevy_reflect/src/impls/bevy_app.rs b/crates/bevy_reflect/src/impls/bevy_app.rs new file mode 100644 index 0000000000000..8c142783a0ecf --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_app.rs @@ -0,0 +1,57 @@ +use crate::{impl_reflect_value, GetTypeRegistration, ReflectDeserialize, TypeRegistryArc}; +use bevy_app::{AppBuilder, Plugin}; +use bevy_ecs::Entity; + +#[derive(Default)] +pub struct ReflectPlugin; + +impl Plugin for ReflectPlugin { + fn build(&self, app: &mut AppBuilder) { + app.init_resource::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + #[cfg(feature = "glam")] + { + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::(); + } + #[cfg(feature = "bevy_ecs")] + { + app.register_type::(); + } + } +} + +impl_reflect_value!(Entity(Hash, PartialEq, Serialize, Deserialize)); + +pub trait RegisterTypeBuilder { + fn register_type(&mut self) -> &mut Self; +} + +impl RegisterTypeBuilder for AppBuilder { + fn register_type(&mut self) -> &mut Self { + { + let registry = self.resources().get_mut::().unwrap(); + registry.write().register::(); + } + self + } +} diff --git a/crates/bevy_reflect/src/impls/bevy_ecs.rs b/crates/bevy_reflect/src/impls/bevy_ecs.rs new file mode 100644 index 0000000000000..4be4a77bacb62 --- /dev/null +++ b/crates/bevy_reflect/src/impls/bevy_ecs.rs @@ -0,0 +1,219 @@ +use crate::{FromType, Reflect}; +use bevy_ecs::{ + Archetype, Component, Entity, EntityMap, FromResources, MapEntities, MapEntitiesError, + Resources, World, +}; +use std::marker::PhantomData; + +#[derive(Clone)] +pub struct ReflectComponent { + add_component: fn(&mut World, resources: &Resources, Entity, &dyn Reflect), + apply_component: fn(&mut World, Entity, &dyn Reflect), + reflect_component: unsafe fn(&Archetype, usize) -> &dyn Reflect, + copy_component: fn(&World, &mut World, &Resources, Entity, Entity), +} + +impl ReflectComponent { + pub fn add_component( + &self, + world: &mut World, + resources: &Resources, + entity: Entity, + component: &dyn Reflect, + ) { + (self.add_component)(world, resources, entity, component); + } + + pub fn apply_component(&self, world: &mut World, entity: Entity, component: &dyn Reflect) { + (self.apply_component)(world, entity, component); + } + + /// # Safety + /// This does not do bound checks on entity_index. You must make sure entity_index is within bounds before calling. + pub unsafe fn reflect_component<'a>( + &self, + archetype: &'a Archetype, + entity_index: usize, + ) -> &'a dyn Reflect { + (self.reflect_component)(archetype, entity_index) + } + + pub fn copy_component( + &self, + source_world: &World, + destination_world: &mut World, + resources: &Resources, + source_entity: Entity, + destination_entity: Entity, + ) { + (self.copy_component)( + source_world, + destination_world, + resources, + source_entity, + destination_entity, + ); + } +} + +impl FromType for ReflectComponent { + fn from_type() -> Self { + ReflectComponent { + add_component: |world, resources, entity, reflected_component| { + let mut component = C::from_resources(resources); + component.apply(reflected_component); + world.insert_one(entity, component).unwrap(); + }, + apply_component: |world, entity, reflected_component| { + let mut component = world.get_mut::(entity).unwrap(); + component.apply(reflected_component); + }, + copy_component: |source_world, + destination_world, + resources, + source_entity, + destination_entity| { + let source_component = source_world.get::(source_entity).unwrap(); + let mut destination_component = C::from_resources(resources); + destination_component.apply(source_component); + destination_world + .insert_one(destination_entity, destination_component) + .unwrap(); + }, + reflect_component: |archetype, index| { + unsafe { + // the type has been looked up by the caller, so this is safe + let ptr = archetype.get::().unwrap().as_ptr().add(index); + ptr.as_ref().unwrap() + } + }, + } + } +} + +#[derive(Clone)] +pub struct SceneComponent { + copy_scene_to_runtime: fn(&World, &mut World, &Resources, Entity, Entity), + marker: PhantomData<(Scene, Runtime)>, +} + +impl, Runtime: Component> SceneComponent { + pub fn copy_scene_to_runtime( + &self, + scene_world: &World, + runtime_world: &mut World, + resources: &Resources, + scene_entity: Entity, + runtime_entity: Entity, + ) { + (self.copy_scene_to_runtime)( + scene_world, + runtime_world, + resources, + scene_entity, + runtime_entity, + ); + } +} + +impl, Runtime: Component> FromType + for SceneComponent +{ + fn from_type() -> Self { + SceneComponent { + copy_scene_to_runtime: |scene_world, + runtime_world, + resources, + scene_entity, + runtime_entity| { + let scene_component = scene_world.get::(scene_entity).unwrap(); + let destination_component = scene_component.into_component(resources); + runtime_world + .insert_one(runtime_entity, destination_component) + .unwrap(); + }, + marker: Default::default(), + } + } +} + +#[derive(Clone)] +pub struct RuntimeComponent { + copy_runtime_to_scene: fn(&World, &mut World, &Resources, Entity, Entity), + marker: PhantomData<(Runtime, Scene)>, +} + +impl, Scene: Component> RuntimeComponent { + pub fn copy_runtime_to_scene( + &self, + runtime_world: &World, + scene_world: &mut World, + resources: &Resources, + runtime_entity: Entity, + scene_entity: Entity, + ) { + (self.copy_runtime_to_scene)( + runtime_world, + scene_world, + resources, + runtime_entity, + scene_entity, + ); + } +} + +impl, Scene: Component> FromType + for RuntimeComponent +{ + fn from_type() -> Self { + RuntimeComponent { + copy_runtime_to_scene: |runtime_world, + scene_world, + resources, + runtime_entity, + scene_entity| { + let runtime_component = runtime_world.get::(runtime_entity).unwrap(); + let scene_component = runtime_component.into_component(resources); + scene_world + .insert_one(scene_entity, scene_component) + .unwrap(); + }, + marker: Default::default(), + } + } +} + +#[derive(Clone)] +pub struct ReflectMapEntities { + map_entities: fn(&mut World, &EntityMap) -> Result<(), MapEntitiesError>, +} + +impl ReflectMapEntities { + pub fn map_entities( + &self, + world: &mut World, + entity_map: &EntityMap, + ) -> Result<(), MapEntitiesError> { + (self.map_entities)(world, entity_map) + } +} + +impl FromType for ReflectMapEntities { + fn from_type() -> Self { + ReflectMapEntities { + map_entities: |world, entity_map| { + for entity in entity_map.values() { + if let Ok(mut component) = world.get_mut::(entity) { + component.map_entities(entity_map)?; + } + } + + Ok(()) + }, + } + } +} + +pub trait IntoComponent { + fn into_component(&self, resources: &Resources) -> ToComponent; +} diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs new file mode 100644 index 0000000000000..f7fd83fe70808 --- /dev/null +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -0,0 +1,10 @@ +use crate::ReflectDeserialize; +use bevy_reflect_derive::impl_reflect_value; +use glam::{Mat3, Mat4, Quat, Vec2, Vec3, Vec4}; + +impl_reflect_value!(Vec2(PartialEq, Serialize, Deserialize)); +impl_reflect_value!(Vec3(PartialEq, Serialize, Deserialize)); +impl_reflect_value!(Vec4(PartialEq, Serialize, Deserialize)); +impl_reflect_value!(Mat3(PartialEq, Serialize, Deserialize)); +impl_reflect_value!(Mat4(PartialEq, Serialize, Deserialize)); +impl_reflect_value!(Quat(PartialEq, Serialize, Deserialize)); diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs new file mode 100644 index 0000000000000..589bd4c0b1327 --- /dev/null +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -0,0 +1,96 @@ +use smallvec::{Array, SmallVec}; +use std::any::Any; + +use crate::{serde::Serializable, List, ListIter, Reflect, ReflectMut, ReflectRef}; + +impl List for SmallVec +where + T::Item: Reflect + Clone, +{ + fn get(&self, index: usize) -> Option<&dyn Reflect> { + if index < SmallVec::len(self) { + Some(&self[index] as &dyn Reflect) + } else { + None + } + } + + fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> { + if index < SmallVec::len(self) { + Some(&mut self[index] as &mut dyn Reflect) + } else { + None + } + } + + fn len(&self) -> usize { + >::len(self) + } + + fn push(&mut self, value: Box) { + let value = value.take::().unwrap_or_else(|value| { + panic!( + "Attempted to push invalid value of type {}.", + value.type_name() + ) + }); + SmallVec::push(self, value); + } + + fn iter(&self) -> ListIter { + ListIter { + list: self, + index: 0, + } + } +} + +impl Reflect for SmallVec +where + T::Item: Reflect + Clone, +{ + fn type_name(&self) -> &str { + std::any::type_name::() + } + + fn any(&self) -> &dyn Any { + self + } + + fn any_mut(&mut self) -> &mut dyn Any { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + crate::list_apply(self, value); + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::List(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::List(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + crate::list_partial_eq(self, value) + } + + fn serializable(&self) -> Option { + None + } +} diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs new file mode 100644 index 0000000000000..0af5cead11d11 --- /dev/null +++ b/crates/bevy_reflect/src/impls/std.rs @@ -0,0 +1,201 @@ +use crate::{ + map_partial_eq, serde::Serializable, DynamicMap, List, ListIter, Map, MapIter, Reflect, + ReflectDeserialize, ReflectMut, ReflectRef, +}; + +use bevy_reflect_derive::impl_reflect_value; +use bevy_utils::{HashMap, HashSet}; +use serde::{Deserialize, Serialize}; +use std::{any::Any, hash::Hash, ops::Range}; + +impl_reflect_value!(bool(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u8(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u16(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u32(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u64(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(u128(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(usize(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i8(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i16(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i32(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i64(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(i128(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(isize(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(f32(Serialize, Deserialize)); +impl_reflect_value!(f64(Serialize, Deserialize)); +impl_reflect_value!(String(Hash, PartialEq, Serialize, Deserialize)); +impl_reflect_value!(Option Deserialize<'de> + Reflect + 'static>(Serialize, Deserialize)); +impl_reflect_value!(HashSet Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize)); +impl_reflect_value!(Range Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize)); + +impl List for Vec { + fn get(&self, index: usize) -> Option<&dyn Reflect> { + <[T]>::get(self, index).map(|value| value as &dyn Reflect) + } + + fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> { + <[T]>::get_mut(self, index).map(|value| value as &mut dyn Reflect) + } + + fn len(&self) -> usize { + <[T]>::len(self) + } + + fn iter(&self) -> ListIter { + ListIter { + list: self, + index: 0, + } + } + + fn push(&mut self, value: Box) { + let value = value.take::().unwrap_or_else(|value| { + panic!( + "Attempted to push invalid value of type {}.", + value.type_name() + ) + }); + Vec::push(self, value); + } +} + +impl Reflect for Vec { + fn type_name(&self) -> &str { + std::any::type_name::() + } + + fn any(&self) -> &dyn Any { + self + } + + fn any_mut(&mut self) -> &mut dyn Any { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + crate::list_apply(self, value); + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::List(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::List(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + crate::list_partial_eq(self, value) + } + + fn serializable(&self) -> Option { + None + } +} + +impl Map for HashMap { + fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { + key.downcast_ref::() + .and_then(|key| HashMap::get(self, key)) + .map(|value| value as &dyn Reflect) + } + + fn get_mut(&mut self, key: &dyn Reflect) -> Option<&mut dyn Reflect> { + key.downcast_ref::() + .and_then(move |key| HashMap::get_mut(self, key)) + .map(|value| value as &mut dyn Reflect) + } + + fn get_at(&self, index: usize) -> Option<(&dyn Reflect, &dyn Reflect)> { + self.iter() + .nth(index) + .map(|(key, value)| (key as &dyn Reflect, value as &dyn Reflect)) + } + + fn len(&self) -> usize { + HashMap::len(self) + } + + fn iter(&self) -> MapIter { + MapIter { + map: self, + index: 0, + } + } + + fn clone_dynamic(&self) -> DynamicMap { + let mut dynamic_map = DynamicMap::default(); + for (k, v) in HashMap::iter(self) { + dynamic_map.insert_boxed(k.clone_value(), v.clone_value()); + } + dynamic_map + } +} + +impl Reflect for HashMap { + fn type_name(&self) -> &str { + std::any::type_name::() + } + + fn any(&self) -> &dyn Any { + self + } + + fn any_mut(&mut self) -> &mut dyn Any { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + if let ReflectRef::Map(map_value) = value.reflect_ref() { + for (key, value) in map_value.iter() { + if let Some(v) = Map::get_mut(self, key) { + v.apply(value) + } + } + } else { + panic!("Attempted to apply a non-map type to a map type."); + } + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Map(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Map(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + map_partial_eq(self, value) + } + + fn serializable(&self) -> Option { + None + } +} diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs new file mode 100644 index 0000000000000..8b5bdeb67d42a --- /dev/null +++ b/crates/bevy_reflect/src/lib.rs @@ -0,0 +1,323 @@ +mod list; +mod map; +mod path; +mod reflect; +mod struct_trait; +mod tuple_struct; +mod type_registry; +mod type_uuid; +mod impls { + #[cfg(feature = "bevy_app")] + mod bevy_app; + #[cfg(feature = "bevy_ecs")] + mod bevy_ecs; + #[cfg(feature = "glam")] + mod glam; + #[cfg(feature = "smallvec")] + mod smallvec; + mod std; + + #[cfg(feature = "bevy_app")] + pub use self::bevy_app::*; + #[cfg(feature = "bevy_ecs")] + pub use self::bevy_ecs::*; + #[cfg(feature = "glam")] + pub use self::glam::*; + #[cfg(feature = "smallvec")] + pub use self::smallvec::*; + pub use self::std::*; +} + +pub mod serde; +pub mod prelude { + #[cfg(feature = "bevy_ecs")] + pub use crate::ReflectComponent; + #[cfg(feature = "bevy_app")] + pub use crate::RegisterTypeBuilder; + pub use crate::{ + reflect_trait, GetField, GetTupleStructField, Reflect, ReflectDeserialize, Struct, + TupleStruct, + }; +} + +pub use impls::*; +pub use list::*; +pub use map::*; +pub use path::*; +pub use reflect::*; +pub use struct_trait::*; +pub use tuple_struct::*; +pub use type_registry::*; +pub use type_uuid::*; + +pub use bevy_reflect_derive::*; +pub use erased_serde; + +#[cfg(test)] +mod tests { + use ::serde::de::DeserializeSeed; + use bevy_utils::HashMap; + use ron::{ + ser::{to_string_pretty, PrettyConfig}, + Deserializer, + }; + + use crate::serde::{ReflectDeserializer, ReflectSerializer}; + + use super::*; + #[test] + fn reflect_struct() { + #[derive(Reflect)] + struct Foo { + a: u32, + b: f32, + c: Bar, + } + #[derive(Reflect)] + struct Bar { + x: u32, + } + + let mut foo = Foo { + a: 42, + b: 3.14, + c: Bar { x: 1 }, + }; + + let a = *foo.get_field::("a").unwrap(); + assert_eq!(a, 42); + + *foo.get_field_mut::("a").unwrap() += 1; + assert_eq!(foo.a, 43); + + let bar = foo.get_field::("c").unwrap(); + assert_eq!(bar.x, 1); + + // nested retrieval + let c = foo.field("c").unwrap(); + if let ReflectRef::Struct(value) = c.reflect_ref() { + assert_eq!(*value.get_field::("x").unwrap(), 1); + } else { + panic!("Expected a struct."); + } + + // patch Foo with a dynamic struct + let mut dynamic_struct = DynamicStruct::default(); + dynamic_struct.insert("a", 123u32); + dynamic_struct.insert("should_be_ignored", 456); + + foo.apply(&dynamic_struct); + assert_eq!(foo.a, 123); + } + + #[test] + fn reflect_map() { + #[derive(Reflect, Hash)] + #[reflect(Hash)] + struct Foo { + a: u32, + b: String, + } + + let key_a = Foo { + a: 1, + b: "k1".to_string(), + }; + + let key_b = Foo { + a: 1, + b: "k1".to_string(), + }; + + let key_c = Foo { + a: 3, + b: "k3".to_string(), + }; + + let mut map = DynamicMap::default(); + map.insert(key_a, 10u32); + assert_eq!(10, *map.get(&key_b).unwrap().downcast_ref::().unwrap()); + assert!(map.get(&key_c).is_none()); + *map.get_mut(&key_b).unwrap().downcast_mut::().unwrap() = 20; + assert_eq!(20, *map.get(&key_b).unwrap().downcast_ref::().unwrap()); + } + + #[test] + fn reflect_unit_struct() { + #[derive(Reflect)] + struct Foo(u32, u64); + + let mut foo = Foo(1, 2); + assert_eq!(1, *foo.get_field::(0).unwrap()); + assert_eq!(2, *foo.get_field::(1).unwrap()); + + let mut patch = DynamicTupleStruct::default(); + patch.insert(3u32); + patch.insert(4u64); + assert_eq!(3, *patch.field(0).unwrap().downcast_ref::().unwrap()); + assert_eq!(4, *patch.field(1).unwrap().downcast_ref::().unwrap()); + + foo.apply(&patch); + assert_eq!(3, foo.0); + assert_eq!(4, foo.1); + + let mut iter = patch.iter_fields(); + assert_eq!(3, *iter.next().unwrap().downcast_ref::().unwrap()); + assert_eq!(4, *iter.next().unwrap().downcast_ref::().unwrap()); + } + + #[test] + #[should_panic(expected = "the given key does not support hashing")] + fn reflect_map_no_hash() { + #[derive(Reflect)] + struct Foo { + a: u32, + } + + let foo = Foo { a: 1 }; + + let mut map = DynamicMap::default(); + map.insert(foo, 10u32); + } + + #[test] + fn reflect_ignore() { + #[derive(Reflect)] + struct Foo { + a: u32, + #[reflect(ignore)] + _b: u32, + } + + let foo = Foo { a: 1, _b: 2 }; + + let values: Vec = foo + .iter_fields() + .map(|value| *value.downcast_ref::().unwrap()) + .collect(); + assert_eq!(values, vec![1]); + } + + #[test] + fn reflect_complex_patch() { + #[derive(Reflect, Eq, PartialEq, Debug)] + struct Foo { + a: u32, + #[reflect(ignore)] + _b: u32, + c: Vec, + d: HashMap, + e: Bar, + } + + #[derive(Reflect, Eq, PartialEq, Debug)] + struct Bar { + x: u32, + } + + let mut hash_map = HashMap::default(); + hash_map.insert(1, 1); + hash_map.insert(2, 2); + let mut foo = Foo { + a: 1, + _b: 1, + c: vec![1, 2], + d: hash_map, + e: Bar { x: 1 }, + }; + + let mut foo_patch = DynamicStruct::default(); + foo_patch.insert("a", 2u32); + foo_patch.insert("b", 2u32); // this should be ignored + + let mut list = DynamicList::default(); + list.push(3isize); + list.push(4isize); + list.push(5isize); + foo_patch.insert("c", list); + + let mut map = DynamicMap::default(); + map.insert(2usize, 3i8); + foo_patch.insert("d", map); + + let mut bar_patch = DynamicStruct::default(); + bar_patch.insert("x", 2u32); + foo_patch.insert("e", bar_patch); + + foo.apply(&foo_patch); + + let mut hash_map = HashMap::default(); + hash_map.insert(1, 1); + hash_map.insert(2, 3); + let expected_foo = Foo { + a: 2, + _b: 1, + c: vec![3, 4, 5], + d: hash_map, + e: Bar { x: 2 }, + }; + + assert_eq!(foo, expected_foo); + } + + #[test] + fn reflect_serialize() { + #[derive(Reflect)] + struct Foo { + a: u32, + #[reflect(ignore)] + _b: u32, + c: Vec, + d: HashMap, + e: Bar, + f: String, + } + + #[derive(Reflect)] + struct Bar { + x: u32, + } + + let mut hash_map = HashMap::default(); + hash_map.insert(1, 1); + hash_map.insert(2, 2); + let foo = Foo { + a: 1, + _b: 1, + c: vec![1, 2], + d: hash_map, + e: Bar { x: 1 }, + f: "hi".to_string(), + }; + + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + + let serializer = ReflectSerializer::new(&foo, ®istry); + let serialized = to_string_pretty(&serializer, PrettyConfig::default()).unwrap(); + + let mut deserializer = Deserializer::from_str(&serialized).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let value = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + let dynamic_struct = value.take::().unwrap(); + + assert!(foo.reflect_partial_eq(&dynamic_struct).unwrap()); + } + + #[test] + fn reflect_take() { + #[derive(Reflect, Debug, PartialEq)] + struct Bar { + x: u32, + } + + let x: Box = Box::new(Bar { x: 2 }); + let y = x.take::().unwrap(); + assert_eq!(y, Bar { x: 2 }); + } +} diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs new file mode 100644 index 0000000000000..4506bb3ba0be8 --- /dev/null +++ b/crates/bevy_reflect/src/list.rs @@ -0,0 +1,178 @@ +use std::any::Any; + +use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; + +/// An ordered, mutable list of [ReflectValue] items. This corresponds to types like [std::vec::Vec]. +pub trait List: Reflect { + fn get(&self, index: usize) -> Option<&dyn Reflect>; + fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect>; + fn push(&mut self, value: Box); + fn len(&self) -> usize; + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn iter(&self) -> ListIter; + fn clone_dynamic(&self) -> DynamicList { + DynamicList { + values: self.iter().map(|value| value.clone_value()).collect(), + } + } +} + +#[derive(Default)] +pub struct DynamicList { + pub(crate) values: Vec>, +} + +impl DynamicList { + pub fn push(&mut self, value: T) { + self.values.push(Box::new(value)); + } + + pub fn push_box(&mut self, value: Box) { + self.values.push(value); + } +} + +impl List for DynamicList { + fn get(&self, index: usize) -> Option<&dyn Reflect> { + self.values.get(index).map(|value| &**value) + } + + fn get_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> { + self.values.get_mut(index).map(|value| &mut **value) + } + + fn len(&self) -> usize { + self.values.len() + } + + fn clone_dynamic(&self) -> DynamicList { + DynamicList { + values: self + .values + .iter() + .map(|value| value.clone_value()) + .collect(), + } + } + + fn iter(&self) -> ListIter { + ListIter { + list: self, + index: 0, + } + } + + fn push(&mut self, value: Box) { + DynamicList::push_box(self, value); + } +} + +impl Reflect for DynamicList { + #[inline] + fn type_name(&self) -> &str { + std::any::type_name::() + } + + #[inline] + fn any(&self) -> &dyn Any { + self + } + + #[inline] + fn any_mut(&mut self) -> &mut dyn Any { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + list_apply(self, value); + } + + #[inline] + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + #[inline] + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::List(self) + } + + #[inline] + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::List(self) + } + + #[inline] + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + #[inline] + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + list_partial_eq(self, value) + } + + fn serializable(&self) -> Option { + None + } +} + +pub struct ListIter<'a> { + pub(crate) list: &'a dyn List, + pub(crate) index: usize, +} + +impl<'a> Iterator for ListIter<'a> { + type Item = &'a dyn Reflect; + + fn next(&mut self) -> Option { + let value = self.list.get(self.index); + self.index += 1; + value + } +} + +#[inline] +pub fn list_apply(a: &mut L, b: &dyn Reflect) { + if let ReflectRef::List(list_value) = b.reflect_ref() { + for (i, value) in list_value.iter().enumerate() { + if i < a.len() { + if let Some(v) = a.get_mut(i) { + v.apply(value); + } + } else { + List::push(a, value.clone_value()); + } + } + } else { + panic!("Attempted to apply a non-list type to a list type."); + } +} + +#[inline] +pub fn list_partial_eq(a: &L, b: &dyn Reflect) -> Option { + let list = if let ReflectRef::List(list) = b.reflect_ref() { + list + } else { + return Some(false); + }; + + if a.len() != list.len() { + return Some(false); + } + + for (a_value, b_value) in a.iter().zip(list.iter()) { + if let Some(false) | None = a_value.reflect_partial_eq(b_value) { + return Some(false); + } + } + + Some(true) +} diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs new file mode 100644 index 0000000000000..eb5a89467f299 --- /dev/null +++ b/crates/bevy_reflect/src/map.rs @@ -0,0 +1,184 @@ +use std::{any::Any, collections::hash_map::Entry}; + +use bevy_utils::HashMap; + +use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; + +/// An ordered ReflectValue->ReflectValue mapping. ReflectValue Keys are assumed to return a non-None hash. +/// Ideally the ordering is stable across runs, but this is not required. +/// This corresponds to types like [std::collections::HashMap]. +pub trait Map: Reflect { + fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect>; + fn get_mut(&mut self, key: &dyn Reflect) -> Option<&mut dyn Reflect>; + fn get_at(&self, index: usize) -> Option<(&dyn Reflect, &dyn Reflect)>; + fn len(&self) -> usize; + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn iter(&self) -> MapIter; + fn clone_dynamic(&self) -> DynamicMap; +} + +const HASH_ERROR: &str = "the given key does not support hashing"; + +#[derive(Default)] +pub struct DynamicMap { + pub values: Vec<(Box, Box)>, + pub indices: HashMap, +} + +impl DynamicMap { + pub fn insert(&mut self, key: K, value: V) { + self.insert_boxed(Box::new(key), Box::new(value)); + } + + pub fn insert_boxed(&mut self, key: Box, value: Box) { + match self.indices.entry(key.reflect_hash().expect(HASH_ERROR)) { + Entry::Occupied(entry) => { + self.values[*entry.get()] = (key, value); + } + Entry::Vacant(entry) => { + entry.insert(self.values.len()); + self.values.push((key, value)); + } + } + } +} + +impl Map for DynamicMap { + fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { + self.indices + .get(&key.reflect_hash().expect(HASH_ERROR)) + .map(|index| &*self.values.get(*index).unwrap().1) + } + + fn get_mut(&mut self, key: &dyn Reflect) -> Option<&mut dyn Reflect> { + self.indices + .get(&key.reflect_hash().expect(HASH_ERROR)) + .cloned() + .map(move |index| &mut *self.values.get_mut(index).unwrap().1) + } + + fn len(&self) -> usize { + self.values.len() + } + + fn clone_dynamic(&self) -> DynamicMap { + DynamicMap { + values: self + .values + .iter() + .map(|(key, value)| (key.clone_value(), value.clone_value())) + .collect(), + indices: self.indices.clone(), + } + } + + fn iter(&self) -> MapIter { + MapIter { + map: self, + index: 0, + } + } + + fn get_at(&self, index: usize) -> Option<(&dyn Reflect, &dyn Reflect)> { + self.values + .get(index) + .map(|(key, value)| (&**key, &**value)) + } +} + +impl Reflect for DynamicMap { + fn type_name(&self) -> &str { + std::any::type_name::() + } + + fn any(&self) -> &dyn Any { + self + } + + fn any_mut(&mut self) -> &mut dyn Any { + self + } + + fn apply(&mut self, value: &dyn Reflect) { + if let ReflectRef::Map(map_value) = value.reflect_ref() { + for (key, value) in map_value.iter() { + if let Some(v) = self.get_mut(key) { + v.apply(value) + } + } + } else { + panic!("Attempted to apply a non-map type to a map type."); + } + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Map(self) + } + + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Map(self) + } + + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + map_partial_eq(self, value) + } + + fn serializable(&self) -> Option { + None + } +} + +pub struct MapIter<'a> { + pub(crate) map: &'a dyn Map, + pub(crate) index: usize, +} + +impl<'a> Iterator for MapIter<'a> { + type Item = (&'a dyn Reflect, &'a dyn Reflect); + + fn next(&mut self) -> Option { + let value = self.map.get_at(self.index); + self.index += 1; + value + } +} + +#[inline] +pub fn map_partial_eq(a: &M, b: &dyn Reflect) -> Option { + let map = if let ReflectRef::Map(map) = b.reflect_ref() { + map + } else { + return Some(false); + }; + + if a.len() != map.len() { + return Some(false); + } + + for (key, value) in a.iter() { + if let Some(map_value) = map.get(key) { + if let Some(false) | None = value.reflect_partial_eq(map_value) { + return Some(false); + } + } else { + return Some(false); + } + } + + Some(true) +} diff --git a/crates/bevy_reflect/src/path.rs b/crates/bevy_reflect/src/path.rs new file mode 100644 index 0000000000000..a8232f7c594f6 --- /dev/null +++ b/crates/bevy_reflect/src/path.rs @@ -0,0 +1,385 @@ +use std::num::ParseIntError; + +use crate::{Reflect, ReflectMut, ReflectRef}; +use thiserror::Error; + +#[derive(Debug, PartialEq, Eq, Error)] +pub enum ReflectPathError<'a> { + #[error("expected an identifier at the given index")] + ExpectedIdent { index: usize }, + #[error("the current struct doesn't have a field with the given name")] + InvalidField { index: usize, field: &'a str }, + #[error("the current tuple struct doesn't have a field with the given index")] + InvalidTupleStructIndex { + index: usize, + tuple_struct_index: usize, + }, + #[error("the current list doesn't have a value at the given index")] + InvalidListIndex { index: usize, list_index: usize }, + #[error("encountered an unexpected token")] + UnexpectedToken { index: usize, token: &'a str }, + #[error("expected a token, but it wasn't there.")] + ExpectedToken { index: usize, token: &'a str }, + #[error("expected a struct, but found a different reflect value")] + ExpectedStruct { index: usize }, + #[error("expected a list, but found a different reflect value")] + ExpectedList { index: usize }, + #[error("failed to parse a usize")] + IndexParseError(#[from] ParseIntError), + #[error("failed to downcast to the path result to the given type")] + InvalidDowncast, +} + +pub trait GetPath { + fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>>; + fn path_mut<'r, 'p>( + &'r mut self, + path: &'p str, + ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>>; + + fn get_path<'r, 'p, T: Reflect>( + &'r self, + path: &'p str, + ) -> Result<&'r T, ReflectPathError<'p>> { + self.path(path).and_then(|p| { + p.downcast_ref::() + .ok_or(ReflectPathError::InvalidDowncast) + }) + } + + fn get_path_mut<'r, 'p, T: Reflect>( + &'r mut self, + path: &'p str, + ) -> Result<&'r mut T, ReflectPathError<'p>> { + self.path_mut(path).and_then(|p| { + p.downcast_mut::() + .ok_or(ReflectPathError::InvalidDowncast) + }) + } +} + +impl GetPath for T { + fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { + (self as &dyn Reflect).path(path) + } + + fn path_mut<'r, 'p>( + &'r mut self, + path: &'p str, + ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { + (self as &mut dyn Reflect).path_mut(path) + } +} + +impl GetPath for dyn Reflect { + fn path<'r, 'p>(&'r self, path: &'p str) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { + let mut index = 0; + let mut current: &dyn Reflect = self; + while let Some(token) = next_token(path, &mut index) { + let current_index = index; + match token { + Token::Dot => { + if let Some(Token::Ident(value)) = next_token(path, &mut index) { + current = read_field(current, value, current_index)?; + } else { + return Err(ReflectPathError::ExpectedIdent { + index: current_index, + }); + } + } + Token::OpenBracket => { + if let Some(Token::Ident(value)) = next_token(path, &mut index) { + match current.reflect_ref() { + ReflectRef::List(reflect_list) => { + let list_index = value.parse::()?; + let list_item = reflect_list.get(list_index).ok_or( + ReflectPathError::InvalidListIndex { + index: current_index, + list_index, + }, + )?; + current = list_item; + } + _ => { + return Err(ReflectPathError::ExpectedList { + index: current_index, + }) + } + } + } else { + return Err(ReflectPathError::ExpectedIdent { + index: current_index, + }); + } + + if let Some(Token::CloseBracket) = next_token(path, &mut index) { + } else { + return Err(ReflectPathError::ExpectedToken { + index: current_index, + token: "]", + }); + } + } + Token::CloseBracket => { + return Err(ReflectPathError::UnexpectedToken { + index: current_index, + token: "]", + }) + } + Token::Ident(value) => { + current = read_field(current, value, current_index)?; + } + } + } + + Ok(current) + } + + fn path_mut<'r, 'p>( + &'r mut self, + path: &'p str, + ) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { + let mut index = 0; + let mut current: &mut dyn Reflect = self; + while let Some(token) = next_token(path, &mut index) { + let current_index = index; + match token { + Token::Dot => { + if let Some(Token::Ident(value)) = next_token(path, &mut index) { + current = read_field_mut(current, value, current_index)?; + } else { + return Err(ReflectPathError::ExpectedIdent { + index: current_index, + }); + } + } + Token::OpenBracket => { + if let Some(Token::Ident(value)) = next_token(path, &mut index) { + match current.reflect_mut() { + ReflectMut::List(reflect_list) => { + let list_index = value.parse::()?; + let list_item = reflect_list.get_mut(list_index).ok_or( + ReflectPathError::InvalidListIndex { + index: current_index, + list_index, + }, + )?; + current = list_item; + } + _ => { + return Err(ReflectPathError::ExpectedStruct { + index: current_index, + }) + } + } + } else { + return Err(ReflectPathError::ExpectedIdent { + index: current_index, + }); + } + + if let Some(Token::CloseBracket) = next_token(path, &mut index) { + } else { + return Err(ReflectPathError::ExpectedToken { + index: current_index, + token: "]", + }); + } + } + Token::CloseBracket => { + return Err(ReflectPathError::UnexpectedToken { + index: current_index, + token: "]", + }) + } + Token::Ident(value) => { + current = read_field_mut(current, value, current_index)?; + } + } + } + + Ok(current) + } +} + +fn read_field<'r, 'p>( + current: &'r dyn Reflect, + field: &'p str, + current_index: usize, +) -> Result<&'r dyn Reflect, ReflectPathError<'p>> { + match current.reflect_ref() { + ReflectRef::Struct(reflect_struct) => { + Ok(reflect_struct + .field(field) + .ok_or(ReflectPathError::InvalidField { + index: current_index, + field, + })?) + } + ReflectRef::TupleStruct(reflect_struct) => { + let tuple_index = field.parse::()?; + Ok(reflect_struct.field(tuple_index).ok_or( + ReflectPathError::InvalidTupleStructIndex { + index: current_index, + tuple_struct_index: tuple_index, + }, + )?) + } + _ => Err(ReflectPathError::ExpectedStruct { + index: current_index, + }), + } +} + +fn read_field_mut<'r, 'p>( + current: &'r mut dyn Reflect, + field: &'p str, + current_index: usize, +) -> Result<&'r mut dyn Reflect, ReflectPathError<'p>> { + match current.reflect_mut() { + ReflectMut::Struct(reflect_struct) => { + Ok(reflect_struct + .field_mut(field) + .ok_or(ReflectPathError::InvalidField { + index: current_index, + field, + })?) + } + ReflectMut::TupleStruct(reflect_struct) => { + let tuple_index = field.parse::()?; + Ok(reflect_struct.field_mut(tuple_index).ok_or( + ReflectPathError::InvalidTupleStructIndex { + index: current_index, + tuple_struct_index: tuple_index, + }, + )?) + } + _ => Err(ReflectPathError::ExpectedStruct { + index: current_index, + }), + } +} + +enum Token<'a> { + Dot, + OpenBracket, + CloseBracket, + Ident(&'a str), +} + +fn next_token<'a>(path: &'a str, index: &mut usize) -> Option> { + if *index >= path.len() { + return None; + } + + match path[*index..].chars().next().unwrap() { + '.' => { + *index += 1; + return Some(Token::Dot); + } + '[' => { + *index += 1; + return Some(Token::OpenBracket); + } + ']' => { + *index += 1; + return Some(Token::CloseBracket); + } + _ => {} + } + + // we can assume we are parsing an ident now + for (char_index, character) in path[*index..].chars().enumerate() { + match character { + '.' | '[' | ']' => { + let ident = Token::Ident(&path[*index..*index + char_index]); + *index += char_index; + return Some(ident); + } + _ => {} + } + } + let ident = Token::Ident(&path[*index..]); + *index = path.len(); + Some(ident) +} + +#[cfg(test)] +mod tests { + use super::GetPath; + use crate::*; + #[test] + fn reflect_path() { + #[derive(Reflect)] + struct A { + w: usize, + x: B, + y: Vec, + z: D, + } + + #[derive(Reflect)] + struct B { + foo: usize, + bar: C, + } + + #[derive(Reflect)] + struct C { + baz: f32, + } + + #[derive(Reflect)] + struct D(E); + + #[derive(Reflect)] + struct E(f32, usize); + + let mut a = A { + w: 1, + x: B { + foo: 10, + bar: C { baz: 3.14 }, + }, + y: vec![C { baz: 1.0 }, C { baz: 2.0 }], + z: D(E(10.0, 42)), + }; + + assert_eq!(*a.get_path::("w").unwrap(), 1); + assert_eq!(*a.get_path::("x.foo").unwrap(), 10); + assert_eq!(*a.get_path::("x.bar.baz").unwrap(), 3.14); + assert_eq!(*a.get_path::("y[1].baz").unwrap(), 2.0); + assert_eq!(*a.get_path::("z.0.1").unwrap(), 42); + + *a.get_path_mut::("y[1].baz").unwrap() = 3.0; + assert_eq!(a.y[1].baz, 3.0); + + assert_eq!( + a.path("x.notreal").err().unwrap(), + ReflectPathError::InvalidField { + index: 2, + field: "notreal" + } + ); + + assert_eq!( + a.path("x..").err().unwrap(), + ReflectPathError::ExpectedIdent { index: 2 } + ); + + assert_eq!( + a.path("x[0]").err().unwrap(), + ReflectPathError::ExpectedList { index: 2 } + ); + + assert_eq!( + a.path("y.x").err().unwrap(), + ReflectPathError::ExpectedStruct { index: 2 } + ); + + assert!(matches!( + a.path("y[badindex]"), + Err(ReflectPathError::IndexParseError(_)) + )); + } +} diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs new file mode 100644 index 0000000000000..ee7a1caca2988 --- /dev/null +++ b/crates/bevy_reflect/src/reflect.rs @@ -0,0 +1,77 @@ +use crate::{serde::Serializable, List, Map, Struct, TupleStruct}; +use std::{any::Any, fmt::Debug}; + +pub use bevy_utils::AHasher as ReflectHasher; + +pub enum ReflectRef<'a> { + Struct(&'a dyn Struct), + TupleStruct(&'a dyn TupleStruct), + List(&'a dyn List), + Map(&'a dyn Map), + Value(&'a dyn Reflect), +} + +pub enum ReflectMut<'a> { + Struct(&'a mut dyn Struct), + TupleStruct(&'a mut dyn TupleStruct), + List(&'a mut dyn List), + Map(&'a mut dyn Map), + Value(&'a mut dyn Reflect), +} + +/// A reflected rust type. +pub trait Reflect: Any + Send + Sync { + fn type_name(&self) -> &str; + fn any(&self) -> &dyn Any; + fn any_mut(&mut self) -> &mut dyn Any; + fn apply(&mut self, value: &dyn Reflect); + fn set(&mut self, value: Box) -> Result<(), Box>; + fn reflect_ref(&self) -> ReflectRef; + fn reflect_mut(&mut self) -> ReflectMut; + fn clone_value(&self) -> Box; + /// Returns a hash of the value (which includes the type) if hashing is supported. Otherwise `None` will be returned. + fn reflect_hash(&self) -> Option; + /// Returns a "partial equal" comparison result if comparison is supported. Otherwise `None` will be returned. + fn reflect_partial_eq(&self, _value: &dyn Reflect) -> Option; + /// Returns a serializable value, if serialization is supported. Otherwise `None` will be returned. + fn serializable(&self) -> Option; +} + +impl Debug for dyn Reflect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("Reflect({})", self.type_name())) + } +} + +impl dyn Reflect { + pub fn downcast(self: Box) -> Result, Box> { + // SAFE?: Same approach used by std::any::Box::downcast. ReflectValue is always Any and type has been checked. + if self.is::() { + unsafe { + let raw: *mut dyn Reflect = Box::into_raw(self); + Ok(Box::from_raw(raw as *mut T)) + } + } else { + Err(self) + } + } + + pub fn take(self: Box) -> Result> { + self.downcast::().map(|value| *value) + } + + #[inline] + pub fn is(&self) -> bool { + self.any().is::() + } + + #[inline] + pub fn downcast_ref(&self) -> Option<&T> { + self.any().downcast_ref::() + } + + #[inline] + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.any_mut().downcast_mut::() + } +} diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs new file mode 100644 index 0000000000000..29203b065f3e5 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de.rs @@ -0,0 +1,403 @@ +use crate::{ + serde::type_fields, DynamicList, DynamicMap, DynamicStruct, DynamicTupleStruct, Reflect, + ReflectDeserialize, TypeRegistry, +}; +use erased_serde::Deserializer; +use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor}; + +pub trait DeserializeValue { + fn deserialize( + deserializer: &mut dyn Deserializer, + type_registry: &TypeRegistry, + ) -> Result, erased_serde::Error>; +} + +pub struct ReflectDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a> ReflectDeserializer<'a> { + pub fn new(registry: &'a TypeRegistry) -> Self { + ReflectDeserializer { registry } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for ReflectDeserializer<'a> { + type Value = Box; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(ReflectVisitor { + registry: self.registry, + }) + } +} + +struct ReflectVisitor<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for ReflectVisitor<'a> { + type Value = Box; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("reflect value") + } + + fn visit_u8(self, v: u8) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_bool(self, v: bool) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_u16(self, v: u16) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_u32(self, v: u32) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_u64(self, v: u64) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_i8(self, v: i8) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_i16(self, v: i16) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_i32(self, v: i32) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_i64(self, v: i64) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_f32(self, v: f32) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_f64(self, v: f64) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_string(self, v: String) -> Result + where + E: de::Error, + { + Ok(Box::new(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + Ok(Box::new(v.to_string())) + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut type_name: Option = None; + while let Some(key) = map.next_key::()? { + match key.as_str() { + type_fields::TYPE => { + type_name = Some(map.next_value()?); + } + type_fields::MAP => { + let _type_name = type_name + .take() + .ok_or_else(|| de::Error::missing_field(type_fields::TYPE))?; + let map = map.next_value_seed(MapDeserializer { + registry: self.registry, + })?; + return Ok(Box::new(map)); + } + type_fields::STRUCT => { + let type_name = type_name + .take() + .ok_or_else(|| de::Error::missing_field(type_fields::TYPE))?; + let mut dynamic_struct = map.next_value_seed(StructDeserializer { + registry: self.registry, + })?; + dynamic_struct.set_name(type_name); + return Ok(Box::new(dynamic_struct)); + } + type_fields::TUPLE_STRUCT => { + let type_name = type_name + .take() + .ok_or_else(|| de::Error::missing_field(type_fields::TYPE))?; + let mut tuple_struct = map.next_value_seed(TupleStructDeserializer { + registry: self.registry, + })?; + tuple_struct.set_name(type_name); + return Ok(Box::new(tuple_struct)); + } + type_fields::LIST => { + let _type_name = type_name + .take() + .ok_or_else(|| de::Error::missing_field(type_fields::TYPE))?; + let list = map.next_value_seed(ListDeserializer { + registry: self.registry, + })?; + return Ok(Box::new(list)); + } + type_fields::VALUE => { + let type_name = type_name + .take() + .ok_or_else(|| de::Error::missing_field(type_fields::TYPE))?; + let registration = + self.registry.get_with_name(&type_name).ok_or_else(|| { + de::Error::custom(format!("No registration found for {}", type_name)) + })?; + let deserialize_reflect = + registration.data::().ok_or_else(|| { + de::Error::custom(format!( + "The TypeRegistration for {} doesn't have DeserializeReflect", + type_name + )) + })?; + let value = map.next_value_seed(DeserializeReflectDeserializer { + reflect_deserialize: deserialize_reflect, + })?; + return Ok(value); + } + _ => return Err(de::Error::unknown_field(key.as_str(), &[])), + } + } + + Err(de::Error::custom("Maps in this location must have the \'type\' field and one of the following fields: \'map\', \'seq\', \'value\'")) + } +} + +struct DeserializeReflectDeserializer<'a> { + reflect_deserialize: &'a ReflectDeserialize, +} + +impl<'a, 'de> DeserializeSeed<'de> for DeserializeReflectDeserializer<'a> { + type Value = Box; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + self.reflect_deserialize.deserialize(deserializer) + } +} + +struct ListDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> DeserializeSeed<'de> for ListDeserializer<'a> { + type Value = DynamicList; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_seq(ListVisitor { + registry: self.registry, + }) + } +} + +struct ListVisitor<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for ListVisitor<'a> { + type Value = DynamicList; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("list value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let mut list = DynamicList::default(); + while let Some(value) = seq.next_element_seed(ReflectDeserializer { + registry: self.registry, + })? { + list.push_box(value); + } + Ok(list) + } +} + +struct MapDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> DeserializeSeed<'de> for MapDeserializer<'a> { + type Value = DynamicMap; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(MapVisitor { + registry: self.registry, + }) + } +} + +struct MapVisitor<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { + type Value = DynamicMap; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("map value") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut dynamic_map = DynamicMap::default(); + while let Some(key) = map.next_key_seed(ReflectDeserializer { + registry: self.registry, + })? { + let value = map.next_value_seed(ReflectDeserializer { + registry: self.registry, + })?; + dynamic_map.insert_boxed(key, value); + } + + Ok(dynamic_map) + } +} + +struct StructDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> DeserializeSeed<'de> for StructDeserializer<'a> { + type Value = DynamicStruct; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_map(StructVisitor { + registry: self.registry, + }) + } +} + +struct StructVisitor<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for StructVisitor<'a> { + type Value = DynamicStruct; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct value") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut dynamic_struct = DynamicStruct::default(); + while let Some(key) = map.next_key::()? { + let value = map.next_value_seed(ReflectDeserializer { + registry: self.registry, + })?; + dynamic_struct.insert_boxed(&key, value); + } + + Ok(dynamic_struct) + } +} + +struct TupleStructDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> DeserializeSeed<'de> for TupleStructDeserializer<'a> { + type Value = DynamicTupleStruct; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_seq(TupleStructVisitor { + registry: self.registry, + }) + } +} + +struct TupleStructVisitor<'a> { + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> { + type Value = DynamicTupleStruct; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("tuple struct value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let mut tuple_struct = DynamicTupleStruct::default(); + while let Some(value) = seq.next_element_seed(ReflectDeserializer { + registry: self.registry, + })? { + tuple_struct.insert_boxed(value); + } + Ok(tuple_struct) + } +} diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs new file mode 100644 index 0000000000000..9f89cda3954c7 --- /dev/null +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -0,0 +1,14 @@ +mod de; +mod ser; + +pub use de::*; +pub use ser::*; + +pub(crate) mod type_fields { + pub const TYPE: &str = "type"; + pub const MAP: &str = "map"; + pub const STRUCT: &str = "struct"; + pub const TUPLE_STRUCT: &str = "tuple_struct"; + pub const LIST: &str = "list"; + pub const VALUE: &str = "value"; +} diff --git a/crates/bevy_reflect/src/serde/ser.rs b/crates/bevy_reflect/src/serde/ser.rs new file mode 100644 index 0000000000000..778b0fb658bc0 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser.rs @@ -0,0 +1,268 @@ +use crate::{ + serde::type_fields, List, Map, Reflect, ReflectRef, Struct, TupleStruct, TypeRegistry, +}; +use serde::{ + ser::{SerializeMap, SerializeSeq}, + Serialize, +}; + +pub enum Serializable<'a> { + Owned(Box), + Borrowed(&'a dyn erased_serde::Serialize), +} + +impl<'a> Serializable<'a> { + #[allow(clippy::should_implement_trait)] + pub fn borrow(&self) -> &dyn erased_serde::Serialize { + match self { + Serializable::Borrowed(serialize) => serialize, + Serializable::Owned(serialize) => serialize, + } + } +} + +fn get_serializable(reflect_value: &dyn Reflect) -> Result { + reflect_value.serializable().ok_or_else(|| { + serde::ser::Error::custom(&format!( + "Type '{}' does not support ReflectValue serialization", + reflect_value.type_name() + )) + }) +} + +pub struct ReflectSerializer<'a> { + pub value: &'a dyn Reflect, + pub registry: &'a TypeRegistry, +} + +impl<'a> ReflectSerializer<'a> { + pub fn new(value: &'a dyn Reflect, registry: &'a TypeRegistry) -> Self { + ReflectSerializer { value, registry } + } +} + +impl<'a> Serialize for ReflectSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self.value.reflect_ref() { + ReflectRef::Struct(value) => StructSerializer { + struct_value: value, + registry: self.registry, + } + .serialize(serializer), + ReflectRef::TupleStruct(value) => TupleStructSerializer { + tuple_struct: value, + registry: self.registry, + } + .serialize(serializer), + ReflectRef::List(value) => ListSerializer { + list: value, + registry: self.registry, + } + .serialize(serializer), + ReflectRef::Map(value) => MapSerializer { + map: value, + registry: self.registry, + } + .serialize(serializer), + ReflectRef::Value(value) => ReflectValueSerializer { + registry: self.registry, + value, + } + .serialize(serializer), + } + } +} + +pub struct ReflectValueSerializer<'a> { + pub registry: &'a TypeRegistry, + pub value: &'a dyn Reflect, +} + +impl<'a> Serialize for ReflectValueSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(2))?; + state.serialize_entry(type_fields::TYPE, self.value.type_name())?; + state.serialize_entry( + type_fields::VALUE, + get_serializable::(self.value)?.borrow(), + )?; + state.end() + } +} + +pub struct StructSerializer<'a> { + pub struct_value: &'a dyn Struct, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for StructSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(2))?; + + state.serialize_entry(type_fields::TYPE, self.struct_value.type_name())?; + state.serialize_entry( + type_fields::STRUCT, + &StructValueSerializer { + struct_value: self.struct_value, + registry: self.registry, + }, + )?; + state.end() + } +} + +pub struct StructValueSerializer<'a> { + pub struct_value: &'a dyn Struct, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for StructValueSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(self.struct_value.field_len()))?; + for (index, value) in self.struct_value.iter_fields().enumerate() { + let key = self.struct_value.name_at(index).unwrap(); + state.serialize_entry(key, &ReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} + +pub struct TupleStructSerializer<'a> { + pub tuple_struct: &'a dyn TupleStruct, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for TupleStructSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(2))?; + + state.serialize_entry(type_fields::TYPE, self.tuple_struct.type_name())?; + state.serialize_entry( + type_fields::TUPLE_STRUCT, + &TupleStructValueSerializer { + tuple_struct: self.tuple_struct, + registry: self.registry, + }, + )?; + state.end() + } +} + +pub struct TupleStructValueSerializer<'a> { + pub tuple_struct: &'a dyn TupleStruct, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for TupleStructValueSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_seq(Some(self.tuple_struct.field_len()))?; + for value in self.tuple_struct.iter_fields() { + state.serialize_element(&ReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} + +pub struct MapSerializer<'a> { + pub map: &'a dyn Map, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for MapSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(2))?; + + state.serialize_entry(type_fields::TYPE, self.map.type_name())?; + state.serialize_entry( + type_fields::MAP, + &MapValueSerializer { + map: self.map, + registry: self.registry, + }, + )?; + state.end() + } +} + +pub struct MapValueSerializer<'a> { + pub map: &'a dyn Map, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for MapValueSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(self.map.len()))?; + for (key, value) in self.map.iter() { + state.serialize_entry( + &ReflectSerializer::new(key, self.registry), + &ReflectSerializer::new(value, self.registry), + )?; + } + state.end() + } +} + +pub struct ListSerializer<'a> { + pub list: &'a dyn List, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for ListSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(2))?; + state.serialize_entry(type_fields::TYPE, self.list.type_name())?; + state.serialize_entry( + type_fields::LIST, + &ListValueSerializer { + list: self.list, + registry: self.registry, + }, + )?; + state.end() + } +} + +pub struct ListValueSerializer<'a> { + pub list: &'a dyn List, + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for ListValueSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_seq(Some(self.list.len()))?; + for value in self.list.iter() { + state.serialize_element(&ReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs new file mode 100644 index 0000000000000..764d36a336cb8 --- /dev/null +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -0,0 +1,256 @@ +use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; +use bevy_utils::HashMap; +use std::{any::Any, borrow::Cow, collections::hash_map::Entry}; + +/// An ordered &str->ReflectValue mapping where &str is a "field". +/// This corresponds to rust struct types. +pub trait Struct: Reflect { + fn field(&self, name: &str) -> Option<&dyn Reflect>; + fn field_mut(&mut self, name: &str) -> Option<&mut dyn Reflect>; + fn field_at(&self, index: usize) -> Option<&dyn Reflect>; + fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn Reflect>; + fn name_at(&self, index: usize) -> Option<&str>; + fn field_len(&self) -> usize; + fn iter_fields(&self) -> FieldIter; + fn clone_dynamic(&self) -> DynamicStruct; +} + +pub struct FieldIter<'a> { + pub(crate) struct_val: &'a dyn Struct, + pub(crate) index: usize, +} + +impl<'a> FieldIter<'a> { + pub fn new(value: &'a dyn Struct) -> Self { + FieldIter { + struct_val: value, + index: 0, + } + } +} + +impl<'a> Iterator for FieldIter<'a> { + type Item = &'a dyn Reflect; + + fn next(&mut self) -> Option { + let value = self.struct_val.field_at(self.index); + self.index += 1; + value + } +} + +pub trait GetField { + fn get_field(&self, name: &str) -> Option<&T>; + fn get_field_mut(&mut self, name: &str) -> Option<&mut T>; +} + +impl GetField for S { + fn get_field(&self, name: &str) -> Option<&T> { + self.field(name).and_then(|value| value.downcast_ref::()) + } + + fn get_field_mut(&mut self, name: &str) -> Option<&mut T> { + self.field_mut(name) + .and_then(|value| value.downcast_mut::()) + } +} + +impl GetField for dyn Struct { + fn get_field(&self, name: &str) -> Option<&T> { + self.field(name).and_then(|value| value.downcast_ref::()) + } + + fn get_field_mut(&mut self, name: &str) -> Option<&mut T> { + self.field_mut(name) + .and_then(|value| value.downcast_mut::()) + } +} + +#[derive(Default)] +pub struct DynamicStruct { + name: String, + fields: Vec>, + field_names: Vec>, + field_indices: HashMap, usize>, +} + +impl DynamicStruct { + pub fn name(&self) -> &str { + &self.name + } + + pub fn set_name(&mut self, name: String) { + self.name = name; + } + + pub fn insert_boxed(&mut self, name: &str, value: Box) { + let name = Cow::Owned(name.to_string()); + match self.field_indices.entry(name) { + Entry::Occupied(entry) => { + self.fields[*entry.get()] = value; + } + Entry::Vacant(entry) => { + self.fields.push(value); + self.field_names.push(entry.key().clone()); + entry.insert(self.fields.len() - 1); + } + } + } + + pub fn insert(&mut self, name: &str, value: T) { + if let Some(index) = self.field_indices.get(name) { + self.fields[*index] = Box::new(value); + } else { + self.insert_boxed(name, Box::new(value)); + } + } +} + +impl Struct for DynamicStruct { + #[inline] + fn field(&self, name: &str) -> Option<&dyn Reflect> { + if let Some(index) = self.field_indices.get(name) { + Some(&*self.fields[*index]) + } else { + None + } + } + + #[inline] + fn field_mut(&mut self, name: &str) -> Option<&mut dyn Reflect> { + if let Some(index) = self.field_indices.get(name) { + Some(&mut *self.fields[*index]) + } else { + None + } + } + + #[inline] + fn field_at(&self, index: usize) -> Option<&dyn Reflect> { + self.fields.get(index).map(|value| &**value) + } + + #[inline] + fn field_at_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> { + self.fields.get_mut(index).map(|value| &mut **value) + } + + #[inline] + fn name_at(&self, index: usize) -> Option<&str> { + self.field_names.get(index).map(|name| name.as_ref()) + } + + #[inline] + fn field_len(&self) -> usize { + self.fields.len() + } + + #[inline] + fn iter_fields(&self) -> FieldIter { + FieldIter { + struct_val: self, + index: 0, + } + } + + fn clone_dynamic(&self) -> DynamicStruct { + DynamicStruct { + name: self.name.clone(), + field_names: self.field_names.clone(), + field_indices: self.field_indices.clone(), + fields: self + .fields + .iter() + .map(|value| value.clone_value()) + .collect(), + } + } +} + +impl Reflect for DynamicStruct { + #[inline] + fn type_name(&self) -> &str { + &self.name + } + + #[inline] + fn any(&self) -> &dyn Any { + self + } + + #[inline] + fn any_mut(&mut self) -> &mut dyn Any { + self + } + + #[inline] + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + #[inline] + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::Struct(self) + } + + #[inline] + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::Struct(self) + } + + fn apply(&mut self, value: &dyn Reflect) { + if let ReflectRef::Struct(struct_value) = value.reflect_ref() { + for (i, value) in struct_value.iter_fields().enumerate() { + let name = struct_value.name_at(i).unwrap(); + if let Some(v) = self.field_mut(name) { + v.apply(value) + } + } + } else { + panic!("Attempted to apply non-struct type to struct type."); + } + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + struct_partial_eq(self, value) + } + + fn serializable(&self) -> Option { + None + } +} + +#[inline] +pub fn struct_partial_eq(a: &S, b: &dyn Reflect) -> Option { + let struct_value = if let ReflectRef::Struct(struct_value) = b.reflect_ref() { + struct_value + } else { + return Some(false); + }; + + if a.field_len() != struct_value.field_len() { + return Some(false); + } + + for (i, value) in struct_value.iter_fields().enumerate() { + let name = struct_value.name_at(i).unwrap(); + if let Some(field_value) = a.field(name) { + if let Some(false) | None = field_value.reflect_partial_eq(value) { + return Some(false); + } + } else { + return Some(false); + } + } + + Some(true) +} diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs new file mode 100644 index 0000000000000..502d836f1c4e8 --- /dev/null +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -0,0 +1,210 @@ +use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; +use std::any::Any; + +/// A rust "tuple struct" reflection +pub trait TupleStruct: Reflect { + fn field(&self, index: usize) -> Option<&dyn Reflect>; + fn field_mut(&mut self, index: usize) -> Option<&mut dyn Reflect>; + fn field_len(&self) -> usize; + fn iter_fields(&self) -> TupleStructFieldIter; + fn clone_dynamic(&self) -> DynamicTupleStruct; +} + +pub struct TupleStructFieldIter<'a> { + pub(crate) tuple_struct: &'a dyn TupleStruct, + pub(crate) index: usize, +} + +impl<'a> TupleStructFieldIter<'a> { + pub fn new(value: &'a dyn TupleStruct) -> Self { + TupleStructFieldIter { + tuple_struct: value, + index: 0, + } + } +} + +impl<'a> Iterator for TupleStructFieldIter<'a> { + type Item = &'a dyn Reflect; + + fn next(&mut self) -> Option { + let value = self.tuple_struct.field(self.index); + self.index += 1; + value + } +} + +pub trait GetTupleStructField { + fn get_field(&self, index: usize) -> Option<&T>; + fn get_field_mut(&mut self, index: usize) -> Option<&mut T>; +} + +impl GetTupleStructField for S { + fn get_field(&self, index: usize) -> Option<&T> { + self.field(index) + .and_then(|value| value.downcast_ref::()) + } + + fn get_field_mut(&mut self, index: usize) -> Option<&mut T> { + self.field_mut(index) + .and_then(|value| value.downcast_mut::()) + } +} + +impl GetTupleStructField for dyn TupleStruct { + fn get_field(&self, index: usize) -> Option<&T> { + self.field(index) + .and_then(|value| value.downcast_ref::()) + } + + fn get_field_mut(&mut self, index: usize) -> Option<&mut T> { + self.field_mut(index) + .and_then(|value| value.downcast_mut::()) + } +} + +#[derive(Default)] +pub struct DynamicTupleStruct { + name: String, + fields: Vec>, +} + +impl DynamicTupleStruct { + pub fn name(&self) -> &str { + &self.name + } + + pub fn set_name(&mut self, name: String) { + self.name = name; + } + + pub fn insert_boxed(&mut self, value: Box) { + self.fields.push(value); + } + + pub fn insert(&mut self, value: T) { + self.insert_boxed(Box::new(value)); + } +} + +impl TupleStruct for DynamicTupleStruct { + #[inline] + fn field(&self, index: usize) -> Option<&dyn Reflect> { + self.fields.get(index).map(|field| &**field) + } + + #[inline] + fn field_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> { + self.fields.get_mut(index).map(|field| &mut **field) + } + + #[inline] + fn field_len(&self) -> usize { + self.fields.len() + } + + #[inline] + fn iter_fields(&self) -> TupleStructFieldIter { + TupleStructFieldIter { + tuple_struct: self, + index: 0, + } + } + + fn clone_dynamic(&self) -> DynamicTupleStruct { + DynamicTupleStruct { + name: self.name.clone(), + fields: self + .fields + .iter() + .map(|value| value.clone_value()) + .collect(), + } + } +} + +impl Reflect for DynamicTupleStruct { + #[inline] + fn type_name(&self) -> &str { + std::any::type_name::() + } + + #[inline] + fn any(&self) -> &dyn Any { + self + } + + #[inline] + fn any_mut(&mut self) -> &mut dyn Any { + self + } + + #[inline] + fn clone_value(&self) -> Box { + Box::new(self.clone_dynamic()) + } + + #[inline] + fn reflect_ref(&self) -> ReflectRef { + ReflectRef::TupleStruct(self) + } + + #[inline] + fn reflect_mut(&mut self) -> ReflectMut { + ReflectMut::TupleStruct(self) + } + + fn apply(&mut self, value: &dyn Reflect) { + if let ReflectRef::TupleStruct(tuple_struct) = value.reflect_ref() { + for (i, value) in tuple_struct.iter_fields().enumerate() { + if let Some(v) = self.field_mut(i) { + v.apply(value) + } + } + } else { + panic!("Attempted to apply non-TupleStruct type to TupleStruct type."); + } + } + + fn set(&mut self, value: Box) -> Result<(), Box> { + *self = value.take()?; + Ok(()) + } + + fn reflect_hash(&self) -> Option { + None + } + + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { + tuple_struct_partial_eq(self, value) + } + + fn serializable(&self) -> Option { + None + } +} + +#[inline] +pub fn tuple_struct_partial_eq(a: &S, b: &dyn Reflect) -> Option { + let tuple_struct = if let ReflectRef::TupleStruct(tuple_struct) = b.reflect_ref() { + tuple_struct + } else { + return Some(false); + }; + + if a.field_len() != tuple_struct.field_len() { + return Some(false); + } + + for (i, value) in tuple_struct.iter_fields().enumerate() { + if let Some(field_value) = a.field(i) { + if let Some(false) | None = field_value.reflect_partial_eq(value) { + return Some(false); + } + } else { + return Some(false); + } + } + + Some(true) +} diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs new file mode 100644 index 0000000000000..6fa39145b3540 --- /dev/null +++ b/crates/bevy_reflect/src/type_registry.rs @@ -0,0 +1,324 @@ +use crate::Reflect; +use bevy_utils::{HashMap, HashSet}; +use downcast_rs::{impl_downcast, Downcast}; +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use serde::Deserialize; +use std::{any::TypeId, fmt::Debug, sync::Arc}; + +#[derive(Default)] +pub struct TypeRegistry { + registrations: HashMap, + short_name_to_id: HashMap, + full_name_to_id: HashMap, + ambiguous_names: HashSet, +} + +// TODO: remove this wrapper once we migrate to Atelier Assets and the Scene AssetLoader doesn't need a TypeRegistry ref +#[derive(Clone, Default)] +pub struct TypeRegistryArc { + pub internal: Arc>, +} + +impl Debug for TypeRegistryArc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.internal.read().full_name_to_id.keys().fmt(f) + } +} + +pub trait GetTypeRegistration { + fn get_type_registration() -> TypeRegistration; +} + +impl TypeRegistry { + pub fn register(&mut self) + where + T: GetTypeRegistration, + { + self.add_registration(T::get_type_registration()); + } + + pub fn add_registration(&mut self, registration: TypeRegistration) { + let short_name = registration.short_name.to_string(); + if self.short_name_to_id.contains_key(&short_name) + || self.ambiguous_names.contains(&short_name) + { + // name is ambiguous. fall back to long names for all ambiguous types + self.short_name_to_id.remove(&short_name); + self.ambiguous_names.insert(short_name); + } else { + self.short_name_to_id + .insert(short_name, registration.type_id); + } + self.full_name_to_id + .insert(registration.name.to_string(), registration.type_id); + self.registrations + .insert(registration.type_id, registration); + } + + pub fn get(&self, type_id: TypeId) -> Option<&TypeRegistration> { + self.registrations.get(&type_id) + } + + pub fn get_mut(&mut self, type_id: TypeId) -> Option<&mut TypeRegistration> { + self.registrations.get_mut(&type_id) + } + + pub fn get_with_name(&self, type_name: &str) -> Option<&TypeRegistration> { + self.full_name_to_id + .get(type_name) + .and_then(|id| self.get(*id)) + } + + pub fn get_with_name_mut(&mut self, type_name: &str) -> Option<&mut TypeRegistration> { + self.full_name_to_id + .get(type_name) + .cloned() + .and_then(move |id| self.get_mut(id)) + } + + pub fn get_with_short_name(&self, short_type_name: &str) -> Option<&TypeRegistration> { + self.short_name_to_id + .get(short_type_name) + .and_then(|id| self.registrations.get(id)) + } + + pub fn get_type_data(&self, type_id: TypeId) -> Option<&T> { + self.get(type_id) + .and_then(|registration| registration.data::()) + } + + pub fn iter(&self) -> impl Iterator { + self.registrations.values() + } +} + +impl TypeRegistryArc { + pub fn read(&self) -> RwLockReadGuard<'_, TypeRegistry> { + self.internal.read() + } + + pub fn write(&self) -> RwLockWriteGuard<'_, TypeRegistry> { + self.internal.write() + } +} + +pub struct TypeRegistration { + type_id: TypeId, + short_name: String, + name: &'static str, + data: HashMap>, +} + +impl TypeRegistration { + #[inline] + pub fn type_id(&self) -> TypeId { + self.type_id + } + + pub fn data(&self) -> Option<&T> { + self.data + .get(&TypeId::of::()) + .and_then(|value| value.downcast_ref()) + } + + pub fn data_mut(&mut self) -> Option<&mut T> { + self.data + .get_mut(&TypeId::of::()) + .and_then(|value| value.downcast_mut()) + } + + pub fn insert(&mut self, data: T) { + self.data.insert(TypeId::of::(), Box::new(data)); + } + + pub fn of() -> Self { + let ty = TypeId::of::(); + let type_name = std::any::type_name::(); + Self { + type_id: ty, + data: HashMap::default(), + name: type_name, + short_name: Self::get_short_name(type_name), + } + } + + pub fn short_name(&self) -> &str { + &self.short_name + } + + pub fn name(&self) -> &'static str { + self.name + } + + fn get_short_name(full_name: &str) -> String { + let mut short_name = String::new(); + + { + // A typename may be a composition of several other type names (e.g. generic parameters) + // separated by the characters that we try to find below. + // Then, each individual typename is shortened to its last path component. + // + // Note: Instead of `find`, `split_inclusive` would be nice but it's still unstable... + let mut remainder = full_name; + while let Some(index) = remainder.find(&['<', '>', '(', ')', '[', ']', ',', ';'][..]) { + let (path, new_remainder) = remainder.split_at(index); + // Push the shortened path in front of the found character + short_name.push_str(path.rsplit(':').next().unwrap()); + // Push the character that was found + let character = new_remainder.chars().next().unwrap(); + short_name.push(character); + // Advance the remainder + if character == ',' || character == ';' { + // A comma or semicolon is always followed by a space + short_name.push(' '); + remainder = &new_remainder[2..]; + } else { + remainder = &new_remainder[1..]; + } + } + + // The remainder will only be non-empty if there were no matches at all + if !remainder.is_empty() { + // Then, the full typename is a path that has to be shortened + short_name.push_str(remainder.rsplit(':').next().unwrap()); + } + } + + short_name + } +} + +impl Clone for TypeRegistration { + fn clone(&self) -> Self { + let mut data = HashMap::default(); + for (id, type_data) in self.data.iter() { + data.insert(*id, (*type_data).clone_type_data()); + } + + TypeRegistration { + data, + name: self.name, + short_name: self.short_name.clone(), + type_id: self.type_id, + } + } +} + +pub trait TypeData: Downcast + Send + Sync { + fn clone_type_data(&self) -> Box; +} +impl_downcast!(TypeData); + +impl TypeData for T +where + T: Clone, +{ + fn clone_type_data(&self) -> Box { + Box::new(self.clone()) + } +} + +pub trait FromType { + fn from_type() -> Self; +} + +#[derive(Clone)] +pub struct ReflectDeserialize { + pub func: fn( + deserializer: &mut dyn erased_serde::Deserializer, + ) -> Result, erased_serde::Error>, +} + +impl ReflectDeserialize { + pub fn deserialize<'de, D>(&self, deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let mut erased = erased_serde::Deserializer::erase(deserializer); + (self.func)(&mut erased) + .map_err(<>::Error as serde::de::Error>::custom) + } +} + +impl Deserialize<'a> + Reflect> FromType for ReflectDeserialize { + fn from_type() -> Self { + ReflectDeserialize { + func: |deserializer| Ok(Box::new(T::deserialize(deserializer)?)), + } + } +} + +#[cfg(test)] +mod test { + use crate::TypeRegistration; + + #[test] + fn test_get_short_name() { + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::()), + "f64" + ); + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::()), + "String" + ); + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::<(u32, f64)>()), + "(u32, f64)" + ); + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::<(String, String)>()), + "(String, String)" + ); + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::<[f64]>()), + "[f64]" + ); + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::<[String]>()), + "[String]" + ); + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::<[f64; 16]>()), + "[f64; 16]" + ); + assert_eq!( + TypeRegistration::get_short_name(std::any::type_name::<[String; 16]>()), + "[String; 16]" + ); + } + + // TODO: re-enable + // #[test] + // fn test_property_type_registration() { + // assert_eq!( + // TypeRegistration::of::>().short_name, + // "Option" + // ); + // assert_eq!( + // TypeRegistration::of::>().short_name, + // "HashMap" + // ); + // assert_eq!( + // TypeRegistration::of::>>().short_name, + // "Option>" + // ); + // assert_eq!( + // TypeRegistration::of::>>>().short_name, + // "Option>>" + // ); + // assert_eq!( + // TypeRegistration::of::>>>().short_name, + // "Option>>" + // ); + // assert_eq!( + // TypeRegistration::of::, Option>>>().short_name, + // "Option, Option>>" + // ); + // assert_eq!( + // TypeRegistration::of::, (String, Option)>>>() + // .short_name, + // "Option, (String, Option)>>" + // ); + // } +} diff --git a/crates/bevy_type_registry/src/type_uuid.rs b/crates/bevy_reflect/src/type_uuid.rs similarity index 78% rename from crates/bevy_type_registry/src/type_uuid.rs rename to crates/bevy_reflect/src/type_uuid.rs index 28d53c3a34e28..e786b51467239 100644 --- a/crates/bevy_type_registry/src/type_uuid.rs +++ b/crates/bevy_reflect/src/type_uuid.rs @@ -1,5 +1,5 @@ -pub use bevy_derive::TypeUuid; -use uuid::Uuid; +pub use bevy_reflect_derive::TypeUuid; +pub use bevy_utils::Uuid; pub trait TypeUuid { const TYPE_UUID: Uuid; diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index ceb74c1cd4e13..6321bee69f814 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_render" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,23 +14,21 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_core = { path = "../bevy_core", version = "0.3.0" } -bevy_derive = { path = "../bevy_derive", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_property = { path = "../bevy_property", version = "0.3.0" } -bevy_transform = { path = "../bevy_transform", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_window = { path = "../bevy_window", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_core = { path = "../bevy_core", version = "0.4.0" } +bevy_derive = { path = "../bevy_derive", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_window = { path = "../bevy_window", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # rendering image = { version = "0.23.12", default-features = false } # misc -uuid = { version = "0.8", features = ["v4", "serde"] } serde = { version = "1", features = ["derive"] } bitflags = "1.2.1" smallvec = "1.4.2" @@ -40,18 +38,21 @@ downcast-rs = "1.2.0" thiserror = "1.0" anyhow = "1.0" hex = "0.4.2" -hexasphere = "2.0.0" +hexasphere = "3.1" parking_lot = "0.11.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] spirv-reflect = "0.2.3" -[target.'cfg(all(not(target_os = "ios"), not(target_arch = "wasm32")))'.dependencies] +[target.'cfg(all(not(target_os = "ios"), not(target_arch = "wasm32"), not(all(target_arch = "aarch64", target_os = "macos"))))'.dependencies] bevy-glsl-to-spirv = "0.2.0" -[target.'cfg(target_os = "ios")'.dependencies] -shaderc = "0.6.3" +[target.'cfg(any(target_os = "ios", all(target_arch = "aarch64", target_os = "macos")))'.dependencies] +shaderc = "0.7.0" [features] png = ["image/png"] hdr = ["image/hdr"] +dds = ["image/dds"] +tga = ["image/tga"] +jpeg = ["image/jpeg"] diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index becec6b88cc3f..22fd64a960b74 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -2,16 +2,17 @@ use super::CameraProjection; use bevy_app::prelude::{EventReader, Events}; use bevy_ecs::{Added, Component, Entity, Local, Query, QuerySet, Res}; use bevy_math::Mat4; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use bevy_window::{WindowCreated, WindowId, WindowResized, Windows}; -#[derive(Default, Debug, Properties)] +#[derive(Default, Debug, Reflect)] +#[reflect(Component)] pub struct Camera { pub projection_matrix: Mat4, pub name: Option, - #[property(ignore)] + #[reflect(ignore)] pub window: WindowId, - #[property(ignore)] + #[reflect(ignore)] pub depth_calculation: DepthCalculation, } @@ -77,7 +78,7 @@ pub fn camera_system( for (entity, mut camera, mut camera_projection) in queries.q0_mut().iter_mut() { if let Some(window) = windows.get(camera.window) { if changed_window_ids.contains(&window.id()) || added_cameras.contains(&entity) { - camera_projection.update(window.width() as usize, window.height() as usize); + camera_projection.update(window.width(), window.height()); camera.projection_matrix = camera_projection.get_projection_matrix(); camera.depth_calculation = camera_projection.depth_calculation(); } diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index f560045217675..7e9e8a4ab6cfa 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -1,15 +1,16 @@ use super::DepthCalculation; use bevy_math::Mat4; -use bevy_property::{Properties, Property}; +use bevy_reflect::{Reflect, ReflectComponent, ReflectDeserialize}; use serde::{Deserialize, Serialize}; pub trait CameraProjection { fn get_projection_matrix(&self) -> Mat4; - fn update(&mut self, width: usize, height: usize); + fn update(&mut self, width: f32, height: f32); fn depth_calculation(&self) -> DepthCalculation; } -#[derive(Debug, Clone, Properties)] +#[derive(Debug, Clone, Reflect)] +#[reflect(Component)] pub struct PerspectiveProjection { pub fov: f32, pub aspect_ratio: f32, @@ -22,8 +23,8 @@ impl CameraProjection for PerspectiveProjection { Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far) } - fn update(&mut self, width: usize, height: usize) { - self.aspect_ratio = width as f32 / height as f32; + fn update(&mut self, width: f32, height: f32) { + self.aspect_ratio = width / height; } fn depth_calculation(&self) -> DepthCalculation { @@ -43,13 +44,15 @@ impl Default for PerspectiveProjection { } // TODO: make this a component instead of a property -#[derive(Debug, Clone, Property, Serialize, Deserialize)] +#[derive(Debug, Clone, Reflect, Serialize, Deserialize)] +#[reflect_value(Serialize, Deserialize)] pub enum WindowOrigin { Center, BottomLeft, } -#[derive(Debug, Clone, Properties)] +#[derive(Debug, Clone, Reflect)] +#[reflect(Component)] pub struct OrthographicProjection { pub left: f32, pub right: f32, @@ -72,11 +75,11 @@ impl CameraProjection for OrthographicProjection { ) } - fn update(&mut self, width: usize, height: usize) { + fn update(&mut self, width: f32, height: f32) { match self.window_origin { WindowOrigin::Center => { - let half_width = width as f32 / 2.0; - let half_height = height as f32 / 2.0; + let half_width = width / 2.0; + let half_height = height / 2.0; self.left = -half_width; self.right = half_width; self.top = half_height; @@ -84,8 +87,8 @@ impl CameraProjection for OrthographicProjection { } WindowOrigin::BottomLeft => { self.left = 0.0; - self.right = width as f32; - self.top = height as f32; + self.right = width; + self.top = height; self.bottom = 0.0; } } diff --git a/crates/bevy_render/src/camera/visible_entities.rs b/crates/bevy_render/src/camera/visible_entities.rs index 7a79e469bb1d8..e1d54bc1c40ad 100644 --- a/crates/bevy_render/src/camera/visible_entities.rs +++ b/crates/bevy_render/src/camera/visible_entities.rs @@ -1,8 +1,8 @@ use super::{Camera, DepthCalculation}; -use crate::Draw; +use crate::prelude::Visible; use bevy_core::FloatOrd; use bevy_ecs::{Entity, Query, With}; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use bevy_transform::prelude::GlobalTransform; #[derive(Debug)] @@ -11,9 +11,10 @@ pub struct VisibleEntity { pub order: FloatOrd, } -#[derive(Default, Debug, Properties)] +#[derive(Default, Debug, Reflect)] +#[reflect(Component)] pub struct VisibleEntities { - #[property(ignore)] + #[reflect(ignore)] pub value: Vec, } @@ -25,8 +26,8 @@ impl VisibleEntities { pub fn visible_entities_system( mut camera_query: Query<(&Camera, &GlobalTransform, &mut VisibleEntities)>, - draw_query: Query<(Entity, &Draw)>, - draw_transform_query: Query<&GlobalTransform, With>, + visible_query: Query<(Entity, &Visible)>, + visible_transform_query: Query<&GlobalTransform, With>, ) { for (camera, camera_global_transform, mut visible_entities) in camera_query.iter_mut() { visible_entities.value.clear(); @@ -34,12 +35,12 @@ pub fn visible_entities_system( let mut no_transform_order = 0.0; let mut transparent_entities = Vec::new(); - for (entity, draw) in draw_query.iter() { - if !draw.is_visible { + for (entity, visible) in visible_query.iter() { + if !visible.is_visible { continue; } - let order = if let Ok(global_transform) = draw_transform_query.get(entity) { + let order = if let Ok(global_transform) = visible_transform_query.get(entity) { let position = global_transform.translation; // smaller distances are sorted to lower indices by using the distance from the camera FloatOrd(match camera.depth_calculation { @@ -52,7 +53,7 @@ pub fn visible_entities_system( order }; - if draw.is_transparent { + if visible.is_transparent { transparent_entities.push(VisibleEntity { entity, order }) } else { visible_entities.value.push(VisibleEntity { entity, order }) diff --git a/crates/bevy_render/src/color.rs b/crates/bevy_render/src/color.rs index 640e44efe76e1..cfa52adc560f6 100644 --- a/crates/bevy_render/src/color.rs +++ b/crates/bevy_render/src/color.rs @@ -7,7 +7,7 @@ use crate::{ use bevy_asset::Handle; use bevy_core::{Byteable, Bytes}; use bevy_math::{Vec3, Vec4}; -use bevy_property::Property; +use bevy_reflect::Reflect; use serde::{Deserialize, Serialize}; use std::ops::{Add, AddAssign, Mul, MulAssign}; @@ -15,7 +15,7 @@ use std::ops::{Add, AddAssign, Mul, MulAssign}; // see comment on bevy issue #688 https://github.com/bevyengine/bevy/pull/688#issuecomment-711414011 /// RGBA color in the Linear sRGB colorspace (often colloquially referred to as "linear", "RGB", or "linear RGB"). #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Property)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] pub struct Color { red: f32, green: f32, diff --git a/crates/bevy_render/src/colorspace.rs b/crates/bevy_render/src/colorspace.rs index 3f789b25d4b9c..28a1a5249f95a 100644 --- a/crates/bevy_render/src/colorspace.rs +++ b/crates/bevy_render/src/colorspace.rs @@ -1,5 +1,3 @@ -// sRGB -//================================================================================================== pub trait SrgbColorSpace { fn linear_to_nonlinear_srgb(self) -> Self; fn nonlinear_to_linear_srgb(self) -> Self; @@ -47,4 +45,3 @@ fn test_srgb_full_roundtrip() { ); } } -//================================================================================================== diff --git a/crates/bevy_render/src/draw.rs b/crates/bevy_render/src/draw.rs index 67bb4c9e0a952..0da2b9e67cab0 100644 --- a/crates/bevy_render/src/draw.rs +++ b/crates/bevy_render/src/draw.rs @@ -1,14 +1,14 @@ use crate::{ pipeline::{PipelineCompiler, PipelineDescriptor, PipelineLayout, PipelineSpecialization}, renderer::{ - BindGroup, BindGroupId, BufferId, BufferUsage, RenderResource, RenderResourceBinding, - RenderResourceBindings, RenderResourceContext, SharedBuffers, + AssetRenderResourceBindings, BindGroup, BindGroupId, BufferId, RenderResource, + RenderResourceBinding, RenderResourceBindings, RenderResourceContext, SharedBuffers, }, shader::Shader, }; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Asset, Assets, Handle}; use bevy_ecs::{Query, Res, ResMut, SystemParam}; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use std::{ops::Range, sync::Arc}; use thiserror::Error; @@ -43,20 +43,34 @@ pub enum RenderCommand { }, } -/// A component that indicates how to draw an entity. -#[derive(Debug, Properties, Clone)] -pub struct Draw { +#[derive(Debug, Clone, Reflect)] +#[reflect(Component)] +pub struct Visible { pub is_visible: bool, + // TODO: consider moving this to materials pub is_transparent: bool, - #[property(ignore)] +} + +impl Default for Visible { + fn default() -> Self { + Visible { + is_visible: true, + is_transparent: false, + } + } +} + +/// A component that indicates how to draw an entity. +#[derive(Debug, Clone, Reflect)] +#[reflect(Component)] +pub struct Draw { + #[reflect(ignore)] pub render_commands: Vec, } impl Default for Draw { fn default() -> Self { Self { - is_visible: true, - is_transparent: false, render_commands: Default::default(), } } @@ -109,45 +123,37 @@ impl Draw { #[derive(Debug, Error)] pub enum DrawError { - #[error("Pipeline does not exist.")] + #[error("pipeline does not exist")] NonExistentPipeline, - #[error("No pipeline set")] + #[error("no pipeline set")] NoPipelineSet, - #[error("Pipeline has no layout")] + #[error("pipeline has no layout")] PipelineHasNoLayout, - #[error("Failed to get a buffer for the given RenderResource.")] + #[error("failed to get a buffer for the given `RenderResource`")] BufferAllocationFailure, + #[error("the given asset does not have any render resources")] + MissingAssetRenderResources, } #[derive(SystemParam)] pub struct DrawContext<'a> { pub pipelines: ResMut<'a, Assets>, pub shaders: ResMut<'a, Assets>, + pub asset_render_resource_bindings: ResMut<'a, AssetRenderResourceBindings>, pub pipeline_compiler: ResMut<'a, PipelineCompiler>, pub render_resource_context: Res<'a, Box>, - pub shared_buffers: Res<'a, SharedBuffers>, + pub shared_buffers: ResMut<'a, SharedBuffers>, #[system_param(ignore)] pub current_pipeline: Option>, } -#[derive(Debug)] -pub struct FetchDrawContext; - impl<'a> DrawContext<'a> { pub fn get_uniform_buffer( - &self, - render_resource: &T, - ) -> Result { - self.get_buffer(render_resource, BufferUsage::UNIFORM) - } - - pub fn get_buffer( - &self, + &mut self, render_resource: &T, - buffer_usage: BufferUsage, ) -> Result { self.shared_buffers - .get_buffer(render_resource, buffer_usage) + .get_uniform_buffer(&**self.render_resource_context, render_resource) .ok_or(DrawError::BufferAllocationFailure) } @@ -192,32 +198,91 @@ impl<'a> DrawContext<'a> { }) } + pub fn set_asset_bind_groups( + &mut self, + draw: &mut Draw, + asset_handle: &Handle, + ) -> Result<(), DrawError> { + if let Some(asset_bindings) = self + .asset_render_resource_bindings + .get_mut_untyped(&asset_handle.clone_weak_untyped()) + { + Self::set_bind_groups_from_bindings_internal( + &self.current_pipeline, + &self.pipelines, + &**self.render_resource_context, + None, + draw, + &mut [asset_bindings], + ) + } else { + Err(DrawError::MissingAssetRenderResources) + } + } + pub fn set_bind_groups_from_bindings( - &self, + &mut self, draw: &mut Draw, render_resource_bindings: &mut [&mut RenderResourceBindings], ) -> Result<(), DrawError> { - let pipeline = self - .current_pipeline - .as_ref() - .ok_or(DrawError::NoPipelineSet)?; - let pipeline_descriptor = self - .pipelines + Self::set_bind_groups_from_bindings_internal( + &self.current_pipeline, + &self.pipelines, + &**self.render_resource_context, + Some(&mut self.asset_render_resource_bindings), + draw, + render_resource_bindings, + ) + } + + fn set_bind_groups_from_bindings_internal( + current_pipeline: &Option>, + pipelines: &Assets, + render_resource_context: &dyn RenderResourceContext, + mut asset_render_resource_bindings: Option<&mut AssetRenderResourceBindings>, + draw: &mut Draw, + render_resource_bindings: &mut [&mut RenderResourceBindings], + ) -> Result<(), DrawError> { + let pipeline = current_pipeline.as_ref().ok_or(DrawError::NoPipelineSet)?; + let pipeline_descriptor = pipelines .get(pipeline) .ok_or(DrawError::NonExistentPipeline)?; let layout = pipeline_descriptor .get_layout() .ok_or(DrawError::PipelineHasNoLayout)?; - for bindings in render_resource_bindings.iter_mut() { - bindings.update_bind_groups(pipeline_descriptor, &**self.render_resource_context); - } - for bind_group_descriptor in layout.bind_groups.iter() { + 'bind_group_descriptors: for bind_group_descriptor in layout.bind_groups.iter() { for bindings in render_resource_bindings.iter_mut() { if let Some(bind_group) = - bindings.get_descriptor_bind_group(bind_group_descriptor.id) + bindings.update_bind_group(bind_group_descriptor, render_resource_context) { draw.set_bind_group(bind_group_descriptor.index, bind_group); - break; + continue 'bind_group_descriptors; + } + } + + // if none of the given RenderResourceBindings have the current bind group, try their assets + let asset_render_resource_bindings = + if let Some(value) = asset_render_resource_bindings.as_mut() { + value + } else { + continue 'bind_group_descriptors; + }; + for bindings in render_resource_bindings.iter_mut() { + for (asset_handle, _) in bindings.iter_assets() { + let asset_bindings = if let Some(asset_bindings) = + asset_render_resource_bindings.get_mut_untyped(asset_handle) + { + asset_bindings + } else { + continue; + }; + + if let Some(bind_group) = asset_bindings + .update_bind_group(bind_group_descriptor, render_resource_context) + { + draw.set_bind_group(bind_group_descriptor.index, bind_group); + continue 'bind_group_descriptors; + } } } } diff --git a/crates/bevy_render/src/entity.rs b/crates/bevy_render/src/entity.rs index efc65ca7c97ab..c8837d39c36c0 100644 --- a/crates/bevy_render/src/entity.rs +++ b/crates/bevy_render/src/entity.rs @@ -1,6 +1,7 @@ use crate::{ camera::{Camera, OrthographicProjection, PerspectiveProjection, VisibleEntities}, pipeline::RenderPipelines, + prelude::Visible, render_graph::base, Draw, Mesh, }; @@ -15,6 +16,7 @@ use bevy_transform::components::{GlobalTransform, Transform}; pub struct MeshBundle { pub mesh: Handle, pub draw: Draw, + pub visible: Visible, pub render_pipelines: RenderPipelines, pub main_pass: MainPass, pub transform: Transform, @@ -35,7 +37,7 @@ impl Default for Camera3dBundle { fn default() -> Self { Camera3dBundle { camera: Camera { - name: Some(base::camera::CAMERA3D.to_string()), + name: Some(base::camera::CAMERA_3D.to_string()), ..Default::default() }, perspective_projection: Default::default(), @@ -63,7 +65,7 @@ impl Default for Camera2dBundle { let far = 1000.0; Camera2dBundle { camera: Camera { - name: Some(base::camera::CAMERA2D.to_string()), + name: Some(base::camera::CAMERA_2D.to_string()), ..Default::default() }, orthographic_projection: OrthographicProjection { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index fc3937ef448cc..c158d53824917 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -11,14 +11,16 @@ pub mod renderer; pub mod shader; pub mod texture; -use bevy_type_registry::RegisterType; +use bevy_ecs::{IntoSystem, SystemStage}; +use bevy_reflect::RegisterTypeBuilder; +use draw::Visible; pub use once_cell; pub mod prelude { pub use crate::{ base::Msaa, color::Color, - draw::Draw, + draw::{Draw, Visible}, entity::*, mesh::{shape, Mesh}, pass::ClearColor, @@ -29,7 +31,7 @@ pub mod prelude { } use crate::prelude::*; -use base::{MainPass, Msaa}; +use base::Msaa; use bevy_app::prelude::*; use bevy_asset::AddAsset; use camera::{ @@ -40,11 +42,11 @@ use pipeline::{ ShaderSpecialization, }; use render_graph::{ - base::{self, BaseRenderGraphBuilder, BaseRenderGraphConfig}, + base::{self, BaseRenderGraphBuilder, BaseRenderGraphConfig, MainPass}, RenderGraph, }; use renderer::{AssetRenderResourceBindings, RenderResourceBindings}; -use std::ops::Range; +use shader::ShaderLoader; #[cfg(feature = "hdr")] use texture::HdrTextureLoader; #[cfg(feature = "png")] @@ -88,62 +90,94 @@ impl Plugin for RenderPlugin { app.init_asset_loader::(); } + app.init_asset_loader::(); + if app.resources().get::().is_none() { app.resources_mut().insert(ClearColor::default()); } - app.add_stage_after(bevy_asset::stage::ASSET_EVENTS, stage::RENDER_RESOURCE) - .add_stage_after(stage::RENDER_RESOURCE, stage::RENDER_GRAPH_SYSTEMS) - .add_stage_after(stage::RENDER_GRAPH_SYSTEMS, stage::DRAW) - .add_stage_after(stage::DRAW, stage::RENDER) - .add_stage_after(stage::RENDER, stage::POST_RENDER) - .add_asset::() - .add_asset::() - .add_asset::() - .add_asset::() - .register_component::() - .register_component::() - .register_component::() - .register_component::() - .register_component::() - .register_component::() - .register_component::() - .register_property::() - .register_property::>() - .register_property::() - .register_property::() - .register_property::() - .register_properties::() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::() - .add_system_to_stage(bevy_app::stage::PRE_UPDATE, draw::clear_draw_system) - .add_system_to_stage(bevy_app::stage::POST_UPDATE, camera::active_cameras_system) - .add_system_to_stage( - bevy_app::stage::POST_UPDATE, - camera::camera_system::, - ) - .add_system_to_stage( - bevy_app::stage::POST_UPDATE, - camera::camera_system::, - ) - // registration order matters here. this must come after all camera_system:: systems - .add_system_to_stage( - bevy_app::stage::POST_UPDATE, - camera::visible_entities_system, - ) - // TODO: turn these "resource systems" into graph nodes and remove the RENDER_RESOURCE stage - .add_system_to_stage(stage::RENDER_RESOURCE, mesh::mesh_resource_provider_system) - .add_system_to_stage(stage::RENDER_RESOURCE, Texture::texture_resource_system) - .add_system_to_stage( - stage::RENDER_GRAPH_SYSTEMS, - render_graph::render_graph_schedule_executor_system, - ) - .add_system_to_stage(stage::DRAW, pipeline::draw_render_pipelines_system) - .add_system_to_stage(stage::POST_RENDER, shader::clear_shader_defs_system); + app.add_stage_after( + bevy_asset::stage::ASSET_EVENTS, + stage::RENDER_RESOURCE, + SystemStage::parallel(), + ) + .add_stage_after( + stage::RENDER_RESOURCE, + stage::RENDER_GRAPH_SYSTEMS, + SystemStage::parallel(), + ) + .add_stage_after( + stage::RENDER_GRAPH_SYSTEMS, + stage::DRAW, + SystemStage::parallel(), + ) + .add_stage_after(stage::DRAW, stage::RENDER, SystemStage::parallel()) + .add_stage_after(stage::RENDER, stage::POST_RENDER, SystemStage::parallel()) + .add_asset::() + .add_asset::() + .add_asset::() + .add_asset::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::() + .add_system_to_stage( + bevy_app::stage::PRE_UPDATE, + draw::clear_draw_system.system(), + ) + .add_system_to_stage( + bevy_app::stage::POST_UPDATE, + camera::active_cameras_system.system(), + ) + .add_system_to_stage( + bevy_app::stage::POST_UPDATE, + camera::camera_system::.system(), + ) + .add_system_to_stage( + bevy_app::stage::POST_UPDATE, + camera::camera_system::.system(), + ) + // registration order matters here. this must come after all camera_system:: systems + .add_system_to_stage( + bevy_app::stage::POST_UPDATE, + camera::visible_entities_system.system(), + ) + .add_system_to_stage( + stage::RENDER_RESOURCE, + shader::shader_update_system.system(), + ) + .add_system_to_stage( + stage::RENDER_RESOURCE, + mesh::mesh_resource_provider_system.system(), + ) + .add_system_to_stage( + stage::RENDER_RESOURCE, + Texture::texture_resource_system.system(), + ) + .add_system_to_stage( + stage::RENDER_GRAPH_SYSTEMS, + render_graph::render_graph_schedule_executor_system.system(), + ) + .add_system_to_stage(stage::DRAW, pipeline::draw_render_pipelines_system.system()) + .add_system_to_stage( + stage::POST_RENDER, + shader::clear_shader_defs_system.system(), + ); if app.resources().get::().is_none() { app.init_resource::(); @@ -156,11 +190,11 @@ impl Plugin for RenderPlugin { render_graph.add_base_graph(config, &msaa); let mut active_cameras = resources.get_mut::().unwrap(); if config.add_3d_camera { - active_cameras.add(base::camera::CAMERA3D); + active_cameras.add(base::camera::CAMERA_3D); } if config.add_2d_camera { - active_cameras.add(base::camera::CAMERA2D); + active_cameras.add(base::camera::CAMERA_2D); } } } diff --git a/crates/bevy_render/src/mesh/mesh.rs b/crates/bevy_render/src/mesh/mesh.rs index ceb001bffc053..f136dcde02f47 100644 --- a/crates/bevy_render/src/mesh/mesh.rs +++ b/crates/bevy_render/src/mesh/mesh.rs @@ -5,13 +5,13 @@ use crate::{ use bevy_app::prelude::{EventReader, Events}; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_core::AsBytes; -use bevy_ecs::{Local, Query, Res}; +use bevy_ecs::{Changed, Entity, Local, Mut, Query, QuerySet, Res, With}; use bevy_math::*; -use bevy_type_registry::TypeUuid; +use bevy_reflect::TypeUuid; use std::borrow::Cow; use crate::pipeline::{InputStepMode, VertexAttributeDescriptor, VertexBufferDescriptor}; -use bevy_utils::HashMap; +use bevy_utils::{HashMap, HashSet}; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; @@ -19,18 +19,34 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; #[derive(Clone, Debug)] pub enum VertexAttributeValues { Float(Vec), + Int(Vec), + Uint(Vec), Float2(Vec<[f32; 2]>), + Int2(Vec<[i32; 2]>), + Uint2(Vec<[u32; 2]>), Float3(Vec<[f32; 3]>), + Int3(Vec<[i32; 3]>), + Uint3(Vec<[u32; 3]>), Float4(Vec<[f32; 4]>), + Int4(Vec<[i32; 4]>), + Uint4(Vec<[u32; 4]>), } impl VertexAttributeValues { pub fn len(&self) -> usize { match *self { VertexAttributeValues::Float(ref values) => values.len(), + VertexAttributeValues::Int(ref values) => values.len(), + VertexAttributeValues::Uint(ref values) => values.len(), VertexAttributeValues::Float2(ref values) => values.len(), + VertexAttributeValues::Int2(ref values) => values.len(), + VertexAttributeValues::Uint2(ref values) => values.len(), VertexAttributeValues::Float3(ref values) => values.len(), + VertexAttributeValues::Int3(ref values) => values.len(), + VertexAttributeValues::Uint3(ref values) => values.len(), VertexAttributeValues::Float4(ref values) => values.len(), + VertexAttributeValues::Int4(ref values) => values.len(), + VertexAttributeValues::Uint4(ref values) => values.len(), } } @@ -42,9 +58,17 @@ impl VertexAttributeValues { pub fn get_bytes(&self) -> &[u8] { match self { VertexAttributeValues::Float(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Int(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Uint(values) => values.as_slice().as_bytes(), VertexAttributeValues::Float2(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Int2(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Uint2(values) => values.as_slice().as_bytes(), VertexAttributeValues::Float3(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Int3(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Uint3(values) => values.as_slice().as_bytes(), VertexAttributeValues::Float4(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Int4(values) => values.as_slice().as_bytes(), + VertexAttributeValues::Uint4(values) => values.as_slice().as_bytes(), } } } @@ -53,9 +77,17 @@ impl From<&VertexAttributeValues> for VertexFormat { fn from(values: &VertexAttributeValues) -> Self { match values { VertexAttributeValues::Float(_) => VertexFormat::Float, + VertexAttributeValues::Int(_) => VertexFormat::Int, + VertexAttributeValues::Uint(_) => VertexFormat::Uint, VertexAttributeValues::Float2(_) => VertexFormat::Float2, + VertexAttributeValues::Int2(_) => VertexFormat::Int2, + VertexAttributeValues::Uint2(_) => VertexFormat::Uint2, VertexAttributeValues::Float3(_) => VertexFormat::Float3, + VertexAttributeValues::Int3(_) => VertexFormat::Int3, + VertexAttributeValues::Uint3(_) => VertexFormat::Uint3, VertexAttributeValues::Float4(_) => VertexFormat::Float4, + VertexAttributeValues::Int4(_) => VertexFormat::Int4, + VertexAttributeValues::Uint4(_) => VertexFormat::Uint4, } } } @@ -66,24 +98,72 @@ impl From> for VertexAttributeValues { } } +impl From> for VertexAttributeValues { + fn from(vec: Vec) -> Self { + VertexAttributeValues::Int(vec) + } +} + +impl From> for VertexAttributeValues { + fn from(vec: Vec) -> Self { + VertexAttributeValues::Uint(vec) + } +} + impl From> for VertexAttributeValues { fn from(vec: Vec<[f32; 2]>) -> Self { VertexAttributeValues::Float2(vec) } } +impl From> for VertexAttributeValues { + fn from(vec: Vec<[i32; 2]>) -> Self { + VertexAttributeValues::Int2(vec) + } +} + +impl From> for VertexAttributeValues { + fn from(vec: Vec<[u32; 2]>) -> Self { + VertexAttributeValues::Uint2(vec) + } +} + impl From> for VertexAttributeValues { fn from(vec: Vec<[f32; 3]>) -> Self { VertexAttributeValues::Float3(vec) } } +impl From> for VertexAttributeValues { + fn from(vec: Vec<[i32; 3]>) -> Self { + VertexAttributeValues::Int3(vec) + } +} + +impl From> for VertexAttributeValues { + fn from(vec: Vec<[u32; 3]>) -> Self { + VertexAttributeValues::Uint3(vec) + } +} + impl From> for VertexAttributeValues { fn from(vec: Vec<[f32; 4]>) -> Self { VertexAttributeValues::Float4(vec) } } +impl From> for VertexAttributeValues { + fn from(vec: Vec<[i32; 4]>) -> Self { + VertexAttributeValues::Int4(vec) + } +} + +impl From> for VertexAttributeValues { + fn from(vec: Vec<[u32; 4]>) -> Self { + VertexAttributeValues::Uint4(vec) + } +} + #[derive(Debug)] pub enum Indices { U16(Vec), @@ -240,9 +320,15 @@ fn remove_current_mesh_resources( remove_resource_save(render_resource_context, handle, INDEX_BUFFER_ASSET_INDEX); } +#[derive(Default)] +pub struct MeshEntities { + entities: HashSet, +} + #[derive(Default)] pub struct MeshResourceProviderState { mesh_event_reader: EventReader>, + mesh_entities: HashMap, MeshEntities>, } pub fn mesh_resource_provider_system( @@ -250,9 +336,12 @@ pub fn mesh_resource_provider_system( render_resource_context: Res>, meshes: Res>, mesh_events: Res>>, - mut query: Query<(&Handle, &mut RenderPipelines)>, + mut queries: QuerySet<( + Query<&mut RenderPipelines, With>>, + Query<(Entity, &Handle, &mut RenderPipelines), Changed>>, + )>, ) { - let mut changed_meshes = bevy_utils::HashSet::>::default(); + let mut changed_meshes = HashSet::default(); let render_resource_context = &**render_resource_context; for event in state.mesh_event_reader.iter(&mesh_events) { match event { @@ -303,39 +392,65 @@ pub fn mesh_resource_provider_system( )), VERTEX_ATTRIBUTE_BUFFER_ID, ); + + if let Some(mesh_entities) = state.mesh_entities.get_mut(changed_mesh_handle) { + for entity in mesh_entities.entities.iter() { + if let Ok(render_pipelines) = queries.q0_mut().get_mut(*entity) { + update_entity_mesh( + render_resource_context, + mesh, + changed_mesh_handle, + render_pipelines, + ); + } + } + } } } // handover buffers to pipeline - for (handle, mut render_pipelines) in query.iter_mut() { + for (entity, handle, render_pipelines) in queries.q1_mut().iter_mut() { + let mesh_entities = state + .mesh_entities + .entry(handle.clone_weak()) + .or_insert_with(MeshEntities::default); + mesh_entities.entities.insert(entity); if let Some(mesh) = meshes.get(handle) { - for render_pipeline in render_pipelines.pipelines.iter_mut() { - render_pipeline.specialization.primitive_topology = mesh.primitive_topology; - // TODO: don't allocate a new vertex buffer descriptor for every entity - render_pipeline.specialization.vertex_buffer_descriptor = - mesh.get_vertex_buffer_descriptor(); - render_pipeline.specialization.index_format = mesh - .indices() - .map(|i| i.into()) - .unwrap_or(IndexFormat::Uint32); - } + update_entity_mesh(render_resource_context, mesh, handle, render_pipelines); + } + } +} - if let Some(RenderResourceId::Buffer(index_buffer_resource)) = - render_resource_context.get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX) - { - // set index buffer into binding - render_pipelines - .bindings - .set_index_buffer(index_buffer_resource); - } +fn update_entity_mesh( + render_resource_context: &dyn RenderResourceContext, + mesh: &Mesh, + handle: &Handle, + mut render_pipelines: Mut, +) { + for render_pipeline in render_pipelines.pipelines.iter_mut() { + render_pipeline.specialization.primitive_topology = mesh.primitive_topology; + // TODO: don't allocate a new vertex buffer descriptor for every entity + render_pipeline.specialization.vertex_buffer_descriptor = + mesh.get_vertex_buffer_descriptor(); + render_pipeline.specialization.index_format = mesh + .indices() + .map(|i| i.into()) + .unwrap_or(IndexFormat::Uint32); + } - if let Some(RenderResourceId::Buffer(vertex_attribute_buffer_resource)) = - render_resource_context.get_asset_resource(handle, VERTEX_ATTRIBUTE_BUFFER_ID) - { - // set index buffer into binding - render_pipelines.bindings.vertex_attribute_buffer = - Some(vertex_attribute_buffer_resource); - } - } + if let Some(RenderResourceId::Buffer(index_buffer_resource)) = + render_resource_context.get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX) + { + // set index buffer into binding + render_pipelines + .bindings + .set_index_buffer(index_buffer_resource); + } + + if let Some(RenderResourceId::Buffer(vertex_attribute_buffer_resource)) = + render_resource_context.get_asset_resource(handle, VERTEX_ATTRIBUTE_BUFFER_ID) + { + // set index buffer into binding + render_pipelines.bindings.vertex_attribute_buffer = Some(vertex_attribute_buffer_resource); } } diff --git a/crates/bevy_render/src/mesh/shape.rs b/crates/bevy_render/src/mesh/shape.rs index c29323d9979f8..72e28938d8276 100644 --- a/crates/bevy_render/src/mesh/shape.rs +++ b/crates/bevy_render/src/mesh/shape.rs @@ -1,7 +1,7 @@ use super::{Indices, Mesh}; use crate::pipeline::PrimitiveTopology; use bevy_math::*; -use hexasphere::Hexasphere; +use hexasphere::shapes::IcoSphere; pub struct Cube { pub size: f32, @@ -273,15 +273,17 @@ impl Default for Icosphere { impl From for Mesh { fn from(sphere: Icosphere) -> Self { if sphere.subdivisions >= 80 { - let temp_sphere = Hexasphere::new(sphere.subdivisions, |_| ()); + // https://oeis.org/A005901 + let subdivisions = sphere.subdivisions + 1; + let number_of_resulting_points = (subdivisions * subdivisions * 10) + 2; panic!( - "Cannot create an icosphere of {} subdivisions due to there being too many vertices being generated: {} (Limited to 65535 vertices or 79 subdivisions)", + "Cannot create an icosphere of {} subdivisions due to there being too many vertices being generated: {}. (Limited to 65535 vertices or 79 subdivisions)", sphere.subdivisions, - temp_sphere.raw_points().len() + number_of_resulting_points ); } - let hexasphere = Hexasphere::new(sphere.subdivisions, |point| { + let generated = IcoSphere::new(sphere.subdivisions, |point| { let inclination = point.z.acos(); let azumith = point.y.atan2(point.x); @@ -291,7 +293,7 @@ impl From for Mesh { [norm_inclination, norm_azumith] }); - let raw_points = hexasphere.raw_points(); + let raw_points = generated.raw_points(); let points = raw_points .iter() @@ -304,12 +306,12 @@ impl From for Mesh { .map(Into::into) .collect::>(); - let uvs = hexasphere.raw_data().to_owned(); + let uvs = generated.raw_data().to_owned(); - let mut indices = Vec::with_capacity(hexasphere.indices_per_main_triangle() * 20); + let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20); for i in 0..20 { - hexasphere.get_indices(i, &mut indices); + generated.get_indices(i, &mut indices); } let indices = Indices::U32(indices); diff --git a/crates/bevy_render/src/pipeline/pipeline.rs b/crates/bevy_render/src/pipeline/pipeline.rs index 3e2c6113532d1..eb8c836d05b39 100644 --- a/crates/bevy_render/src/pipeline/pipeline.rs +++ b/crates/bevy_render/src/pipeline/pipeline.rs @@ -7,7 +7,7 @@ use super::{ PipelineLayout, StencilStateDescriptor, }; use crate::{shader::ShaderStages, texture::TextureFormat}; -use bevy_type_registry::TypeUuid; +use bevy_reflect::TypeUuid; #[derive(Clone, Debug, TypeUuid)] #[uuid = "ebfc1d11-a2a4-44cb-8f12-c49cc631146c"] diff --git a/crates/bevy_render/src/pipeline/pipeline_compiler.rs b/crates/bevy_render/src/pipeline/pipeline_compiler.rs index 37676ab0fa9de..1bb10a519a9b2 100644 --- a/crates/bevy_render/src/pipeline/pipeline_compiler.rs +++ b/crates/bevy_render/src/pipeline/pipeline_compiler.rs @@ -2,19 +2,19 @@ use super::{state_descriptors::PrimitiveTopology, IndexFormat, PipelineDescripto use crate::{ pipeline::{BindType, InputStepMode, VertexBufferDescriptor}, renderer::RenderResourceContext, - shader::{Shader, ShaderSource}, + shader::{Shader, ShaderError, ShaderSource}, }; use bevy_asset::{Assets, Handle}; -use bevy_property::{Properties, Property}; +use bevy_reflect::Reflect; use bevy_utils::{HashMap, HashSet}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -#[derive(Clone, Eq, PartialEq, Debug, Properties)] +#[derive(Clone, Eq, PartialEq, Debug, Reflect)] pub struct PipelineSpecialization { pub shader_specialization: ShaderSpecialization, pub primitive_topology: PrimitiveTopology, - pub dynamic_bindings: Vec, + pub dynamic_bindings: HashSet, pub index_format: IndexFormat, pub vertex_buffer_descriptor: VertexBufferDescriptor, pub sample_count: u32, @@ -40,7 +40,7 @@ impl PipelineSpecialization { } } -#[derive(Clone, Eq, PartialEq, Debug, Default, Property, Serialize, Deserialize)] +#[derive(Clone, Eq, PartialEq, Debug, Default, Reflect, Serialize, Deserialize)] pub struct ShaderSpecialization { pub shader_defs: HashSet, } @@ -60,6 +60,7 @@ struct SpecializedPipeline { #[derive(Debug, Default)] pub struct PipelineCompiler { specialized_shaders: HashMap, Vec>, + specialized_shader_pipelines: HashMap, Vec>>, specialized_pipelines: HashMap, Vec>, } @@ -70,7 +71,7 @@ impl PipelineCompiler { shaders: &mut Assets, shader_handle: &Handle, shader_specialization: &ShaderSpecialization, - ) -> Handle { + ) -> Result, ShaderError> { let specialized_shaders = self .specialized_shaders .entry(shader_handle.clone_weak()) @@ -80,7 +81,7 @@ impl PipelineCompiler { // don't produce new shader if the input source is already spirv if let ShaderSource::Spirv(_) = shader.source { - return shader_handle.clone_weak(); + return Ok(shader_handle.clone_weak()); } if let Some(specialized_shader) = @@ -91,7 +92,7 @@ impl PipelineCompiler { }) { // if shader has already been compiled with current configuration, use existing shader - specialized_shader.shader.clone_weak() + Ok(specialized_shader.shader.clone_weak()) } else { // if no shader exists with the current configuration, create new shader and compile let shader_def_vec = shader_specialization @@ -100,14 +101,14 @@ impl PipelineCompiler { .cloned() .collect::>(); let compiled_shader = - render_resource_context.get_specialized_shader(shader, Some(&shader_def_vec)); + render_resource_context.get_specialized_shader(shader, Some(&shader_def_vec))?; let specialized_handle = shaders.add(compiled_shader); let weak_specialized_handle = specialized_handle.clone_weak(); specialized_shaders.push(SpecializedShader { shader: specialized_handle, specialization: shader_specialization.clone(), }); - weak_specialized_handle + Ok(weak_specialized_handle) } } @@ -138,23 +139,31 @@ impl PipelineCompiler { ) -> Handle { let source_descriptor = pipelines.get(source_pipeline).unwrap(); let mut specialized_descriptor = source_descriptor.clone(); - specialized_descriptor.shader_stages.vertex = self.compile_shader( - render_resource_context, - shaders, - &specialized_descriptor.shader_stages.vertex, - &pipeline_specialization.shader_specialization, - ); + let specialized_vertex_shader = self + .compile_shader( + render_resource_context, + shaders, + &specialized_descriptor.shader_stages.vertex, + &pipeline_specialization.shader_specialization, + ) + .unwrap(); + specialized_descriptor.shader_stages.vertex = specialized_vertex_shader.clone_weak(); + let mut specialized_fragment_shader = None; specialized_descriptor.shader_stages.fragment = specialized_descriptor .shader_stages .fragment .as_ref() .map(|fragment| { - self.compile_shader( - render_resource_context, - shaders, - fragment, - &pipeline_specialization.shader_specialization, - ) + let shader = self + .compile_shader( + render_resource_context, + shaders, + fragment, + &pipeline_specialization.shader_specialization, + ) + .unwrap(); + specialized_fragment_shader = Some(shader.clone_weak()); + shader }); let mut layout = render_resource_context.reflect_pipeline_layout( @@ -221,7 +230,7 @@ impl PipelineCompiler { .push(compiled_vertex_attribute); } else { panic!( - "Attribute {} is required by shader, but not supplied by mesh. Either remove the attribute from the shader or supply the attribute ({}) to the mesh. ", + "Attribute {} is required by shader, but not supplied by mesh. Either remove the attribute from the shader or supply the attribute ({}) to the mesh.", shader_vertex_attribute.name, shader_vertex_attribute.name, ); @@ -244,6 +253,18 @@ impl PipelineCompiler { &shaders, ); + // track specialized shader pipelines + self.specialized_shader_pipelines + .entry(specialized_vertex_shader) + .or_insert_with(Default::default) + .push(source_pipeline.clone_weak()); + if let Some(specialized_fragment_shader) = specialized_fragment_shader { + self.specialized_shader_pipelines + .entry(specialized_fragment_shader) + .or_insert_with(Default::default) + .push(source_pipeline.clone_weak()); + } + let specialized_pipelines = self .specialized_pipelines .entry(source_pipeline.clone_weak()) @@ -282,4 +303,56 @@ impl PipelineCompiler { }) .flatten() } + + /// Update specialized shaders and remove any related specialized + /// pipelines and assets. + pub fn update_shader( + &mut self, + shader: &Handle, + pipelines: &mut Assets, + shaders: &mut Assets, + render_resource_context: &dyn RenderResourceContext, + ) -> Result<(), ShaderError> { + if let Some(specialized_shaders) = self.specialized_shaders.get_mut(shader) { + for specialized_shader in specialized_shaders { + // Recompile specialized shader. If it fails, we bail immediately. + let shader_def_vec = specialized_shader + .specialization + .shader_defs + .iter() + .cloned() + .collect::>(); + let new_handle = + shaders.add(render_resource_context.get_specialized_shader( + shaders.get(shader).unwrap(), + Some(&shader_def_vec), + )?); + + // Replace handle and remove old from assets. + let old_handle = std::mem::replace(&mut specialized_shader.shader, new_handle); + shaders.remove(&old_handle); + + // Find source pipelines that use the old specialized + // shader, and remove from tracking. + if let Some(source_pipelines) = + self.specialized_shader_pipelines.remove(&old_handle) + { + // Remove all specialized pipelines from tracking + // and asset storage. They will be rebuilt on next + // draw. + for source_pipeline in source_pipelines { + if let Some(specialized_pipelines) = + self.specialized_pipelines.remove(&source_pipeline) + { + for p in specialized_pipelines { + pipelines.remove(p.pipeline); + } + } + } + } + } + } + + Ok(()) + } } diff --git a/crates/bevy_render/src/pipeline/pipeline_layout.rs b/crates/bevy_render/src/pipeline/pipeline_layout.rs index 7069e1dca411b..3aa3a1ed8a03b 100644 --- a/crates/bevy_render/src/pipeline/pipeline_layout.rs +++ b/crates/bevy_render/src/pipeline/pipeline_layout.rs @@ -34,7 +34,7 @@ impl PipelineLayout { || binding.name != shader_binding.name || binding.index != shader_binding.index { - panic!("Binding {} in BindGroup {} does not match across all shader types: {:?} {:?}", binding.index, bind_group.index, binding, shader_binding); + panic!("Binding {} in BindGroup {} does not match across all shader types: {:?} {:?}.", binding.index, bind_group.index, binding, shader_binding); } } else { bind_group.bindings.push(shader_binding.clone()); diff --git a/crates/bevy_render/src/pipeline/render_pipelines.rs b/crates/bevy_render/src/pipeline/render_pipelines.rs index 6fa8871060216..0831e93cd0ad8 100644 --- a/crates/bevy_render/src/pipeline/render_pipelines.rs +++ b/crates/bevy_render/src/pipeline/render_pipelines.rs @@ -2,15 +2,15 @@ use super::{PipelineDescriptor, PipelineSpecialization}; use crate::{ draw::{Draw, DrawContext}, mesh::{Indices, Mesh}, - prelude::Msaa, + prelude::{Msaa, Visible}, renderer::RenderResourceBindings, }; use bevy_asset::{Assets, Handle}; use bevy_ecs::{Query, Res, ResMut}; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; +use bevy_utils::HashSet; -#[derive(Debug, Properties, Default, Clone)] -#[non_exhaustive] +#[derive(Debug, Default, Clone, Reflect)] pub struct RenderPipeline { pub pipeline: Handle, pub specialization: PipelineSpecialization, @@ -39,10 +39,11 @@ impl RenderPipeline { } } -#[derive(Debug, Properties, Clone)] +#[derive(Debug, Clone, Reflect)] +#[reflect(Component)] pub struct RenderPipelines { pub pipelines: Vec, - #[property(ignore)] + #[reflect(ignore)] pub bindings: RenderResourceBindings, } @@ -81,10 +82,10 @@ pub fn draw_render_pipelines_system( mut render_resource_bindings: ResMut, msaa: Res, meshes: Res>, - mut query: Query<(&mut Draw, &mut RenderPipelines, &Handle)>, + mut query: Query<(&mut Draw, &mut RenderPipelines, &Handle, &Visible)>, ) { - for (mut draw, mut render_pipelines, mesh_handle) in query.iter_mut() { - if !draw.is_visible { + for (mut draw, mut render_pipelines, mesh_handle, visible) in query.iter_mut() { + if !visible.is_visible { continue; } @@ -111,9 +112,22 @@ pub fn draw_render_pipelines_system( .bindings .iter_dynamic_bindings() .map(|name| name.to_string()) - .collect::>(); + .collect::>(); pipeline.dynamic_bindings_generation = render_pipelines.bindings.dynamic_bindings_generation(); + for (handle, _) in render_pipelines.bindings.iter_assets() { + if let Some(bindings) = draw_context + .asset_render_resource_bindings + .get_untyped(handle) + { + for binding in bindings.iter_dynamic_bindings() { + pipeline + .specialization + .dynamic_bindings + .insert(binding.to_string()); + } + } + } } } diff --git a/crates/bevy_render/src/pipeline/state_descriptors.rs b/crates/bevy_render/src/pipeline/state_descriptors.rs index 1d04ab33af4f3..ffd5b99995133 100644 --- a/crates/bevy_render/src/pipeline/state_descriptors.rs +++ b/crates/bevy_render/src/pipeline/state_descriptors.rs @@ -1,5 +1,5 @@ use crate::texture::TextureFormat; -use bevy_property::Property; +use bevy_reflect::Reflect; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug)] @@ -59,7 +59,7 @@ pub enum CompareFunction { Always = 7, } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Property)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Reflect)] pub enum PrimitiveTopology { PointList = 0, LineList = 1, @@ -182,7 +182,7 @@ impl Default for BlendOperation { } } -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Property)] +#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Reflect)] pub enum IndexFormat { Uint16 = 0, Uint32 = 1, diff --git a/crates/bevy_render/src/pipeline/vertex_buffer_descriptor.rs b/crates/bevy_render/src/pipeline/vertex_buffer_descriptor.rs index 37ecd2760d326..42bf9673df44a 100644 --- a/crates/bevy_render/src/pipeline/vertex_buffer_descriptor.rs +++ b/crates/bevy_render/src/pipeline/vertex_buffer_descriptor.rs @@ -1,12 +1,13 @@ use super::VertexFormat; -use bevy_property::Property; +use bevy_reflect::{Reflect, ReflectDeserialize}; use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, hash::{Hash, Hasher}, }; -#[derive(Clone, Debug, Eq, PartialEq, Default, Property, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Default, Reflect, Serialize, Deserialize)] +#[reflect_value(Serialize, Deserialize, PartialEq)] pub struct VertexBufferDescriptor { pub name: Cow<'static, str>, pub stride: u64, diff --git a/crates/bevy_render/src/render_graph/base.rs b/crates/bevy_render/src/render_graph/base.rs index 5bc46a85152cf..e1630c4e8f2dd 100644 --- a/crates/bevy_render/src/render_graph/base.rs +++ b/crates/bevy_render/src/render_graph/base.rs @@ -10,11 +10,12 @@ use crate::{ texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage}, Color, }; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use bevy_window::WindowId; /// A component that indicates that an entity should be drawn in the "main pass" -#[derive(Default, Properties)] +#[derive(Default, Reflect)] +#[reflect(Component)] pub struct MainPass; #[derive(Debug)] @@ -63,8 +64,8 @@ pub struct BaseRenderGraphConfig { pub mod node { pub const PRIMARY_SWAP_CHAIN: &str = "swapchain"; - pub const CAMERA3D: &str = "camera3d"; - pub const CAMERA2D: &str = "camera2d"; + pub const CAMERA_3D: &str = "camera_3d"; + pub const CAMERA_2D: &str = "camera_2d"; pub const TEXTURE_COPY: &str = "texture_copy"; pub const MAIN_DEPTH_TEXTURE: &str = "main_pass_depth_texture"; pub const MAIN_SAMPLED_COLOR_ATTACHMENT: &str = "main_pass_sampled_color_attachment"; @@ -73,8 +74,8 @@ pub mod node { } pub mod camera { - pub const CAMERA3D: &str = "Camera3d"; - pub const CAMERA2D: &str = "Camera2d"; + pub const CAMERA_3D: &str = "Camera3d"; + pub const CAMERA_2D: &str = "Camera2d"; } impl Default for BaseRenderGraphConfig { @@ -100,11 +101,11 @@ impl BaseRenderGraphBuilder for RenderGraph { fn add_base_graph(&mut self, config: &BaseRenderGraphConfig, msaa: &Msaa) -> &mut Self { self.add_node(node::TEXTURE_COPY, TextureCopyNode::default()); if config.add_3d_camera { - self.add_system_node(node::CAMERA3D, CameraNode::new(camera::CAMERA3D)); + self.add_system_node(node::CAMERA_3D, CameraNode::new(camera::CAMERA_3D)); } if config.add_2d_camera { - self.add_system_node(node::CAMERA2D, CameraNode::new(camera::CAMERA2D)); + self.add_system_node(node::CAMERA_2D, CameraNode::new(camera::CAMERA_2D)); } self.add_node(node::SHARED_BUFFERS, SharedBuffersNode::default()); @@ -153,11 +154,11 @@ impl BaseRenderGraphBuilder for RenderGraph { main_pass_node.use_default_clear_color(0); if config.add_3d_camera { - main_pass_node.add_camera(camera::CAMERA3D); + main_pass_node.add_camera(camera::CAMERA_3D); } if config.add_2d_camera { - main_pass_node.add_camera(camera::CAMERA2D); + main_pass_node.add_camera(camera::CAMERA_2D); } self.add_node(node::MAIN_PASS, main_pass_node); @@ -168,11 +169,13 @@ impl BaseRenderGraphBuilder for RenderGraph { .unwrap(); if config.add_3d_camera { - self.add_node_edge(node::CAMERA3D, node::MAIN_PASS).unwrap(); + self.add_node_edge(node::CAMERA_3D, node::MAIN_PASS) + .unwrap(); } if config.add_2d_camera { - self.add_node_edge(node::CAMERA2D, node::MAIN_PASS).unwrap(); + self.add_node_edge(node::CAMERA_2D, node::MAIN_PASS) + .unwrap(); } } diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index 65c3319b50a46..22cbb72893dca 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -1,5 +1,5 @@ use super::{Edge, Node, NodeId, NodeLabel, NodeState, RenderGraphError, SlotLabel, SystemNode}; -use bevy_ecs::{Commands, Schedule}; +use bevy_ecs::{Commands, Schedule, SystemStage}; use bevy_utils::HashMap; use std::{borrow::Cow, fmt::Debug}; pub struct RenderGraph { @@ -12,7 +12,7 @@ pub struct RenderGraph { impl Default for RenderGraph { fn default() -> Self { let mut schedule = Schedule::default(); - schedule.add_stage("update"); + schedule.add_stage("update", SystemStage::parallel()); Self { nodes: Default::default(), node_names: Default::default(), @@ -40,10 +40,9 @@ impl RenderGraph { where T: SystemNode + 'static, { - self.system_node_schedule - .as_mut() - .unwrap() - .add_boxed_system_to_stage("update", node.get_system(&mut self.commands)); + let schedule = self.system_node_schedule.as_mut().unwrap(); + let stage = schedule.get_stage_mut::("update").unwrap(); + stage.add_system_boxed(node.get_system(&mut self.commands)); self.add_node(name, node) } diff --git a/crates/bevy_render/src/render_graph/mod.rs b/crates/bevy_render/src/render_graph/mod.rs index d91697d6eaa45..a8c249e697c23 100644 --- a/crates/bevy_render/src/render_graph/mod.rs +++ b/crates/bevy_render/src/render_graph/mod.rs @@ -21,26 +21,26 @@ use thiserror::Error; #[derive(Error, Debug, Eq, PartialEq)] pub enum RenderGraphError { - #[error("Node does not exist")] + #[error("node does not exist")] InvalidNode(NodeLabel), - #[error("Node slot does not exist")] + #[error("node slot does not exist")] InvalidNodeSlot(SlotLabel), - #[error("Node does not match the given type")] + #[error("node does not match the given type")] WrongNodeType, - #[error("Attempted to connect a node output slot to an incompatible input node slot")] + #[error("attempted to connect a node output slot to an incompatible input node slot")] MismatchedNodeSlots { output_node: NodeId, output_slot: usize, input_node: NodeId, input_slot: usize, }, - #[error("Attempted to add an edge that already exists")] + #[error("attempted to add an edge that already exists")] EdgeAlreadyExists(Edge), - #[error("Node has an unconnected input slot.")] + #[error("node has an unconnected input slot")] UnconnectedNodeInputSlot { node: NodeId, input_slot: usize }, - #[error("Node has an unconnected output slot.")] + #[error("node has an unconnected output slot")] UnconnectedNodeOutputSlot { node: NodeId, output_slot: usize }, - #[error("Node input slot already occupied")] + #[error("node input slot already occupied")] NodeInputSlotAlreadyOccupied { node: NodeId, input_slot: usize, diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs index 3cf3aa13a6a6d..1c2a19443314e 100644 --- a/crates/bevy_render/src/render_graph/node.rs +++ b/crates/bevy_render/src/render_graph/node.rs @@ -1,9 +1,9 @@ use super::{Edge, RenderGraphError, ResourceSlotInfo, ResourceSlots}; use crate::renderer::RenderContext; use bevy_ecs::{Commands, Resources, System, World}; +use bevy_utils::Uuid; use downcast_rs::{impl_downcast, Downcast}; use std::{borrow::Cow, fmt::Debug}; -use uuid::Uuid; #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct NodeId(Uuid); @@ -37,7 +37,7 @@ pub trait Node: Downcast + Send + Sync + 'static { impl_downcast!(Node); pub trait SystemNode: Node { - fn get_system(&self, commands: &mut Commands) -> Box>; + fn get_system(&self, commands: &mut Commands) -> Box>; } #[derive(Debug)] diff --git a/crates/bevy_render/src/render_graph/nodes/camera_node.rs b/crates/bevy_render/src/render_graph/nodes/camera_node.rs index 574535f4f28c4..5e677c75b3019 100644 --- a/crates/bevy_render/src/render_graph/nodes/camera_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/camera_node.rs @@ -44,7 +44,7 @@ impl Node for CameraNode { } impl SystemNode for CameraNode { - fn get_system(&self, commands: &mut Commands) -> Box> { + fn get_system(&self, commands: &mut Commands) -> Box> { let system = camera_node_system.system(); commands.insert_local_resource( system.id(), diff --git a/crates/bevy_render/src/render_graph/nodes/pass_node.rs b/crates/bevy_render/src/render_graph/nodes/pass_node.rs index d0c130d14c45f..72f0dfb860be7 100644 --- a/crates/bevy_render/src/render_graph/nodes/pass_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/pass_node.rs @@ -6,6 +6,7 @@ use crate::{ BindGroupDescriptor, BindType, BindingDescriptor, BindingShaderStage, PipelineDescriptor, UniformProperty, }, + prelude::Visible, render_graph::{Node, ResourceSlotInfo, ResourceSlots}, renderer::{ BindGroup, BindGroupId, BufferId, RenderContext, RenderResourceBindings, RenderResourceType, @@ -236,15 +237,19 @@ where continue; }; - if !draw.is_visible { - continue; + if let Ok(visible) = world.get::(visible_entity.entity) { + if !visible.is_visible { + continue; + } } // each Draw component contains an ordered list of render commands. we turn those into actual render commands here for render_command in draw.render_commands.iter() { match render_command { RenderCommand::SetPipeline { pipeline } => { - // TODO: Filter pipelines + if draw_state.is_pipeline_set(pipeline.clone_weak()) { + continue; + } render_pass.set_pipeline(pipeline); let descriptor = pipelines.get(pipeline).unwrap(); draw_state.set_pipeline(pipeline, descriptor); @@ -290,18 +295,27 @@ where offset, slot, } => { + if draw_state.is_vertex_buffer_set(*slot, *buffer, *offset) { + continue; + } render_pass.set_vertex_buffer(*slot, *buffer, *offset); - draw_state.set_vertex_buffer(*slot, *buffer); + draw_state.set_vertex_buffer(*slot, *buffer, *offset); } RenderCommand::SetIndexBuffer { buffer, offset } => { + if draw_state.is_index_buffer_set(*buffer, *offset) { + continue; + } render_pass.set_index_buffer(*buffer, *offset); - draw_state.set_index_buffer(*buffer) + draw_state.set_index_buffer(*buffer, *offset) } RenderCommand::SetBindGroup { index, bind_group, dynamic_uniform_indices, } => { + if dynamic_uniform_indices.is_none() && draw_state.is_bind_group_set(*index, *bind_group) { + continue; + } let pipeline = pipelines.get(draw_state.pipeline.as_ref().unwrap()).unwrap(); let layout = pipeline.get_layout().unwrap(); let bind_group_descriptor = layout.get_bind_group(*index).unwrap(); @@ -329,8 +343,8 @@ where struct DrawState { pipeline: Option>, bind_groups: Vec>, - vertex_buffers: Vec>, - index_buffer: Option, + vertex_buffers: Vec>, + index_buffer: Option<(BufferId, u64)>, } impl DrawState { @@ -338,12 +352,24 @@ impl DrawState { self.bind_groups[index as usize] = Some(bind_group); } - pub fn set_vertex_buffer(&mut self, index: u32, buffer: BufferId) { - self.vertex_buffers[index as usize] = Some(buffer); + pub fn is_bind_group_set(&self, index: u32, bind_group: BindGroupId) -> bool { + self.bind_groups[index as usize] == Some(bind_group) + } + + pub fn set_vertex_buffer(&mut self, index: u32, buffer: BufferId, offset: u64) { + self.vertex_buffers[index as usize] = Some((buffer, offset)); + } + + pub fn is_vertex_buffer_set(&self, index: u32, buffer: BufferId, offset: u64) -> bool { + self.vertex_buffers[index as usize] == Some((buffer, offset)) } - pub fn set_index_buffer(&mut self, buffer: BufferId) { - self.index_buffer = Some(buffer); + pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64) { + self.index_buffer = Some((buffer, offset)); + } + + pub fn is_index_buffer_set(&self, buffer: BufferId, offset: u64) -> bool { + self.index_buffer == Some((buffer, offset)) } pub fn can_draw(&self) -> bool { @@ -355,6 +381,10 @@ impl DrawState { self.can_draw() && self.index_buffer.is_some() } + pub fn is_pipeline_set(&self, pipeline: Handle) -> bool { + self.pipeline == Some(pipeline) + } + pub fn set_pipeline( &mut self, handle: &Handle, diff --git a/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs b/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs index 0235c7407240d..e513d1434cccc 100644 --- a/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs @@ -1,6 +1,6 @@ use crate::{ - draw::Draw, pipeline::RenderPipelines, + prelude::Visible, render_graph::{CommandQueue, Node, ResourceSlots, SystemNode}, renderer::{ self, BufferInfo, BufferUsage, RenderContext, RenderResourceBinding, @@ -9,11 +9,15 @@ use crate::{ texture, }; -use bevy_asset::{Asset, Assets, Handle, HandleId}; -use bevy_ecs::{Commands, Entity, IntoSystem, Local, Query, Res, ResMut, Resources, System, World}; +use bevy_app::{EventReader, Events}; +use bevy_asset::{Asset, AssetEvent, Assets, Handle, HandleId}; +use bevy_ecs::{ + Changed, Commands, Entity, IntoSystem, Local, Or, Query, QuerySet, Res, ResMut, Resources, + System, With, World, +}; use bevy_utils::HashMap; use renderer::{AssetRenderResourceBindings, BufferId, RenderResourceType, RenderResources}; -use std::{hash::Hash, marker::PhantomData, ops::DerefMut}; +use std::{any::TypeId, hash::Hash, marker::PhantomData, ops::DerefMut}; #[derive(Debug)] struct QueuedBufferWrite { @@ -80,13 +84,14 @@ impl BufferArray { } } - pub fn resize(&mut self, render_resource_context: &dyn RenderResourceContext) { + pub fn resize(&mut self, render_resource_context: &dyn RenderResourceContext) -> bool { if self.len <= self.buffer_capacity { - return; + return false; } self.allocate_buffer(render_resource_context); // TODO: allow shrinking + true } pub fn allocate_buffer(&mut self, render_resource_context: &dyn RenderResourceContext) { @@ -189,12 +194,29 @@ where } /// Resize BufferArray buffers if they aren't large enough - fn resize_buffer_arrays(&mut self, render_resource_context: &dyn RenderResourceContext) { + fn resize_buffer_arrays( + &mut self, + render_resource_context: &dyn RenderResourceContext, + ) -> bool { + let mut resized = false; for buffer_array in self.buffer_arrays.iter_mut() { if let Some(buffer_array) = buffer_array { - buffer_array.resize(render_resource_context); + resized |= buffer_array.resize(render_resource_context); + } + } + + resized + } + + fn set_required_staging_buffer_size_to_max(&mut self) { + let mut new_size = 0; + for buffer_array in self.buffer_arrays.iter() { + if let Some(buffer_array) = buffer_array { + new_size += buffer_array.item_size * buffer_array.len; } } + + self.required_staging_buffer_size = new_size; } /// Update the staging buffer to provide enough space to copy data to target buffers. @@ -238,91 +260,83 @@ where staging_buffer: &mut [u8], ) { for (i, render_resource) in uniforms.iter().enumerate() { - match render_resource.resource_type() { - Some(RenderResourceType::Buffer) => { - let size = render_resource.buffer_byte_len().unwrap(); - let render_resource_name = uniforms.get_render_resource_name(i).unwrap(); - let aligned_size = - render_resource_context.get_aligned_uniform_size(size, false); - let buffer_array = self.buffer_arrays[i].as_mut().unwrap(); - let range = 0..aligned_size as u64; - let (target_buffer, target_offset) = if dynamic_uniforms { - let binding = buffer_array.get_binding(id).unwrap(); - let dynamic_index = if let RenderResourceBinding::Buffer { - dynamic_index: Some(dynamic_index), - .. - } = binding - { - dynamic_index - } else { - panic!("dynamic index should always be set"); - }; - render_resource_bindings.set(render_resource_name, binding); - (buffer_array.buffer.unwrap(), dynamic_index) + if let Some(RenderResourceType::Buffer) = render_resource.resource_type() { + let size = render_resource.buffer_byte_len().unwrap(); + let render_resource_name = uniforms.get_render_resource_name(i).unwrap(); + let aligned_size = render_resource_context.get_aligned_uniform_size(size, false); + let buffer_array = self.buffer_arrays[i].as_mut().unwrap(); + let range = 0..aligned_size as u64; + let (target_buffer, target_offset) = if dynamic_uniforms { + let binding = buffer_array.get_binding(id).unwrap(); + let dynamic_index = if let RenderResourceBinding::Buffer { + dynamic_index: Some(dynamic_index), + .. + } = binding + { + dynamic_index } else { - let mut matching_buffer = None; - if let Some(binding) = render_resource_bindings.get(render_resource_name) { - let buffer_id = binding.get_buffer().unwrap(); - if let Some(BufferInfo { - size: current_size, .. - }) = render_resource_context.get_buffer_info(buffer_id) - { - if aligned_size == current_size { - matching_buffer = Some(buffer_id); - } else { - render_resource_context.remove_buffer(buffer_id); - } + panic!("Dynamic index should always be set."); + }; + render_resource_bindings.set(render_resource_name, binding); + (buffer_array.buffer.unwrap(), dynamic_index) + } else { + let mut matching_buffer = None; + if let Some(binding) = render_resource_bindings.get(render_resource_name) { + let buffer_id = binding.get_buffer().unwrap(); + if let Some(BufferInfo { + size: current_size, .. + }) = render_resource_context.get_buffer_info(buffer_id) + { + if aligned_size == current_size { + matching_buffer = Some(buffer_id); + } else { + render_resource_context.remove_buffer(buffer_id); } } + } - let resource = if let Some(matching_buffer) = matching_buffer { - matching_buffer - } else { - let mut usage = BufferUsage::UNIFORM; - if let Some(render_resource_hints) = - uniforms.get_render_resource_hints(i) - { - if render_resource_hints.contains(RenderResourceHints::BUFFER) { - usage = BufferUsage::STORAGE - } + let resource = if let Some(matching_buffer) = matching_buffer { + matching_buffer + } else { + let mut usage = BufferUsage::UNIFORM; + if let Some(render_resource_hints) = uniforms.get_render_resource_hints(i) { + if render_resource_hints.contains(RenderResourceHints::BUFFER) { + usage = BufferUsage::STORAGE } + } - let buffer = render_resource_context.create_buffer(BufferInfo { - size: aligned_size, - buffer_usage: BufferUsage::COPY_DST | usage, - ..Default::default() - }); - - render_resource_bindings.set( - render_resource_name, - RenderResourceBinding::Buffer { - buffer, - range, - dynamic_index: None, - }, - ); - buffer - }; - - (resource, 0) + let buffer = render_resource_context.create_buffer(BufferInfo { + size: aligned_size, + buffer_usage: BufferUsage::COPY_DST | usage, + ..Default::default() + }); + + render_resource_bindings.set( + render_resource_name, + RenderResourceBinding::Buffer { + buffer, + range, + dynamic_index: None, + }, + ); + buffer }; - render_resource.write_buffer_bytes( - &mut staging_buffer[self.current_staging_buffer_offset - ..(self.current_staging_buffer_offset + size)], - ); + (resource, 0) + }; - self.queued_buffer_writes.push(QueuedBufferWrite { - buffer: target_buffer, - target_offset: target_offset as usize, - source_offset: self.current_staging_buffer_offset, - size, - }); - self.current_staging_buffer_offset += size; - } - Some(RenderResourceType::Texture) => { /* ignore textures */ } - Some(RenderResourceType::Sampler) => { /* ignore samplers */ } - None => { /* ignore None */ } + render_resource.write_buffer_bytes( + &mut staging_buffer[self.current_staging_buffer_offset + ..(self.current_staging_buffer_offset + size)], + ); + + self.queued_buffer_writes.push(QueuedBufferWrite { + buffer: target_buffer, + target_offset: target_offset as usize, + source_offset: self.current_staging_buffer_offset, + size, + }); + self.current_staging_buffer_offset += size; } } } @@ -387,7 +401,7 @@ impl SystemNode for RenderResourcesNode where T: renderer::RenderResources, { - fn get_system(&self, commands: &mut Commands) -> Box> { + fn get_system(&self, commands: &mut Commands) -> Box> { let system = render_resources_node_system::.system(); commands.insert_local_resource( system.id(), @@ -420,36 +434,59 @@ impl Default for RenderResourcesNodeState { fn render_resources_node_system( mut state: Local>, + mut entities_waiting_for_textures: Local>, render_resource_context: Res>, - mut query: Query<(Entity, &T, &Draw, &mut RenderPipelines)>, + mut queries: QuerySet<( + Query<(Entity, &T, &Visible, &mut RenderPipelines), Or<(Changed, Changed)>>, + Query<(Entity, &T, &Visible, &mut RenderPipelines)>, + )>, ) { let state = state.deref_mut(); let uniform_buffer_arrays = &mut state.uniform_buffer_arrays; let render_resource_context = &**render_resource_context; uniform_buffer_arrays.begin_update(); // initialize uniform buffer arrays using the first RenderResources - if let Some((_, first, _, _)) = query.iter_mut().next() { + if let Some((_, first, _, _)) = queries.q0_mut().iter_mut().next() { uniform_buffer_arrays.initialize(first, render_resource_context); } - for entity in query.removed::() { + for entity in queries.q0().removed::() { uniform_buffer_arrays.remove_bindings(*entity); } - for (entity, uniforms, draw, mut render_pipelines) in query.iter_mut() { - if !draw.is_visible { - continue; + // handle entities that were waiting for texture loads on the last update + for entity in std::mem::take(&mut *entities_waiting_for_textures) { + if let Ok((entity, uniforms, _visible, mut render_pipelines)) = + queries.q1_mut().get_mut(entity) + { + if !setup_uniform_texture_resources::( + &uniforms, + render_resource_context, + &mut render_pipelines.bindings, + ) { + entities_waiting_for_textures.push(entity); + } } + } + for (entity, uniforms, visible, mut render_pipelines) in queries.q0_mut().iter_mut() { + if !visible.is_visible { + continue; + } uniform_buffer_arrays.prepare_uniform_buffers(entity, uniforms); - setup_uniform_texture_resources::( + if !setup_uniform_texture_resources::( &uniforms, render_resource_context, &mut render_pipelines.bindings, - ) + ) { + entities_waiting_for_textures.push(entity); + } } - uniform_buffer_arrays.resize_buffer_arrays(render_resource_context); + let resized = uniform_buffer_arrays.resize_buffer_arrays(render_resource_context); + if resized { + uniform_buffer_arrays.set_required_staging_buffer_size_to_max() + } uniform_buffer_arrays.resize_staging_buffer(render_resource_context); if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer { @@ -458,19 +495,41 @@ fn render_resources_node_system( staging_buffer, 0..state.uniform_buffer_arrays.staging_buffer_size as u64, &mut |mut staging_buffer, _render_resource_context| { - for (entity, uniforms, draw, mut render_pipelines) in query.iter_mut() { - if !draw.is_visible { - continue; + // if the buffer array was resized, write all entities to the new buffer, otherwise only write changes + if resized { + for (entity, uniforms, visible, mut render_pipelines) in + queries.q1_mut().iter_mut() + { + if !visible.is_visible { + continue; + } + + state.uniform_buffer_arrays.write_uniform_buffers( + entity, + &uniforms, + state.dynamic_uniforms, + render_resource_context, + &mut render_pipelines.bindings, + &mut staging_buffer, + ); } + } else { + for (entity, uniforms, visible, mut render_pipelines) in + queries.q0_mut().iter_mut() + { + if !visible.is_visible { + continue; + } - state.uniform_buffer_arrays.write_uniform_buffers( - entity, - &uniforms, - state.dynamic_uniforms, - render_resource_context, - &mut render_pipelines.bindings, - &mut staging_buffer, - ); + state.uniform_buffer_arrays.write_uniform_buffers( + entity, + &uniforms, + state.dynamic_uniforms, + render_resource_context, + &mut render_pipelines.bindings, + &mut staging_buffer, + ); + } } }, ); @@ -521,13 +580,11 @@ where } } -const EXPECT_ASSET_MESSAGE: &str = "Only assets that exist should be in the modified assets list"; - impl SystemNode for AssetRenderResourcesNode where T: renderer::RenderResources + Asset, { - fn get_system(&self, commands: &mut Commands) -> Box> { + fn get_system(&self, commands: &mut Commands) -> Box> { let system = asset_render_resources_node_system::.system(); commands.insert_local_resource( system.id(), @@ -542,35 +599,91 @@ where } } +struct AssetRenderNodeState { + event_reader: EventReader>, + assets_waiting_for_textures: Vec, +} + +impl Default for AssetRenderNodeState { + fn default() -> Self { + Self { + event_reader: Default::default(), + assets_waiting_for_textures: Default::default(), + } + } +} + +#[allow(clippy::too_many_arguments)] fn asset_render_resources_node_system( mut state: Local>, + mut asset_state: Local>, assets: Res>, + asset_events: Res>>, mut asset_render_resource_bindings: ResMut, render_resource_context: Res>, - mut query: Query<(&Handle, &Draw, &mut RenderPipelines)>, + mut queries: QuerySet<( + Query<(&Handle, &mut RenderPipelines), Changed>>, + Query<&mut RenderPipelines, With>>, + )>, + entity_query: Query, ) { let state = state.deref_mut(); let uniform_buffer_arrays = &mut state.uniform_buffer_arrays; let render_resource_context = &**render_resource_context; - let modified_assets = assets.ids().collect::>(); + let mut changed_assets = HashMap::default(); + for event in asset_state.event_reader.iter(&asset_events) { + match event { + AssetEvent::Created { ref handle } => { + if let Some(asset) = assets.get(handle) { + changed_assets.insert(handle.id, asset); + } + } + AssetEvent::Modified { ref handle } => { + if let Some(asset) = assets.get(handle) { + changed_assets.insert(handle.id, asset); + } + } + AssetEvent::Removed { ref handle } => { + uniform_buffer_arrays.remove_bindings(handle.id); + // if asset was modified and removed in the same update, ignore the modification + // events are ordered so future modification events are ok + changed_assets.remove(&handle.id); + } + } + } + + // handle assets that were waiting for texture loads on the last update + for asset_handle in std::mem::take(&mut asset_state.assets_waiting_for_textures) { + if let Some(asset) = assets.get(asset_handle) { + let mut bindings = + asset_render_resource_bindings.get_or_insert_mut(&Handle::::weak(asset_handle)); + if !setup_uniform_texture_resources::(&asset, render_resource_context, &mut bindings) + { + asset_state.assets_waiting_for_textures.push(asset_handle); + } + } + } uniform_buffer_arrays.begin_update(); // initialize uniform buffer arrays using the first RenderResources - if let Some(first_handle) = modified_assets.get(0) { - let asset = assets.get(*first_handle).expect(EXPECT_ASSET_MESSAGE); + if let Some(asset) = changed_assets.values().next() { uniform_buffer_arrays.initialize(asset, render_resource_context); } - for asset_handle in modified_assets.iter() { - let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE); + for (asset_handle, asset) in changed_assets.iter() { uniform_buffer_arrays.prepare_uniform_buffers(*asset_handle, asset); let mut bindings = asset_render_resource_bindings.get_or_insert_mut(&Handle::::weak(*asset_handle)); - setup_uniform_texture_resources::(&asset, render_resource_context, &mut bindings); + if !setup_uniform_texture_resources::(&asset, render_resource_context, &mut bindings) { + asset_state.assets_waiting_for_textures.push(*asset_handle); + } } - uniform_buffer_arrays.resize_buffer_arrays(render_resource_context); + let resized = uniform_buffer_arrays.resize_buffer_arrays(render_resource_context); + if resized { + uniform_buffer_arrays.set_required_staging_buffer_size_to_max() + } uniform_buffer_arrays.resize_staging_buffer(render_resource_context); if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer { @@ -579,19 +692,34 @@ fn asset_render_resources_node_system( staging_buffer, 0..state.uniform_buffer_arrays.staging_buffer_size as u64, &mut |mut staging_buffer, _render_resource_context| { - for asset_handle in modified_assets.iter() { - let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE); - let mut render_resource_bindings = asset_render_resource_bindings - .get_or_insert_mut(&Handle::::weak(*asset_handle)); - // TODO: only setup buffer if we haven't seen this handle before - state.uniform_buffer_arrays.write_uniform_buffers( - *asset_handle, - &asset, - state.dynamic_uniforms, - render_resource_context, - &mut render_resource_bindings, - &mut staging_buffer, - ); + if resized { + for (asset_handle, asset) in assets.iter() { + let mut render_resource_bindings = asset_render_resource_bindings + .get_or_insert_mut(&Handle::::weak(asset_handle)); + // TODO: only setup buffer if we haven't seen this handle before + state.uniform_buffer_arrays.write_uniform_buffers( + asset_handle, + &asset, + state.dynamic_uniforms, + render_resource_context, + &mut render_resource_bindings, + &mut staging_buffer, + ); + } + } else { + for (asset_handle, asset) in changed_assets.iter() { + let mut render_resource_bindings = asset_render_resource_bindings + .get_or_insert_mut(&Handle::::weak(*asset_handle)); + // TODO: only setup buffer if we haven't seen this handle before + state.uniform_buffer_arrays.write_uniform_buffers( + *asset_handle, + &asset, + state.dynamic_uniforms, + render_resource_context, + &mut render_resource_bindings, + &mut staging_buffer, + ); + } } }, ); @@ -602,23 +730,35 @@ fn asset_render_resources_node_system( .copy_staging_buffer_to_final_buffers(&mut state.command_queue, staging_buffer); } - for (asset_handle, draw, mut render_pipelines) in query.iter_mut() { - if !draw.is_visible { - continue; - } - if let Some(asset_bindings) = asset_render_resource_bindings.get(asset_handle) { - render_pipelines.bindings.extend(asset_bindings); + // update removed entity asset mapping + for entity in entity_query.removed::>() { + if let Ok(mut render_pipelines) = queries.q1_mut().get_mut(*entity) { + render_pipelines + .bindings + .remove_asset_with_type(TypeId::of::()) } } + + // update changed entity asset mapping + for (asset_handle, mut render_pipelines) in queries.q0_mut().iter_mut() { + render_pipelines + .bindings + .remove_asset_with_type(TypeId::of::()); + render_pipelines + .bindings + .add_asset(asset_handle.clone_weak_untyped(), TypeId::of::()); + } } fn setup_uniform_texture_resources( uniforms: &T, render_resource_context: &dyn RenderResourceContext, render_resource_bindings: &mut RenderResourceBindings, -) where +) -> bool +where T: renderer::RenderResources, { + let mut success = true; for (i, render_resource) in uniforms.iter().enumerate() { if let Some(RenderResourceType::Texture) = render_resource.resource_type() { let render_resource_name = uniforms.get_render_resource_name(i).unwrap(); @@ -640,8 +780,12 @@ fn setup_uniform_texture_resources( RenderResourceBinding::Sampler(sampler_resource.get_sampler().unwrap()), ); continue; + } else { + success = false; } } } } + + success } diff --git a/crates/bevy_render/src/render_graph/nodes/shared_buffers_node.rs b/crates/bevy_render/src/render_graph/nodes/shared_buffers_node.rs index 676df16fc5162..39144b22d744d 100644 --- a/crates/bevy_render/src/render_graph/nodes/shared_buffers_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/shared_buffers_node.rs @@ -16,8 +16,7 @@ impl Node for SharedBuffersNode { _input: &ResourceSlots, _output: &mut ResourceSlots, ) { - let shared_buffers = resources.get::().unwrap(); - let mut command_queue = shared_buffers.reset_command_queue(); - command_queue.execute(render_context); + let mut shared_buffers = resources.get_mut::().unwrap(); + shared_buffers.apply(render_context); } } diff --git a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs b/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs index d68a6729ec217..f2f0d7500429c 100644 --- a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs @@ -34,13 +34,17 @@ impl Node for TextureCopyNode { } let texture_descriptor: TextureDescriptor = texture.into(); - let width = texture.size.x as usize; - let aligned_width = render_context - .resources() - .get_aligned_texture_size(texture.size.x as usize); + let width = texture.size.width as usize; + let aligned_width = + render_context.resources().get_aligned_texture_size(width); let format_size = texture.format.pixel_size(); - let mut aligned_data = - vec![0; format_size * aligned_width * texture.size.y as usize]; + let mut aligned_data = vec![ + 0; + format_size + * aligned_width + * texture.size.height as usize + * texture.size.depth as usize + ]; texture .data .chunks_exact(format_size * width) diff --git a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs b/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs index 1c700c4d8c818..b8a95ccdb9110 100644 --- a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs @@ -49,7 +49,7 @@ impl Node for WindowSwapChainNode { let window = windows .get(self.window_id) - .expect("Received window resized event for non-existent window"); + .expect("Received window resized event for non-existent window."); let render_resource_context = render_context.resources_mut(); diff --git a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs b/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs index dc6ee403f3ed8..d287002ab4e29 100644 --- a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs @@ -52,7 +52,7 @@ impl Node for WindowTextureNode { let window = windows .get(self.window_id) - .expect("Received window resized event for non-existent window"); + .expect("Received window resized event for non-existent window."); if self .window_created_event_reader @@ -68,8 +68,8 @@ impl Node for WindowTextureNode { render_resource_context.remove_texture(old_texture); } - self.descriptor.size.width = window.width(); - self.descriptor.size.height = window.height(); + self.descriptor.size.width = window.physical_width(); + self.descriptor.size.height = window.physical_height(); let texture_resource = render_resource_context.create_texture(self.descriptor); output.set(WINDOW_TEXTURE, RenderResourceId::Texture(texture_resource)); } diff --git a/crates/bevy_render/src/render_graph/schedule.rs b/crates/bevy_render/src/render_graph/schedule.rs index e250de45de664..23d70d2ec2e8a 100644 --- a/crates/bevy_render/src/render_graph/schedule.rs +++ b/crates/bevy_render/src/render_graph/schedule.rs @@ -4,7 +4,8 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum StagerError { - #[error("Encountered a RenderGraphError")] + // This might have to be `:` tagged at the end. + #[error("encountered a `RenderGraphError`")] RenderGraphError(#[from] RenderGraphError), } @@ -211,7 +212,7 @@ fn stage_node( .map(|e| { node_stages_and_jobs .get(&e.get_output_node()) - .expect("already checked that parents were visited") + .expect("Already checked that parents were visited.") }) .max() { @@ -223,7 +224,7 @@ fn stage_node( .filter(|e| { let (max_stage, _) = node_stages_and_jobs .get(&e.get_output_node()) - .expect("already checked that parents were visited"); + .expect("Already checked that parents were visited."); max_stage == max_parent_stage }) .count(); diff --git a/crates/bevy_render/src/render_graph/system.rs b/crates/bevy_render/src/render_graph/system.rs index 875c33298c653..78fa184c2469e 100644 --- a/crates/bevy_render/src/render_graph/system.rs +++ b/crates/bevy_render/src/render_graph/system.rs @@ -10,12 +10,7 @@ pub fn render_graph_schedule_executor_system(world: &mut World, resources: &mut commands.apply(world, resources); if let Some(schedule) = system_schedule.as_mut() { - schedule.run_on_systems(|system| { - if !system.is_initialized() { - system.initialize(world, resources); - } - }); - schedule.run(world, resources); + schedule.initialize_and_run(world, resources); } let mut render_graph = resources.get_mut::().unwrap(); if let Some(schedule) = system_schedule.take() { diff --git a/crates/bevy_render/src/renderer/headless_render_resource_context.rs b/crates/bevy_render/src/renderer/headless_render_resource_context.rs index 03e3177f4c636..ed182a51bb1d3 100644 --- a/crates/bevy_render/src/renderer/headless_render_resource_context.rs +++ b/crates/bevy_render/src/renderer/headless_render_resource_context.rs @@ -2,7 +2,7 @@ use super::RenderResourceContext; use crate::{ pipeline::{BindGroupDescriptorId, PipelineDescriptor}, renderer::{BindGroup, BufferId, BufferInfo, RenderResourceId, SamplerId, TextureId}, - shader::Shader, + shader::{Shader, ShaderError}, texture::{SamplerDescriptor, TextureDescriptor}, }; use bevy_asset::{Assets, Handle, HandleUntyped}; @@ -149,7 +149,13 @@ impl RenderResourceContext for HeadlessRenderResourceContext { size } - fn get_specialized_shader(&self, shader: &Shader, _macros: Option<&[String]>) -> Shader { - shader.clone() + fn get_specialized_shader( + &self, + shader: &Shader, + _macros: Option<&[String]>, + ) -> Result { + Ok(shader.clone()) } + + fn remove_stale_bind_groups(&self) {} } diff --git a/crates/bevy_render/src/renderer/render_resource/buffer.rs b/crates/bevy_render/src/renderer/render_resource/buffer.rs index e5104e140d09a..c254e228f17d1 100644 --- a/crates/bevy_render/src/renderer/render_resource/buffer.rs +++ b/crates/bevy_render/src/renderer/render_resource/buffer.rs @@ -1,4 +1,4 @@ -use uuid::Uuid; +use bevy_utils::Uuid; #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] pub struct BufferId(Uuid); diff --git a/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs b/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs index a09dfe86a6cbf..e02ce2661ebc8 100644 --- a/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs +++ b/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs @@ -5,7 +5,7 @@ use crate::{ }; use bevy_asset::{Asset, Handle, HandleUntyped}; use bevy_utils::{HashMap, HashSet}; -use std::ops::Range; +use std::{any::TypeId, ops::Range}; #[derive(Clone, PartialEq, Eq, Debug)] pub enum RenderResourceBinding { @@ -36,10 +36,13 @@ impl RenderResourceBinding { } pub fn is_dynamic_buffer(&self) -> bool { - matches!(self, RenderResourceBinding::Buffer { - dynamic_index: Some(_), - .. - }) + matches!( + self, + RenderResourceBinding::Buffer { + dynamic_index: Some(_), + .. + } + ) } pub fn get_sampler(&self) -> Option { @@ -67,6 +70,7 @@ pub struct RenderResourceBindings { /// A Buffer that is filled with zeros that will be used for attributes required by the shader, but undefined by the mesh. pub vertex_fallback_buffer: Option, pub index_buffer: Option, + assets: HashSet<(HandleUntyped, TypeId)>, bind_groups: HashMap, bind_group_descriptors: HashMap>, dirty_bind_groups: HashSet, @@ -99,6 +103,10 @@ impl RenderResourceBindings { self.dirty_bind_groups.insert(*id); } } + } else { + // unmatched bind group descriptors might now match + self.bind_group_descriptors + .retain(|_, value| value.is_some()); } } @@ -120,11 +128,12 @@ impl RenderResourceBindings { self.bind_group_descriptors.insert(descriptor.id, Some(id)); BindGroupStatus::Changed(id) } else { + self.bind_group_descriptors.insert(descriptor.id, None); BindGroupStatus::NoMatch } } - pub fn update_bind_group( + fn update_bind_group_status( &mut self, bind_group_descriptor: &BindGroupDescriptor, ) -> BindGroupStatus { @@ -144,6 +153,50 @@ impl RenderResourceBindings { } } + pub fn add_asset(&mut self, handle: HandleUntyped, type_id: TypeId) { + self.dynamic_bindings_generation += 1; + self.assets.insert((handle, type_id)); + } + + pub fn remove_asset_with_type(&mut self, type_id: TypeId) { + self.dynamic_bindings_generation += 1; + self.assets.retain(|(_, current_id)| *current_id != type_id); + } + + pub fn iter_assets(&self) -> impl Iterator { + self.assets.iter() + } + + pub fn update_bind_group( + &mut self, + bind_group_descriptor: &BindGroupDescriptor, + render_resource_context: &dyn RenderResourceContext, + ) -> Option<&BindGroup> { + let status = self.update_bind_group_status(bind_group_descriptor); + match status { + BindGroupStatus::Changed(id) => { + let bind_group = self + .get_bind_group(id) + .expect("`RenderResourceSet` was just changed, so it should exist."); + render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group); + Some(bind_group) + } + BindGroupStatus::Unchanged(id) => { + // PERF: this is only required because RenderResourceContext::remove_stale_bind_groups doesn't inform RenderResourceBindings + // when a stale bind group has been removed + let bind_group = self + .get_bind_group(id) + .expect("`RenderResourceSet` was just changed, so it should exist."); + render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group); + Some(bind_group) + } + BindGroupStatus::NoMatch => { + // ignore unchanged / unmatched render resource sets + None + } + } + } + pub fn update_bind_groups( &mut self, pipeline: &PipelineDescriptor, @@ -151,26 +204,7 @@ impl RenderResourceBindings { ) { let layout = pipeline.get_layout().unwrap(); for bind_group_descriptor in layout.bind_groups.iter() { - match self.update_bind_group(bind_group_descriptor) { - BindGroupStatus::Changed(id) => { - let bind_group = self - .get_bind_group(id) - .expect("RenderResourceSet was just changed, so it should exist"); - render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group); - } - // TODO: Don't re-create bind groups if they havent changed. this will require cleanup of orphan bind groups and - // removal of global context.clear_bind_groups() - // PERF: see above - BindGroupStatus::Unchanged(id) => { - let bind_group = self - .get_bind_group(id) - .expect("RenderResourceSet was just changed, so it should exist"); - render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group); - } - BindGroupStatus::NoMatch => { - // ignore unchanged / unmatched render resource sets - } - } + self.update_bind_group(bind_group_descriptor, render_resource_context); } } @@ -208,10 +242,13 @@ impl RenderResourceBindings { self.bindings .iter() .filter(|(_, binding)| { - matches!(binding, RenderResourceBinding::Buffer { - dynamic_index: Some(_), - .. - }) + matches!( + binding, + RenderResourceBinding::Buffer { + dynamic_index: Some(_), + .. + } + ) }) .map(|(name, _)| name.as_str()) } @@ -224,7 +261,11 @@ pub struct AssetRenderResourceBindings { impl AssetRenderResourceBindings { pub fn get(&self, handle: &Handle) -> Option<&RenderResourceBindings> { - self.bindings.get(&handle.clone_weak_untyped()) + self.get_untyped(&handle.clone_weak_untyped()) + } + + pub fn get_untyped(&self, handle: &HandleUntyped) -> Option<&RenderResourceBindings> { + self.bindings.get(handle) } pub fn get_or_insert_mut( @@ -237,7 +278,14 @@ impl AssetRenderResourceBindings { } pub fn get_mut(&mut self, handle: &Handle) -> Option<&mut RenderResourceBindings> { - self.bindings.get_mut(&handle.clone_weak_untyped()) + self.get_mut_untyped(&handle.clone_weak_untyped()) + } + + pub fn get_mut_untyped( + &mut self, + handle: &HandleUntyped, + ) -> Option<&mut RenderResourceBindings> { + self.bindings.get_mut(handle) } } @@ -289,15 +337,15 @@ mod tests { equal_bindings.set("a", resource1.clone()); equal_bindings.set("b", resource2.clone()); - let status = bindings.update_bind_group(&bind_group_descriptor); + let status = bindings.update_bind_group_status(&bind_group_descriptor); let id = if let BindGroupStatus::Changed(id) = status { id } else { - panic!("expected a changed bind group"); + panic!("Expected a changed bind group."); }; let different_bind_group_status = - different_bindings.update_bind_group(&bind_group_descriptor); + different_bindings.update_bind_group_status(&bind_group_descriptor); if let BindGroupStatus::Changed(different_bind_group_id) = different_bind_group_status { assert_ne!( id, different_bind_group_id, @@ -305,23 +353,24 @@ mod tests { ); different_bind_group_id } else { - panic!("expected a changed bind group"); + panic!("Expected a changed bind group."); }; - let equal_bind_group_status = equal_bindings.update_bind_group(&bind_group_descriptor); + let equal_bind_group_status = + equal_bindings.update_bind_group_status(&bind_group_descriptor); if let BindGroupStatus::Changed(equal_bind_group_id) = equal_bind_group_status { assert_eq!( id, equal_bind_group_id, "equal bind group should have the same id" ); } else { - panic!("expected a changed bind group"); + panic!("Expected a changed bind group."); }; let mut unmatched_bindings = RenderResourceBindings::default(); unmatched_bindings.set("a", resource1.clone()); let unmatched_bind_group_status = - unmatched_bindings.update_bind_group(&bind_group_descriptor); + unmatched_bindings.update_bind_group_status(&bind_group_descriptor); assert_eq!(unmatched_bind_group_status, BindGroupStatus::NoMatch); } } diff --git a/crates/bevy_render/src/renderer/render_resource/shared_buffers.rs b/crates/bevy_render/src/renderer/render_resource/shared_buffers.rs index 39dcb3c673ea5..bf2e399e19938 100644 --- a/crates/bevy_render/src/renderer/render_resource/shared_buffers.rs +++ b/crates/bevy_render/src/renderer/render_resource/shared_buffers.rs @@ -1,77 +1,106 @@ use super::{BufferId, BufferInfo, RenderResource, RenderResourceBinding}; use crate::{ render_graph::CommandQueue, - renderer::{BufferUsage, RenderResourceContext}, + renderer::{BufferUsage, RenderContext, RenderResourceContext}, }; -use bevy_ecs::Res; -use parking_lot::RwLock; -use std::sync::Arc; +use bevy_ecs::{Res, ResMut}; -// TODO: Instead of allocating small "exact size" buffers each frame, this should use multiple large shared buffers and probably -// a long-living "cpu mapped" staging buffer. Im punting that for now because I don't know the best way to use wgpu's new async -// buffer mapping yet. pub struct SharedBuffers { - render_resource_context: Box, - buffers: Arc>>, - command_queue: Arc>, + staging_buffer: Option, + uniform_buffer: Option, + buffers_to_free: Vec, + buffer_size: usize, + initial_size: usize, + current_offset: usize, + command_queue: CommandQueue, } impl SharedBuffers { - pub fn new(render_resource_context: Box) -> Self { + pub fn new(initial_size: usize) -> Self { Self { - render_resource_context, - buffers: Default::default(), + staging_buffer: None, + uniform_buffer: None, + buffer_size: 0, + current_offset: 0, + initial_size, + buffers_to_free: Default::default(), command_queue: Default::default(), } } - pub fn get_buffer( - &self, + pub fn grow( + &mut self, + render_resource_context: &dyn RenderResourceContext, + required_space: usize, + ) { + while self.buffer_size < self.current_offset + required_space { + self.buffer_size = if self.buffer_size == 0 { + self.initial_size + } else { + self.buffer_size * 2 + }; + } + + self.current_offset = 0; + + if let Some(staging_buffer) = self.staging_buffer.take() { + render_resource_context.unmap_buffer(staging_buffer); + self.buffers_to_free.push(staging_buffer); + } + + if let Some(uniform_buffer) = self.uniform_buffer.take() { + self.buffers_to_free.push(uniform_buffer); + } + + self.staging_buffer = Some(render_resource_context.create_buffer(BufferInfo { + size: self.buffer_size, + buffer_usage: BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC, + mapped_at_creation: true, + })); + self.uniform_buffer = Some(render_resource_context.create_buffer(BufferInfo { + size: self.buffer_size, + buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, + mapped_at_creation: false, + })); + } + + pub fn get_uniform_buffer( + &mut self, + render_resource_context: &dyn RenderResourceContext, render_resource: &T, - buffer_usage: BufferUsage, ) -> Option { if let Some(size) = render_resource.buffer_byte_len() { - let aligned_size = self - .render_resource_context - .get_aligned_uniform_size(size, false); - // PERF: this buffer will be slow - let staging_buffer = self.render_resource_context.create_buffer(BufferInfo { - size: aligned_size, - buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE, - mapped_at_creation: true, - }); + // TODO: overlap alignment if/when possible + let aligned_size = render_resource_context.get_aligned_uniform_size(size, true); + let mut new_offset = self.current_offset + aligned_size; + if new_offset > self.buffer_size { + self.grow(render_resource_context, aligned_size); + new_offset = aligned_size; + } - self.render_resource_context.write_mapped_buffer( + let range = self.current_offset as u64..new_offset as u64; + let staging_buffer = self.staging_buffer.unwrap(); + let uniform_buffer = self.uniform_buffer.unwrap(); + render_resource_context.write_mapped_buffer( staging_buffer, - 0..size as u64, + range.clone(), &mut |data, _renderer| { render_resource.write_buffer_bytes(data); }, ); - self.render_resource_context.unmap_buffer(staging_buffer); - - let destination_buffer = self.render_resource_context.create_buffer(BufferInfo { - size: aligned_size, - buffer_usage: BufferUsage::COPY_DST | buffer_usage, - ..Default::default() - }); - - let mut command_queue = self.command_queue.write(); - command_queue.copy_buffer_to_buffer( + self.command_queue.copy_buffer_to_buffer( staging_buffer, - 0, - destination_buffer, - 0, - size as u64, + self.current_offset as u64, + uniform_buffer, + self.current_offset as u64, + aligned_size as u64, ); - let mut buffers = self.buffers.write(); - buffers.push(staging_buffer); - buffers.push(destination_buffer); + self.current_offset = new_offset; Some(RenderResourceBinding::Buffer { - buffer: destination_buffer, - range: 0..aligned_size as u64, + buffer: uniform_buffer, + range, dynamic_index: None, }) } else { @@ -79,21 +108,29 @@ impl SharedBuffers { } } - // TODO: remove this when this actually uses shared buffers - pub fn free_buffers(&self) { - let mut buffers = self.buffers.write(); - for buffer in buffers.drain(..) { - self.render_resource_context.remove_buffer(buffer) + pub fn update(&mut self, render_resource_context: &dyn RenderResourceContext) { + self.current_offset = 0; + for buffer in self.buffers_to_free.drain(..) { + render_resource_context.remove_buffer(buffer) + } + + if let Some(staging_buffer) = self.staging_buffer { + render_resource_context.map_buffer(staging_buffer); } } - pub fn reset_command_queue(&self) -> CommandQueue { - let mut command_queue = self.command_queue.write(); - std::mem::take(&mut *command_queue) + pub fn apply(&mut self, render_context: &mut dyn RenderContext) { + if let Some(staging_buffer) = self.staging_buffer { + render_context.resources().unmap_buffer(staging_buffer); + } + let mut command_queue = std::mem::take(&mut self.command_queue); + command_queue.execute(render_context); } } -// TODO: remove this when this actually uses shared buffers -pub fn free_shared_buffers_system(shared_buffers: Res) { - shared_buffers.free_buffers(); +pub fn shared_buffers_update_system( + mut shared_buffers: ResMut, + render_resource_context: Res>, +) { + shared_buffers.update(&**render_resource_context); } diff --git a/crates/bevy_render/src/renderer/render_resource/texture.rs b/crates/bevy_render/src/renderer/render_resource/texture.rs index 06bafa787e02a..46e7e51753eee 100644 --- a/crates/bevy_render/src/renderer/render_resource/texture.rs +++ b/crates/bevy_render/src/renderer/render_resource/texture.rs @@ -1,4 +1,4 @@ -use uuid::Uuid; +use bevy_utils::Uuid; #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] pub struct TextureId(Uuid); diff --git a/crates/bevy_render/src/renderer/render_resource_context.rs b/crates/bevy_render/src/renderer/render_resource_context.rs index 3e49b0bccafa4..365595fac7816 100644 --- a/crates/bevy_render/src/renderer/render_resource_context.rs +++ b/crates/bevy_render/src/renderer/render_resource_context.rs @@ -1,7 +1,7 @@ use crate::{ pipeline::{BindGroupDescriptorId, PipelineDescriptor, PipelineLayout}, renderer::{BindGroup, BufferId, BufferInfo, RenderResourceId, SamplerId, TextureId}, - shader::{Shader, ShaderLayout, ShaderStages}, + shader::{Shader, ShaderError, ShaderLayout, ShaderStages}, texture::{SamplerDescriptor, TextureDescriptor}, }; use bevy_asset::{Asset, Assets, Handle, HandleUntyped}; @@ -29,7 +29,11 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static { fn create_buffer_with_data(&self, buffer_info: BufferInfo, data: &[u8]) -> BufferId; fn create_shader_module(&self, shader_handle: &Handle, shaders: &Assets); fn create_shader_module_from_source(&self, shader_handle: &Handle, shader: &Shader); - fn get_specialized_shader(&self, shader: &Shader, macros: Option<&[String]>) -> Shader; + fn get_specialized_shader( + &self, + shader: &Shader, + macros: Option<&[String]>, + ) -> Result; fn remove_buffer(&self, buffer: BufferId); fn remove_texture(&self, texture: TextureId); fn remove_sampler(&self, sampler: SamplerId); @@ -62,6 +66,7 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static { bind_group: &BindGroup, ); fn clear_bind_groups(&self); + fn remove_stale_bind_groups(&self); /// Reflects the pipeline layout from its shaders. /// /// If `bevy_conventions` is true, it will be assumed that the shader follows "bevy shader conventions". These allow diff --git a/crates/bevy_render/src/shader/shader.rs b/crates/bevy_render/src/shader/shader.rs index 14e575f670366..19a89af632f0f 100644 --- a/crates/bevy_render/src/shader/shader.rs +++ b/crates/bevy_render/src/shader/shader.rs @@ -1,7 +1,16 @@ +use crate::{ + pipeline::{PipelineCompiler, PipelineDescriptor}, + renderer::RenderResourceContext, +}; + use super::ShaderLayout; -use bevy_asset::Handle; -use bevy_type_registry::TypeUuid; +use bevy_app::{EventReader, Events}; +use bevy_asset::{AssetEvent, AssetLoader, Assets, Handle, LoadContext, LoadedAsset}; +use bevy_ecs::{Local, Res, ResMut}; +use bevy_reflect::TypeUuid; +use bevy_utils::{tracing::error, BoxedFuture}; use std::marker::Copy; +use thiserror::Error; /// The stage of a shader #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] @@ -11,7 +20,32 @@ pub enum ShaderStage { Compute, } -#[cfg(all(not(target_os = "ios"), not(target_arch = "wasm32")))] +/// An error that occurs during shader handling. +#[derive(Error, Debug)] +pub enum ShaderError { + /// Shader compilation error. + #[error("Shader compilation error: {0}")] + Compilation(String), + + #[cfg(any(target_os = "ios", all(target_arch = "aarch64", target_os = "macos")))] + /// shaderc error. + #[error("shaderc error")] + ShaderC(#[from] shaderc::Error), + + #[cfg(any(target_os = "ios", all(target_arch = "aarch64", target_os = "macos")))] + #[error("Error initializing shaderc Compiler")] + ErrorInitializingShadercCompiler, + + #[cfg(any(target_os = "ios", all(target_arch = "aarch64", target_os = "macos")))] + #[error("Error initializing shaderc CompileOptions")] + ErrorInitializingShadercCompileOptions, +} + +#[cfg(all( + not(target_os = "ios"), + not(target_arch = "wasm32"), + not(all(target_arch = "aarch64", target_os = "macos")) +))] impl Into for ShaderStage { fn into(self) -> bevy_glsl_to_spirv::ShaderType { match self { @@ -22,16 +56,21 @@ impl Into for ShaderStage { } } -#[cfg(all(not(target_os = "ios"), not(target_arch = "wasm32")))] +#[cfg(all( + not(target_os = "ios"), + not(target_arch = "wasm32"), + not(all(target_arch = "aarch64", target_os = "macos")) +))] pub fn glsl_to_spirv( glsl_source: &str, stage: ShaderStage, shader_defs: Option<&[String]>, -) -> Vec { - bevy_glsl_to_spirv::compile(glsl_source, stage.into(), shader_defs).unwrap() +) -> Result, ShaderError> { + bevy_glsl_to_spirv::compile(glsl_source, stage.into(), shader_defs) + .map_err(ShaderError::Compilation) } -#[cfg(target_os = "ios")] +#[cfg(any(target_os = "ios", all(target_arch = "aarch64", target_os = "macos")))] impl Into for ShaderStage { fn into(self) -> shaderc::ShaderKind { match self { @@ -42,31 +81,31 @@ impl Into for ShaderStage { } } -#[cfg(target_os = "ios")] +#[cfg(any(target_os = "ios", all(target_arch = "aarch64", target_os = "macos")))] pub fn glsl_to_spirv( glsl_source: &str, stage: ShaderStage, shader_defs: Option<&[String]>, -) -> Vec { - let mut compiler = shaderc::Compiler::new().unwrap(); - let mut options = shaderc::CompileOptions::new().unwrap(); +) -> Result, ShaderError> { + let mut compiler = + shaderc::Compiler::new().ok_or(ShaderError::ErrorInitializingShadercCompiler)?; + let mut options = shaderc::CompileOptions::new() + .ok_or(ShaderError::ErrorInitializingShadercCompileOptions)?; if let Some(shader_defs) = shader_defs { for def in shader_defs.iter() { options.add_macro_definition(def, None); } } - let binary_result = compiler - .compile_into_spirv( - glsl_source, - stage.into(), - "shader.glsl", - "main", - Some(&options), - ) - .unwrap(); + let binary_result = compiler.compile_into_spirv( + glsl_source, + stage.into(), + "shader.glsl", + "main", + Some(&options), + )?; - binary_result.as_binary().to_vec() + Ok(binary_result.as_binary().to_vec()) } fn bytes_to_words(bytes: &[u8]) -> Vec { @@ -114,19 +153,19 @@ impl Shader { } #[cfg(not(target_arch = "wasm32"))] - pub fn get_spirv(&self, macros: Option<&[String]>) -> Vec { + pub fn get_spirv(&self, macros: Option<&[String]>) -> Result, ShaderError> { match self.source { - ShaderSource::Spirv(ref bytes) => bytes.clone(), + ShaderSource::Spirv(ref bytes) => Ok(bytes.clone()), ShaderSource::Glsl(ref source) => glsl_to_spirv(&source, self.stage, macros), } } #[cfg(not(target_arch = "wasm32"))] - pub fn get_spirv_shader(&self, macros: Option<&[String]>) -> Shader { - Shader { - source: ShaderSource::Spirv(self.get_spirv(macros)), + pub fn get_spirv_shader(&self, macros: Option<&[String]>) -> Result { + Ok(Shader { + source: ShaderSource::Spirv(self.get_spirv(macros)?), stage: self.stage, - } + }) } #[cfg(not(target_arch = "wasm32"))] @@ -137,13 +176,13 @@ impl Shader { enforce_bevy_conventions, )) } else { - panic!("Cannot reflect layout of non-SpirV shader. Try compiling this shader to SpirV first using self.get_spirv_shader()"); + panic!("Cannot reflect layout of non-SpirV shader. Try compiling this shader to SpirV first using self.get_spirv_shader()."); } } #[cfg(target_arch = "wasm32")] pub fn reflect_layout(&self, _enforce_bevy_conventions: bool) -> Option { - panic!("Cannot reflect layout on wasm32"); + panic!("Cannot reflect layout on wasm32."); } } @@ -188,3 +227,60 @@ impl ShaderStages { } } } + +#[derive(Default)] +pub struct ShaderLoader; + +impl AssetLoader for ShaderLoader { + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result<(), anyhow::Error>> { + Box::pin(async move { + let ext = load_context.path().extension().unwrap().to_str().unwrap(); + + let shader = match ext { + "vert" => Shader::from_glsl(ShaderStage::Vertex, std::str::from_utf8(bytes)?), + "frag" => Shader::from_glsl(ShaderStage::Fragment, std::str::from_utf8(bytes)?), + _ => panic!("unhandled extension: {}", ext), + }; + + load_context.set_default_asset(LoadedAsset::new(shader)); + Ok(()) + }) + } + + fn extensions(&self) -> &[&str] { + &["vert", "frag"] + } +} + +pub fn shader_update_system( + mut shaders: ResMut>, + mut pipelines: ResMut>, + shader_events: Res>>, + mut shader_event_reader: Local>>, + mut pipeline_compiler: ResMut, + render_resource_context: Res>, +) { + for event in shader_event_reader.iter(&shader_events) { + match event { + AssetEvent::Modified { handle } => { + if let Err(e) = pipeline_compiler.update_shader( + handle, + &mut pipelines, + &mut shaders, + &**render_resource_context, + ) { + error!("Failed to update shader: {}", e); + } + } + // Creating shaders on the fly is unhandled since they + // have to exist already when assigned to a pipeline. If a + // shader is removed the pipeline keeps using its + // specialized version. Maybe this should be a warning? + AssetEvent::Created { .. } | AssetEvent::Removed { .. } => (), + } + } +} diff --git a/crates/bevy_render/src/shader/shader_defs.rs b/crates/bevy_render/src/shader/shader_defs.rs index b8ff477745acd..977872462732c 100644 --- a/crates/bevy_render/src/shader/shader_defs.rs +++ b/crates/bevy_render/src/shader/shader_defs.rs @@ -98,14 +98,16 @@ pub fn asset_shader_defs_system( T: ShaderDefs + Send + Sync + 'static, { for (asset_handle, mut render_pipelines) in query.iter_mut() { - let shader_defs = assets.get(asset_handle).unwrap(); - for shader_def in shader_defs.iter_shader_defs() { - for render_pipeline in render_pipelines.pipelines.iter_mut() { - render_pipeline - .specialization - .shader_specialization - .shader_defs - .insert(shader_def.to_string()); + if let Some(asset_handle) = assets.get(asset_handle) { + let shader_defs = asset_handle; + for shader_def in shader_defs.iter_shader_defs() { + for render_pipeline in render_pipelines.pipelines.iter_mut() { + render_pipeline + .specialization + .shader_specialization + .shader_defs + .insert(shader_def.to_string()); + } } } } diff --git a/crates/bevy_render/src/shader/shader_reflect.rs b/crates/bevy_render/src/shader/shader_reflect.rs index 2a35d0fb21b49..13bfbeae38386 100644 --- a/crates/bevy_render/src/shader/shader_reflect.rs +++ b/crates/bevy_render/src/shader/shader_reflect.rs @@ -84,7 +84,7 @@ impl ShaderLayout { entry_point: entry_point_name, } } - Err(err) => panic!("Failed to reflect shader layout: {:?}", err), + Err(err) => panic!("Failed to reflect shader layout: {:?}.", err), } } } @@ -108,7 +108,7 @@ fn reflect_dimension(type_description: &ReflectTypeDescription) -> TextureViewDi ReflectDimension::Type2d => TextureViewDimension::D2, ReflectDimension::Type3d => TextureViewDimension::D3, ReflectDimension::Cube => TextureViewDimension::Cube, - dimension => panic!("unsupported image dimension: {:?}", dimension), + dimension => panic!("Unsupported image dimension: {:?}.", dimension), } } @@ -142,7 +142,7 @@ fn reflect_binding( ), // TODO: detect comparison "true" case: https://github.com/gpuweb/gpuweb/issues/552 ReflectDescriptorType::Sampler => (&binding.name, BindType::Sampler { comparison: false }), - _ => panic!("unsupported bind type {:?}", binding.descriptor_type), + _ => panic!("Unsupported bind type {:?}.", binding.descriptor_type), }; let mut shader_stage = match shader_stage { @@ -199,7 +199,7 @@ fn reflect_uniform_numeric(type_description: &ReflectTypeDescription) -> Uniform match traits.numeric.scalar.signedness { 0 => NumberType::UInt, 1 => NumberType::Int, - signedness => panic!("unexpected signedness {}", signedness), + signedness => panic!("Unexpected signedness {}.", signedness), } } else if type_description .type_flags @@ -207,7 +207,7 @@ fn reflect_uniform_numeric(type_description: &ReflectTypeDescription) -> Uniform { NumberType::Float } else { - panic!("unexpected type flag {:?}", type_description.type_flags); + panic!("Unexpected type flag {:?}.", type_description.type_flags); }; // TODO: handle scalar width here @@ -252,7 +252,7 @@ fn reflect_vertex_format(type_description: &ReflectTypeDescription) -> VertexFor match traits.numeric.scalar.signedness { 0 => NumberType::UInt, 1 => NumberType::Int, - signedness => panic!("unexpected signedness {}", signedness), + signedness => panic!("Unexpected signedness {}.", signedness), } } else if type_description .type_flags @@ -260,7 +260,7 @@ fn reflect_vertex_format(type_description: &ReflectTypeDescription) -> VertexFor { NumberType::Float } else { - panic!("unexpected type flag {:?}", type_description.type_flags); + panic!("Unexpected type flag {:?}.", type_description.type_flags); }; let width = traits.numeric.scalar.width; @@ -328,7 +328,8 @@ mod tests { } "#, ) - .get_spirv_shader(None); + .get_spirv_shader(None) + .unwrap(); let layout = vertex_shader.reflect_layout(true).unwrap(); assert_eq!( diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index f3929892e64cd..da03340ef67ea 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -1,7 +1,6 @@ -use super::{Texture, TextureFormat}; +use super::{Extent3d, Texture, TextureDimension, TextureFormat}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; -use bevy_math::Vec2; use bevy_utils::BoxedFuture; /// Loads HDR textures as Texture assets @@ -37,7 +36,8 @@ impl AssetLoader for HdrTextureLoader { } let texture = Texture::new( - Vec2::new(info.width as f32, info.height as f32), + Extent3d::new(info.width, info.height, 1), + TextureDimension::D2, rgba_data, format, ); diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs index 2def1cf6389f0..f866a0751843b 100644 --- a/crates/bevy_render/src/texture/image_texture_loader.rs +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -1,7 +1,6 @@ -use super::{Texture, TextureFormat}; +use super::{Extent3d, Texture, TextureDimension, TextureFormat}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; -use bevy_math::Vec2; use bevy_utils::BoxedFuture; /// Loader for images that can be read by the `image` crate. @@ -10,6 +9,8 @@ use bevy_utils::BoxedFuture; #[derive(Clone, Default)] pub struct ImageTextureLoader; +const FILE_EXTENSIONS: &[&str] = &["png", "dds", "tga", "jpg", "jpeg"]; + impl AssetLoader for ImageTextureLoader { fn load<'a>( &'a self, @@ -24,16 +25,15 @@ impl AssetLoader for ImageTextureLoader { let ext = load_context.path().extension().unwrap().to_str().unwrap(); - // NOTE: If more formats are added they can be added here. - let img_format = if ext.eq_ignore_ascii_case("png") { - image::ImageFormat::Png - } else { - panic!( + let img_format = image::ImageFormat::from_extension(ext) + .ok_or_else(|| { + format!( "Unexpected image format {:?} for file {}, this is an error in `bevy_render`.", ext, load_context.path().display() ) - }; + }) + .unwrap(); // Load the image in the expected format. // Some formats like PNG allow for R or RG textures too, so the texture @@ -148,13 +148,18 @@ impl AssetLoader for ImageTextureLoader { } } - let texture = Texture::new(Vec2::new(width as f32, height as f32), data, format); + let texture = Texture::new( + Extent3d::new(width, height, 1), + TextureDimension::D2, + data, + format, + ); load_context.set_default_asset(LoadedAsset::new(texture)); Ok(()) }) } fn extensions(&self) -> &[&str] { - &["png"] + FILE_EXTENSIONS } } diff --git a/crates/bevy_render/src/texture/texture.rs b/crates/bevy_render/src/texture/texture.rs index 4a39f365b4c2f..89d07ddbdb3b2 100644 --- a/crates/bevy_render/src/texture/texture.rs +++ b/crates/bevy_render/src/texture/texture.rs @@ -1,12 +1,11 @@ -use super::{SamplerDescriptor, TextureDescriptor, TextureFormat}; +use super::{Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat}; use crate::renderer::{ RenderResource, RenderResourceContext, RenderResourceId, RenderResourceType, }; use bevy_app::prelude::{EventReader, Events}; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::{Res, ResMut}; -use bevy_math::Vec2; -use bevy_type_registry::TypeUuid; +use bevy_reflect::TypeUuid; use bevy_utils::HashSet; pub const TEXTURE_ASSET_INDEX: u64 = 0; @@ -16,8 +15,9 @@ pub const SAMPLER_ASSET_INDEX: u64 = 1; #[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"] pub struct Texture { pub data: Vec, - pub size: Vec2, + pub size: Extent3d, pub format: TextureFormat, + pub dimension: TextureDimension, pub sampler: SamplerDescriptor, } @@ -25,31 +25,48 @@ impl Default for Texture { fn default() -> Self { Texture { data: Default::default(), - size: Default::default(), + size: Extent3d { + width: 1, + height: 1, + depth: 1, + }, format: TextureFormat::Rgba8UnormSrgb, + dimension: TextureDimension::D2, sampler: Default::default(), } } } impl Texture { - pub fn new(size: Vec2, data: Vec, format: TextureFormat) -> Self { + pub fn new( + size: Extent3d, + dimension: TextureDimension, + data: Vec, + format: TextureFormat, + ) -> Self { debug_assert_eq!( - size.x as usize * size.y as usize * format.pixel_size(), + size.volume() * format.pixel_size(), data.len(), "Pixel data, size and format have to match", ); Self { data, size, + dimension, format, ..Default::default() } } - pub fn new_fill(size: Vec2, pixel: &[u8], format: TextureFormat) -> Self { + pub fn new_fill( + size: Extent3d, + dimension: TextureDimension, + pixel: &[u8], + format: TextureFormat, + ) -> Self { let mut value = Texture { format, + dimension, ..Default::default() }; value.resize(size); @@ -57,11 +74,11 @@ impl Texture { debug_assert_eq!( pixel.len() % format.pixel_size(), 0, - "Must not have incomplete pixel data" + "Must not have incomplete pixel data." ); debug_assert!( pixel.len() <= value.data.len(), - "Fill data must fit within pixel buffer" + "Fill data must fit within pixel buffer." ); for current_pixel in value.data.chunks_exact_mut(pixel.len()) { @@ -70,16 +87,42 @@ impl Texture { value } - pub fn aspect(&self) -> f32 { - self.size.y / self.size.x + pub fn aspect_2d(&self) -> f32 { + self.size.height as f32 / self.size.width as f32 } - pub fn resize(&mut self, size: Vec2) { + pub fn resize(&mut self, size: Extent3d) { self.size = size; - let width = size.x as usize; - let height = size.y as usize; self.data - .resize(width * height * self.format.pixel_size(), 0); + .resize(size.volume() * self.format.pixel_size(), 0); + } + + /// Changes the `size`, asserting that the total number of data elements (pixels) remains the same. + pub fn reinterpret_size(&mut self, new_size: Extent3d) { + assert!( + new_size.volume() == self.size.volume(), + "Incompatible sizes: old = {:?} new = {:?}", + self.size, + new_size + ); + + self.size = new_size; + } + + /// Takes a 2D texture containing vertically stacked images of the same size, and reinterprets it as a 2D array texture, + /// where each of the stacked images becomes one layer of the array. This is primarily for use with the `texture2DArray` + /// shader uniform type. + pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) { + // Must be a stacked image, and the height must be divisible by layers. + assert!(self.dimension == TextureDimension::D2); + assert!(self.size.depth == 1); + assert_eq!(self.size.height % layers, 0); + + self.reinterpret_size(Extent3d { + width: self.size.width, + height: self.size.height / layers, + depth: layers, + }); } pub fn texture_resource_system( diff --git a/crates/bevy_render/src/texture/texture_descriptor.rs b/crates/bevy_render/src/texture/texture_descriptor.rs index 82515d4f89176..15ab341ccda56 100644 --- a/crates/bevy_render/src/texture/texture_descriptor.rs +++ b/crates/bevy_render/src/texture/texture_descriptor.rs @@ -14,14 +14,10 @@ pub struct TextureDescriptor { impl From<&Texture> for TextureDescriptor { fn from(texture: &Texture) -> Self { TextureDescriptor { - size: Extent3d { - width: texture.size.x as u32, - height: texture.size.y as u32, - depth: 1, - }, + size: texture.size, mip_level_count: 1, sample_count: 1, - dimension: TextureDimension::D2, + dimension: texture.dimension, format: texture.format, usage: TextureUsage::SAMPLED | TextureUsage::COPY_DST, } diff --git a/crates/bevy_render/src/texture/texture_dimension.rs b/crates/bevy_render/src/texture/texture_dimension.rs index 77e8b42262b42..d230be2303c7e 100644 --- a/crates/bevy_render/src/texture/texture_dimension.rs +++ b/crates/bevy_render/src/texture/texture_dimension.rs @@ -1,5 +1,7 @@ // NOTE: These are currently just copies of the wgpu types, but they might change in the future +use bevy_math::Vec3; + /// Dimensions of a particular texture view. #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] pub enum TextureViewDimension { @@ -27,6 +29,24 @@ pub struct Extent3d { pub depth: u32, } +impl Extent3d { + pub fn new(width: u32, height: u32, depth: u32) -> Self { + Self { + width, + height, + depth, + } + } + + pub fn volume(&self) -> usize { + (self.width * self.height * self.depth) as usize + } + + pub fn as_vec3(&self) -> Vec3 { + Vec3::new(self.width as f32, self.height as f32, self.depth as f32) + } +} + /// Type of data shaders will read from a texture. #[derive(Copy, Hash, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum TextureComponentType { diff --git a/crates/bevy_scene/Cargo.toml b/crates/bevy_scene/Cargo.toml index 855a9b13e9bd4..a6d862f6e88fc 100644 --- a/crates/bevy_scene/Cargo.toml +++ b/crates/bevy_scene/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_scene" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,12 +14,12 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_property = { path = "../bevy_property", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other serde = { version = "1.0", features = ["derive"] } diff --git a/crates/bevy_scene/src/command.rs b/crates/bevy_scene/src/command.rs index 3c9c6ec2526ee..54efbd552a12c 100644 --- a/crates/bevy_scene/src/command.rs +++ b/crates/bevy_scene/src/command.rs @@ -1,5 +1,6 @@ use bevy_asset::Handle; -use bevy_ecs::{Command, Commands, Resources, World}; +use bevy_ecs::{Command, Commands, Entity, Resources, World}; +use bevy_transform::hierarchy::ChildBuilder; use crate::{Scene, SceneSpawner}; @@ -23,3 +24,29 @@ impl SpawnSceneCommands for Commands { self.add_command(SpawnScene { scene_handle }) } } + +pub struct SpawnSceneAsChild { + scene_handle: Handle, + parent: Entity, +} + +impl Command for SpawnSceneAsChild { + fn write(self: Box, _world: &mut World, resources: &mut Resources) { + let mut spawner = resources.get_mut::().unwrap(); + spawner.spawn_as_child(self.scene_handle, self.parent); + } +} + +pub trait SpawnSceneAsChildCommands { + fn spawn_scene(&mut self, scene: Handle) -> &mut Self; +} + +impl<'a> SpawnSceneAsChildCommands for ChildBuilder<'a> { + fn spawn_scene(&mut self, scene_handle: Handle) -> &mut Self { + self.add_command(SpawnSceneAsChild { + scene_handle, + parent: self.parent_entity(), + }); + self + } +} diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index f8ed4bbb0d55a..da6b4915ba406 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,14 +1,13 @@ use crate::{serde::SceneSerializer, Scene}; use anyhow::Result; use bevy_ecs::{EntityMap, Resources, World}; -use bevy_property::{DynamicProperties, PropertyTypeRegistry}; -use bevy_type_registry::{ComponentRegistry, TypeRegistry, TypeUuid}; +use bevy_reflect::{Reflect, ReflectComponent, ReflectMapEntities, TypeRegistryArc, TypeUuid}; use serde::Serialize; use thiserror::Error; #[derive(Error, Debug)] pub enum DynamicSceneToWorldError { - #[error("Scene contains an unregistered component.")] + #[error("scene contains an unregistered component")] UnregisteredComponent { type_name: String }, } @@ -20,16 +19,17 @@ pub struct DynamicScene { pub struct Entity { pub entity: u32, - pub components: Vec, + pub components: Vec>, } impl DynamicScene { - pub fn from_scene(scene: &Scene, component_registry: &ComponentRegistry) -> Self { - Self::from_world(&scene.world, component_registry) + pub fn from_scene(scene: &Scene, type_registry: &TypeRegistryArc) -> Self { + Self::from_world(&scene.world, type_registry) } - pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self { + pub fn from_world(world: &World, type_registry: &TypeRegistryArc) -> Self { let mut scene = DynamicScene::default(); + let type_registry = type_registry.read(); for archetype in world.archetypes() { let mut entities = Vec::new(); for (index, entity) in archetype.iter_entities().enumerate() { @@ -40,11 +40,15 @@ impl DynamicScene { }) } for type_info in archetype.types() { - if let Some(component_registration) = component_registry.get(&type_info.id()) { - let properties = - component_registration.get_component_properties(&archetype, index); - - entities[index].components.push(properties.to_dynamic()); + if let Some(registration) = type_registry.get(type_info.id()) { + if let Some(reflect_component) = registration.data::() { + // SAFE: the index comes directly from a currently live component + unsafe { + let component = + reflect_component.reflect_component(&archetype, index); + entities[index].components.push(component.clone_value()); + } + } } } } @@ -60,38 +64,45 @@ impl DynamicScene { world: &mut World, resources: &Resources, ) -> Result<(), DynamicSceneToWorldError> { - let type_registry = resources.get::().unwrap(); - let component_registry = type_registry.component.read(); + let type_registry = resources.get::().unwrap(); + let type_registry = type_registry.read(); let mut entity_map = EntityMap::default(); for scene_entity in self.entities.iter() { let new_entity = world.reserve_entity(); entity_map.insert(bevy_ecs::Entity::new(scene_entity.entity), new_entity); for component in scene_entity.components.iter() { - let component_registration = component_registry - .get_with_name(&component.type_name) + let registration = type_registry + .get_with_name(component.type_name()) .ok_or_else(|| DynamicSceneToWorldError::UnregisteredComponent { - type_name: component.type_name.to_string(), + type_name: component.type_name().to_string(), + })?; + let reflect_component = + registration.data::().ok_or_else(|| { + DynamicSceneToWorldError::UnregisteredComponent { + type_name: component.type_name().to_string(), + } })?; - if world.has_component_type(new_entity, component_registration.ty) { - component_registration.apply_property_to_entity(world, new_entity, component); + if world.has_component_type(new_entity, registration.type_id()) { + reflect_component.apply_component(world, new_entity, &**component); } else { - component_registration - .add_property_to_entity(world, resources, new_entity, component); + reflect_component.add_component(world, resources, new_entity, &**component); } } } - for component_registration in component_registry.iter() { - component_registration - .map_entities(world, &entity_map) - .unwrap(); + for registration in type_registry.iter() { + if let Some(map_entities_reflect) = registration.data::() { + map_entities_reflect + .map_entities(world, &entity_map) + .unwrap(); + } } Ok(()) } // TODO: move to AssetSaver when it is implemented - pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result { + pub fn serialize_ron(&self, registry: &TypeRegistryArc) -> Result { serialize_ron(SceneSerializer::new(self, registry)) } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 6fec4e5b6a660..349007910a808 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -5,6 +5,7 @@ mod scene_loader; mod scene_spawner; pub mod serde; +use bevy_ecs::{IntoSystem, SystemStage}; pub use command::*; pub use dynamic_scene::*; pub use scene::*; @@ -12,7 +13,9 @@ pub use scene_loader::*; pub use scene_spawner::*; pub mod prelude { - pub use crate::{DynamicScene, Scene, SceneSpawner, SpawnSceneCommands}; + pub use crate::{ + DynamicScene, Scene, SceneSpawner, SpawnSceneAsChildCommands, SpawnSceneCommands, + }; } use bevy_app::prelude::*; @@ -29,7 +32,7 @@ impl Plugin for ScenePlugin { .add_asset::() .init_asset_loader::() .init_resource::() - .add_stage_after(stage::EVENT, SCENE_STAGE) - .add_system_to_stage(SCENE_STAGE, scene_spawner_system); + .add_stage_after(stage::EVENT, SCENE_STAGE, SystemStage::parallel()) + .add_system_to_stage(SCENE_STAGE, scene_spawner_system.system()); } } diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 29b67211e8541..028ba75ad71fa 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,5 +1,5 @@ use bevy_ecs::World; -use bevy_type_registry::TypeUuid; +use bevy_reflect::TypeUuid; #[derive(Debug, TypeUuid)] #[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"] diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index 2daa54475fee2..4ff76330e12d9 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -2,23 +2,20 @@ use crate::serde::SceneDeserializer; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_ecs::{FromResources, Resources}; -use bevy_property::PropertyTypeRegistry; -use bevy_type_registry::TypeRegistry; +use bevy_reflect::TypeRegistryArc; use bevy_utils::BoxedFuture; -use parking_lot::RwLock; use serde::de::DeserializeSeed; -use std::sync::Arc; #[derive(Debug)] pub struct SceneLoader { - property_type_registry: Arc>, + type_registry: TypeRegistryArc, } impl FromResources for SceneLoader { fn from_resources(resources: &Resources) -> Self { - let type_registry = resources.get::().unwrap(); + let type_registry = resources.get::().unwrap(); SceneLoader { - property_type_registry: type_registry.property.clone(), + type_registry: (&*type_registry).clone(), } } } @@ -30,10 +27,9 @@ impl AssetLoader for SceneLoader { load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result<()>> { Box::pin(async move { - let registry = self.property_type_registry.read(); let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; let scene_deserializer = SceneDeserializer { - property_type_registry: ®istry, + type_registry: &*self.type_registry.read(), }; let scene = scene_deserializer.deserialize(&mut deserializer)?; load_context.set_default_asset(LoadedAsset::new(scene)); diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 48eb32e8071d2..428eda2920452 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -1,8 +1,9 @@ use crate::{DynamicScene, Scene}; use bevy_app::prelude::*; use bevy_asset::{AssetEvent, Assets, Handle}; -use bevy_ecs::{EntityMap, Resources, World}; -use bevy_type_registry::TypeRegistry; +use bevy_ecs::{Entity, EntityMap, Resources, World}; +use bevy_reflect::{ReflectComponent, ReflectMapEntities, TypeRegistryArc}; +use bevy_transform::prelude::Parent; use bevy_utils::HashMap; use thiserror::Error; use uuid::Uuid; @@ -28,17 +29,20 @@ pub struct SceneSpawner { spawned_instances: HashMap, scene_asset_event_reader: EventReader>, dynamic_scenes_to_spawn: Vec>, - scenes_to_spawn: Vec>, + scenes_to_spawn: Vec<(Handle, InstanceId)>, scenes_to_despawn: Vec>, + scenes_with_parent: Vec<(InstanceId, Entity)>, } #[derive(Error, Debug)] pub enum SceneSpawnError { - #[error("Scene contains an unregistered component.")] + #[error("scene contains the unregistered component `{type_name}`. consider adding `#[reflect(Component)]` to your type")] UnregisteredComponent { type_name: String }, - #[error("Scene does not exist. Perhaps it is still loading?")] + #[error("scene contains the unregistered type `{type_name}`. consider registering the type using `app.register_type::()`")] + UnregisteredType { type_name: String }, + #[error("scene does not exist")] NonExistentScene { handle: Handle }, - #[error("Scene does not exist. Perhaps it is still loading?")] + #[error("scene does not exist")] NonExistentRealScene { handle: Handle }, } @@ -48,7 +52,14 @@ impl SceneSpawner { } pub fn spawn(&mut self, scene_handle: Handle) { - self.scenes_to_spawn.push(scene_handle); + let instance_id = InstanceId::new(); + self.scenes_to_spawn.push((scene_handle, instance_id)); + } + + pub fn spawn_as_child(&mut self, scene_handle: Handle, parent: Entity) { + let instance_id = InstanceId::new(); + self.scenes_to_spawn.push((scene_handle, instance_id)); + self.scenes_with_parent.push((instance_id, parent)); } pub fn despawn(&mut self, scene_handle: Handle) { @@ -100,8 +111,8 @@ impl SceneSpawner { scene_handle: &Handle, instance_info: &mut InstanceInfo, ) -> Result<(), SceneSpawnError> { - let type_registry = resources.get::().unwrap(); - let component_registry = type_registry.component.read(); + let type_registry = resources.get::().unwrap(); + let type_registry = type_registry.read(); let scenes = resources.get::>().unwrap(); let scene = scenes .get(scene_handle) @@ -116,18 +127,23 @@ impl SceneSpawner { .entry(bevy_ecs::Entity::new(scene_entity.entity)) .or_insert_with(|| world.reserve_entity()); for component in scene_entity.components.iter() { - let component_registration = component_registry - .get_with_name(&component.type_name) - .ok_or(SceneSpawnError::UnregisteredComponent { - type_name: component.type_name.to_string(), + let registration = type_registry + .get_with_name(component.type_name()) + .ok_or_else(|| SceneSpawnError::UnregisteredType { + type_name: component.type_name().to_string(), })?; - if world.has_component_type(entity, component_registration.ty) { - if component.type_name != "Camera" { - component_registration.apply_property_to_entity(world, entity, component); + let reflect_component = + registration.data::().ok_or_else(|| { + SceneSpawnError::UnregisteredComponent { + type_name: component.type_name().to_string(), + } + })?; + if world.has_component_type(entity, registration.type_id()) { + if registration.short_name() != "Camera" { + reflect_component.apply_component(world, entity, &**component); } } else { - component_registration - .add_property_to_entity(world, resources, entity, component); + reflect_component.add_component(world, resources, entity, &**component); } } } @@ -140,12 +156,21 @@ impl SceneSpawner { resources: &Resources, scene_handle: Handle, ) -> Result<(), SceneSpawnError> { - let instance_id = InstanceId::new(); + self.spawn_sync_internal(world, resources, scene_handle, InstanceId::new()) + } + + fn spawn_sync_internal( + &mut self, + world: &mut World, + resources: &Resources, + scene_handle: Handle, + instance_id: InstanceId, + ) -> Result<(), SceneSpawnError> { let mut instance_info = InstanceInfo { entity_map: EntityMap::default(), }; - let type_registry = resources.get::().unwrap(); - let component_registry = type_registry.component.read(); + let type_registry = resources.get::().unwrap(); + let type_registry = type_registry.read(); let scenes = resources.get::>().unwrap(); let scene = scenes @@ -161,22 +186,33 @@ impl SceneSpawner { .entry(*scene_entity) .or_insert_with(|| world.reserve_entity()); for type_info in archetype.types() { - if let Some(component_registration) = component_registry.get(&type_info.id()) { - component_registration.component_copy( - &scene.world, - world, - resources, - *scene_entity, - entity, - ); - } + let registration = type_registry.get(type_info.id()).ok_or_else(|| { + SceneSpawnError::UnregisteredType { + type_name: type_info.type_name().to_string(), + } + })?; + let reflect_component = + registration.data::().ok_or_else(|| { + SceneSpawnError::UnregisteredComponent { + type_name: registration.name().to_string(), + } + })?; + reflect_component.copy_component( + &scene.world, + world, + resources, + *scene_entity, + entity, + ); } } } - for component_registration in component_registry.iter() { - component_registration - .map_entities(world, &instance_info.entity_map) - .unwrap(); + for registration in type_registry.iter() { + if let Some(map_entities_reflect) = registration.data::() { + map_entities_reflect + .map_entities(world, &instance_info.entity_map) + .unwrap(); + } } self.spawned_instances.insert(instance_id, instance_info); let spawned = self @@ -238,11 +274,11 @@ impl SceneSpawner { let scenes_to_spawn = std::mem::take(&mut self.scenes_to_spawn); - for scene_handle in scenes_to_spawn { - match self.spawn_sync(world, resources, scene_handle) { + for (scene_handle, instance_id) in scenes_to_spawn { + match self.spawn_sync_internal(world, resources, scene_handle, instance_id) { Ok(_) => {} Err(SceneSpawnError::NonExistentRealScene { handle }) => { - self.scenes_to_spawn.push(handle) + self.scenes_to_spawn.push((handle, instance_id)) } Err(err) => return Err(err), } @@ -250,6 +286,24 @@ impl SceneSpawner { Ok(()) } + + pub(crate) fn set_scene_instance_parent_sync(&mut self, world: &mut World) { + let scenes_with_parent = std::mem::take(&mut self.scenes_with_parent); + + for (instance_id, parent) in scenes_with_parent { + if let Some(instance) = self.spawned_instances.get(&instance_id) { + for entity in instance.entity_map.values() { + if let Err(bevy_ecs::ComponentError::MissingComponent(_)) = + world.get::(entity) + { + let _ = world.insert_one(entity, Parent(parent)); + } + } + } else { + self.scenes_with_parent.push((instance_id, parent)); + } + } + } } pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) { @@ -269,8 +323,11 @@ pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) { } scene_spawner.despawn_queued_scenes(world).unwrap(); - scene_spawner.spawn_queued_scenes(world, resources).unwrap(); + scene_spawner + .spawn_queued_scenes(world, resources) + .unwrap_or_else(|err| panic!("{}", err)); scene_spawner .update_spawned_scenes(world, resources, &updated_spawned_scenes) .unwrap(); + scene_spawner.set_scene_instance_parent_sync(world); } diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 8a9a47c3bf42a..46f7f03f9b39e 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -1,8 +1,8 @@ use crate::{DynamicScene, Entity}; use anyhow::Result; -use bevy_property::{ - property_serde::{DynamicPropertiesDeserializer, DynamicPropertiesSerializer}, - DynamicProperties, PropertyTypeRegistry, +use bevy_reflect::{ + serde::{ReflectDeserializer, ReflectSerializer}, + Reflect, TypeRegistry, TypeRegistryArc, }; use serde::{ de::{DeserializeSeed, Error, MapAccess, SeqAccess, Visitor}, @@ -12,11 +12,11 @@ use serde::{ pub struct SceneSerializer<'a> { pub scene: &'a DynamicScene, - pub registry: &'a PropertyTypeRegistry, + pub registry: &'a TypeRegistryArc, } impl<'a> SceneSerializer<'a> { - pub fn new(scene: &'a DynamicScene, registry: &'a PropertyTypeRegistry) -> Self { + pub fn new(scene: &'a DynamicScene, registry: &'a TypeRegistryArc) -> Self { SceneSerializer { scene, registry } } } @@ -39,7 +39,7 @@ impl<'a> Serialize for SceneSerializer<'a> { pub struct EntitySerializer<'a> { pub entity: &'a Entity, - pub registry: &'a PropertyTypeRegistry, + pub registry: &'a TypeRegistryArc, } impl<'a> Serialize for EntitySerializer<'a> { @@ -61,8 +61,8 @@ impl<'a> Serialize for EntitySerializer<'a> { } pub struct ComponentsSerializer<'a> { - pub components: &'a [DynamicProperties], - pub registry: &'a PropertyTypeRegistry, + pub components: &'a [Box], + pub registry: &'a TypeRegistryArc, } impl<'a> Serialize for ComponentsSerializer<'a> { @@ -71,10 +71,10 @@ impl<'a> Serialize for ComponentsSerializer<'a> { S: serde::Serializer, { let mut state = serializer.serialize_seq(Some(self.components.len()))?; - for dynamic_properties in self.components.iter() { - state.serialize_element(&DynamicPropertiesSerializer::new( - dynamic_properties, - self.registry, + for component in self.components.iter() { + state.serialize_element(&ReflectSerializer::new( + &**component, + &*self.registry.read(), ))?; } state.end() @@ -82,7 +82,7 @@ impl<'a> Serialize for ComponentsSerializer<'a> { } pub struct SceneDeserializer<'a> { - pub property_type_registry: &'a PropertyTypeRegistry, + pub type_registry: &'a TypeRegistry, } impl<'a, 'de> DeserializeSeed<'de> for SceneDeserializer<'a> { @@ -94,14 +94,14 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneDeserializer<'a> { { Ok(DynamicScene { entities: deserializer.deserialize_seq(SceneEntitySeqVisitor { - property_type_registry: self.property_type_registry, + type_registry: self.type_registry, })?, }) } } struct SceneEntitySeqVisitor<'a> { - pub property_type_registry: &'a PropertyTypeRegistry, + pub type_registry: &'a TypeRegistry, } impl<'a, 'de> Visitor<'de> for SceneEntitySeqVisitor<'a> { @@ -117,7 +117,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntitySeqVisitor<'a> { { let mut entities = Vec::new(); while let Some(entity) = seq.next_element_seed(SceneEntityDeserializer { - property_type_registry: self.property_type_registry, + type_registry: self.type_registry, })? { entities.push(entity); } @@ -127,7 +127,7 @@ impl<'a, 'de> Visitor<'de> for SceneEntitySeqVisitor<'a> { } pub struct SceneEntityDeserializer<'a> { - pub property_type_registry: &'a PropertyTypeRegistry, + pub type_registry: &'a TypeRegistry, } impl<'a, 'de> DeserializeSeed<'de> for SceneEntityDeserializer<'a> { @@ -141,7 +141,7 @@ impl<'a, 'de> DeserializeSeed<'de> for SceneEntityDeserializer<'a> { ENTITY_STRUCT, &[ENTITY_FIELD_ENTITY, ENTITY_FIELD_COMPONENTS], SceneEntityVisitor { - registry: self.property_type_registry, + registry: self.type_registry, }, ) } @@ -158,9 +158,8 @@ pub const ENTITY_STRUCT: &str = "Entity"; pub const ENTITY_FIELD_ENTITY: &str = "entity"; pub const ENTITY_FIELD_COMPONENTS: &str = "components"; -#[derive(Debug)] struct SceneEntityVisitor<'a> { - pub registry: &'a PropertyTypeRegistry, + pub registry: &'a TypeRegistry, } impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { @@ -211,11 +210,11 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { } pub struct ComponentVecDeserializer<'a> { - pub registry: &'a PropertyTypeRegistry, + pub registry: &'a TypeRegistry, } impl<'a, 'de> DeserializeSeed<'de> for ComponentVecDeserializer<'a> { - type Value = Vec; + type Value = Vec>; fn deserialize(self, deserializer: D) -> Result where @@ -228,11 +227,11 @@ impl<'a, 'de> DeserializeSeed<'de> for ComponentVecDeserializer<'a> { } struct ComponentSeqVisitor<'a> { - pub registry: &'a PropertyTypeRegistry, + pub registry: &'a TypeRegistry, } impl<'a, 'de> Visitor<'de> for ComponentSeqVisitor<'a> { - type Value = Vec; + type Value = Vec>; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("list of components") @@ -243,9 +242,7 @@ impl<'a, 'de> Visitor<'de> for ComponentSeqVisitor<'a> { A: SeqAccess<'de>, { let mut dynamic_properties = Vec::new(); - while let Some(entity) = - seq.next_element_seed(DynamicPropertiesDeserializer::new(self.registry))? - { + while let Some(entity) = seq.next_element_seed(ReflectDeserializer::new(self.registry))? { dynamic_properties.push(entity); } diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 58b5a5ac4b42b..57f35d7ec3273 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_sprite" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,17 +14,18 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_core = { path = "../bevy_core", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_render = { path = "../bevy_render", version = "0.3.0" } -bevy_transform = { path = "../bevy_transform", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_core = { path = "../bevy_core", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_render = { path = "../bevy_render", version = "0.4.0" } +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other rectangle-pack = "0.2" thiserror = "1.0" guillotiere = "0.6.0" +serde = { version = "1", features = ["derive"] } diff --git a/crates/bevy_sprite/src/collide_aabb.rs b/crates/bevy_sprite/src/collide_aabb.rs index 489fdfd2c931c..096ed84ed053e 100644 --- a/crates/bevy_sprite/src/collide_aabb.rs +++ b/crates/bevy_sprite/src/collide_aabb.rs @@ -42,7 +42,7 @@ pub fn collide(a_pos: Vec3, a_size: Vec2, b_pos: Vec3, b_size: Vec2) -> Option { - if y_depth < x_depth { + if y_depth.abs() < x_depth.abs() { Some(y_collision) } else { Some(x_collision) diff --git a/crates/bevy_sprite/src/color_material.rs b/crates/bevy_sprite/src/color_material.rs index 28a618b03b2fb..e82e75f21e54e 100644 --- a/crates/bevy_sprite/src/color_material.rs +++ b/crates/bevy_sprite/src/color_material.rs @@ -1,6 +1,6 @@ use bevy_asset::{self, Handle}; +use bevy_reflect::TypeUuid; use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture}; -use bevy_type_registry::TypeUuid; #[derive(Debug, RenderResources, ShaderDefs, TypeUuid)] #[uuid = "506cff92-a9f3-4543-862d-6851c7fdfc99"] diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index be5cdd2c27c95..8fc4827523c77 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -24,8 +24,8 @@ impl DynamicTextureAtlasBuilder { texture: &Texture, ) -> Option { let allocation = self.atlas_allocator.allocate(size2( - texture.size.x as i32 + self.padding, - texture.size.y as i32 + self.padding, + texture.size.width as i32 + self.padding, + texture.size.height as i32 + self.padding, )); if let Some(allocation) = allocation { let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap(); @@ -72,7 +72,7 @@ impl DynamicTextureAtlasBuilder { let mut rect = allocation.rectangle; rect.max.x -= self.padding; rect.max.y -= self.padding; - let atlas_width = atlas_texture.size.x as usize; + let atlas_width = atlas_texture.size.width as usize; let rect_width = rect.width() as usize; let format_size = atlas_texture.format.pixel_size(); diff --git a/crates/bevy_sprite/src/entity.rs b/crates/bevy_sprite/src/entity.rs index ace55688b503a..9136cce5ed598 100644 --- a/crates/bevy_sprite/src/entity.rs +++ b/crates/bevy_sprite/src/entity.rs @@ -7,7 +7,7 @@ use bevy_ecs::Bundle; use bevy_render::{ mesh::Mesh, pipeline::{RenderPipeline, RenderPipelines}, - prelude::Draw, + prelude::{Draw, Visible}, render_graph::base::MainPass, }; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -19,6 +19,7 @@ pub struct SpriteBundle { pub material: Handle, pub main_pass: MainPass, pub draw: Draw, + pub visible: Visible, pub render_pipelines: RenderPipelines, pub transform: Transform, pub global_transform: GlobalTransform, @@ -27,16 +28,17 @@ pub struct SpriteBundle { impl Default for SpriteBundle { fn default() -> Self { Self { - mesh: QUAD_HANDLE, + mesh: QUAD_HANDLE.typed(), render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - SPRITE_PIPELINE_HANDLE, + SPRITE_PIPELINE_HANDLE.typed(), )]), - draw: Draw { + visible: Visible { is_transparent: true, ..Default::default() }, - sprite: Default::default(), main_pass: MainPass, + draw: Default::default(), + sprite: Default::default(), material: Default::default(), transform: Default::default(), global_transform: Default::default(), @@ -54,6 +56,7 @@ pub struct SpriteSheetBundle { pub texture_atlas: Handle, /// Data pertaining to how the sprite is drawn on the screen pub draw: Draw, + pub visible: Visible, pub render_pipelines: RenderPipelines, pub main_pass: MainPass, pub mesh: Handle, // TODO: maybe abstract this out @@ -65,14 +68,15 @@ impl Default for SpriteSheetBundle { fn default() -> Self { Self { render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - SPRITE_SHEET_PIPELINE_HANDLE, + SPRITE_SHEET_PIPELINE_HANDLE.typed(), )]), - draw: Draw { + visible: Visible { is_transparent: true, ..Default::default() }, - mesh: QUAD_HANDLE, main_pass: MainPass, + mesh: QUAD_HANDLE.typed(), + draw: Default::default(), sprite: Default::default(), texture_atlas: Default::default(), transform: Default::default(), diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 2bad06938efb0..bc574243a4bf5 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -9,6 +9,7 @@ mod sprite; mod texture_atlas; mod texture_atlas_builder; +use bevy_ecs::IntoSystem; pub use color_material::*; pub use dynamic_texture_atlas_builder::*; pub use rect::*; @@ -25,29 +26,31 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_asset::{AddAsset, Assets, Handle}; +use bevy_asset::{AddAsset, Assets, Handle, HandleUntyped}; use bevy_math::Vec2; +use bevy_reflect::{RegisterTypeBuilder, TypeUuid}; use bevy_render::{ mesh::{shape, Mesh}, render_graph::RenderGraph, shader::asset_shader_defs_system, }; -use bevy_type_registry::TypeUuid; use sprite::sprite_system; #[derive(Default)] pub struct SpritePlugin; -pub const QUAD_HANDLE: Handle = Handle::weak_from_u64(Mesh::TYPE_UUID, 14240461981130137526); +pub const QUAD_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Mesh::TYPE_UUID, 14240461981130137526); impl Plugin for SpritePlugin { fn build(&self, app: &mut AppBuilder) { app.add_asset::() .add_asset::() - .add_system_to_stage(stage::POST_UPDATE, sprite_system) + .register_type::() + .add_system_to_stage(stage::POST_UPDATE, sprite_system.system()) .add_system_to_stage( stage::POST_UPDATE, - asset_shader_defs_system::, + asset_shader_defs_system::.system(), ); let resources = app.resources_mut(); diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index c4cd96a5d3dad..221df7cb95091 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,6 +1,7 @@ use crate::{ColorMaterial, Sprite, TextureAtlas, TextureAtlasSprite}; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Assets, HandleUntyped}; use bevy_ecs::Resources; +use bevy_reflect::TypeUuid; use bevy_render::{ pipeline::{ BlendDescriptor, BlendFactor, BlendOperation, ColorStateDescriptor, ColorWrite, @@ -11,13 +12,12 @@ use bevy_render::{ shader::{Shader, ShaderStage, ShaderStages}, texture::TextureFormat, }; -use bevy_type_registry::TypeUuid; -pub const SPRITE_PIPELINE_HANDLE: Handle = - Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 2785347840338765446); +pub const SPRITE_PIPELINE_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 2785347840338765446); -pub const SPRITE_SHEET_PIPELINE_HANDLE: Handle = - Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 9016885805180281612); +pub const SPRITE_SHEET_PIPELINE_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 9016885805180281612); pub fn build_sprite_sheet_pipeline(shaders: &mut Assets) -> PipelineDescriptor { PipelineDescriptor { diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 9efc960dc6a9d..b5c3c836d5235 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -2,10 +2,11 @@ use crate::ColorMaterial; use bevy_asset::{Assets, Handle}; use bevy_ecs::{Query, Res}; use bevy_math::Vec2; +use bevy_reflect::{Reflect, ReflectDeserialize, TypeUuid}; use bevy_render::{renderer::RenderResources, texture::Texture}; -use bevy_type_registry::TypeUuid; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Default, RenderResources, TypeUuid)] +#[derive(Debug, Default, RenderResources, TypeUuid, Reflect)] #[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"] pub struct Sprite { pub size: Vec2, @@ -14,7 +15,8 @@ pub struct Sprite { } /// Determines how `Sprite` resize should be handled -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum SpriteResizeMode { Manual, Automatic, @@ -48,7 +50,11 @@ pub fn sprite_system( let material = materials.get(handle).unwrap(); if let Some(ref texture_handle) = material.texture { if let Some(texture) = textures.get(texture_handle) { - sprite.size = texture.size; + let texture_size = texture.size.as_vec3().truncate(); + // only set sprite size if it has changed (this check prevents change detection from triggering) + if sprite.size != texture_size { + sprite.size = texture_size; + } } } } diff --git a/crates/bevy_sprite/src/texture_atlas.rs b/crates/bevy_sprite/src/texture_atlas.rs index 8336c8d1ce11a..c78d398432fd3 100644 --- a/crates/bevy_sprite/src/texture_atlas.rs +++ b/crates/bevy_sprite/src/texture_atlas.rs @@ -2,12 +2,12 @@ use crate::Rect; use bevy_asset::Handle; use bevy_core::Byteable; use bevy_math::Vec2; +use bevy_reflect::TypeUuid; use bevy_render::{ color::Color, renderer::{RenderResource, RenderResources}, texture::Texture, }; -use bevy_type_registry::TypeUuid; use bevy_utils::HashMap; /// An atlas containing multiple textures (like a spritesheet or a tilemap) diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 262667a8d65ed..2be6d0058e04d 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -1,7 +1,7 @@ use crate::{Rect, TextureAtlas}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; -use bevy_render::texture::{Texture, TextureFormat}; +use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; use bevy_utils::HashMap; use rectangle_pack::{ contains_smallest_box, pack_rects, volume_heuristic, GroupedRectsToPlace, PackedLocation, @@ -9,45 +9,60 @@ use rectangle_pack::{ }; use thiserror::Error; +#[derive(Debug, Error)] +pub enum TextureAtlasBuilderError { + #[error("could not pack textures into an atlas within the given bounds")] + NotEnoughSpace, +} + #[derive(Debug)] +/// A builder which is used to create a texture atlas from many individual +/// sprites. pub struct TextureAtlasBuilder { - pub textures: Vec>, - pub rects_to_place: GroupedRectsToPlace>, - pub initial_size: Vec2, - pub max_size: Vec2, + /// The grouped rects which must be placed with a key value pair of a + /// texture handle to an index. + rects_to_place: GroupedRectsToPlace>, + /// The initial atlas size in pixels. + initial_size: Vec2, + /// The absolute maximum size of the texture atlas in pixels. + max_size: Vec2, } impl Default for TextureAtlasBuilder { fn default() -> Self { - Self::new(Vec2::new(256., 256.), Vec2::new(2048., 2048.)) + Self { + rects_to_place: GroupedRectsToPlace::new(), + initial_size: Vec2::new(256., 256.), + max_size: Vec2::new(2048., 2048.), + } } } -#[derive(Debug, Error)] -pub enum RectanglePackError { - #[error("Could not pack textures into an atlas within the given bounds")] - NotEnoughSpace, -} +pub type TextureAtlasBuilderResult = Result; impl TextureAtlasBuilder { - pub fn new(initial_size: Vec2, max_size: Vec2) -> Self { - Self { - textures: Default::default(), - rects_to_place: GroupedRectsToPlace::new(), - initial_size, - max_size, - } + /// Sets the initial size of the atlas in pixels. + pub fn initial_size(mut self, size: Vec2) -> Self { + self.initial_size = size; + self + } + + /// Sets the max size of the atlas in pixels. + pub fn max_size(mut self, size: Vec2) -> Self { + self.max_size = size; + self } + /// Adds a texture to be copied to the texture atlas. pub fn add_texture(&mut self, texture_handle: Handle, texture: &Texture) { self.rects_to_place.push_rect( texture_handle, None, - RectToInsert::new(texture.size.x as u32, texture.size.y as u32, 1), + RectToInsert::new(texture.size.width, texture.size.height, 1), ) } - fn place_texture( + fn copy_texture( &mut self, atlas_texture: &mut Texture, texture: &Texture, @@ -57,7 +72,7 @@ impl TextureAtlasBuilder { let rect_height = packed_location.height() as usize; let rect_x = packed_location.x() as usize; let rect_y = packed_location.y() as usize; - let atlas_width = atlas_texture.size.x as usize; + let atlas_width = atlas_texture.size.width as usize; let format_size = atlas_texture.format.pixel_size(); for (texture_y, bound_y) in (rect_y..rect_y + rect_height).enumerate() { @@ -70,10 +85,21 @@ impl TextureAtlasBuilder { } } + /// Consumes the builder and returns a result with a new texture atlas. + /// + /// Internally it copies all rectangles from the textures and copies them + /// into a new texture which the texture atlas will use. It is not useful to + /// hold a strong handle to the texture afterwards else it will exist twice + /// in memory. + /// + /// # Errors + /// + /// If there is not enough space in the atlas texture, an error will + /// be returned. It is then recommended to make a larger sprite sheet. pub fn finish( mut self, textures: &mut Assets, - ) -> Result { + ) -> Result { let initial_width = self.initial_size.x as u32; let initial_height = self.initial_size.y as u32; let max_width = self.max_size.x as u32; @@ -93,7 +119,6 @@ impl TextureAtlasBuilder { let mut target_bins = std::collections::BTreeMap::new(); target_bins.insert(0, TargetBin::new(current_width, current_height, 1)); - rect_placements = match pack_rects( &self.rects_to_place, target_bins, @@ -102,7 +127,8 @@ impl TextureAtlasBuilder { ) { Ok(rect_placements) => { atlas_texture = Texture::new_fill( - Vec2::new(current_width as f32, current_height as f32), + Extent3d::new(current_width, current_height, 1), + TextureDimension::D2, &[0, 0, 0, 0], TextureFormat::Rgba8UnormSrgb, ); @@ -120,7 +146,7 @@ impl TextureAtlasBuilder { } } - let rect_placements = rect_placements.ok_or(RectanglePackError::NotEnoughSpace)?; + let rect_placements = rect_placements.ok_or(TextureAtlasBuilderError::NotEnoughSpace)?; let mut texture_rects = Vec::with_capacity(rect_placements.packed_locations().len()); let mut texture_handles = HashMap::default(); @@ -134,10 +160,10 @@ impl TextureAtlasBuilder { ); texture_handles.insert(texture_handle.clone_weak(), texture_rects.len()); texture_rects.push(Rect { min, max }); - self.place_texture(&mut atlas_texture, texture, packed_location); + self.copy_texture(&mut atlas_texture, texture, packed_location); } Ok(TextureAtlas { - size: atlas_texture.size, + size: atlas_texture.size.as_vec3().truncate(), texture: textures.add(atlas_texture), textures: texture_rects, texture_handles: Some(texture_handles), diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index 344ec184a2788..a11a6f1db5acf 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_tasks" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", diff --git a/crates/bevy_tasks/src/task_pool.rs b/crates/bevy_tasks/src/task_pool.rs index 7185dfb8614bb..febd8351c9a29 100644 --- a/crates/bevy_tasks/src/task_pool.rs +++ b/crates/bevy_tasks/src/task_pool.rs @@ -69,10 +69,12 @@ impl Drop for TaskPoolInner { fn drop(&mut self) { self.shutdown_tx.close(); + let panicking = thread::panicking(); for join_handle in self.threads.drain(..) { - join_handle - .join() - .expect("task thread panicked while executing"); + let res = join_handle.join(); + if !panicking { + res.expect("Task thread panicked while executing."); + } } } } @@ -132,7 +134,7 @@ impl TaskPool { // Use unwrap_err because we expect a Closed error future::block_on(shutdown_future).unwrap_err(); }) - .expect("failed to spawn thread") + .expect("Failed to spawn thread.") }) .collect(); @@ -167,34 +169,51 @@ impl TaskPool { let executor: &async_executor::Executor = &*self.executor; let executor: &'scope async_executor::Executor = unsafe { mem::transmute(executor) }; - let fut = async move { - let mut scope = Scope { - executor, - spawned: Vec::new(), - }; - - f(&mut scope); - - let mut results = Vec::with_capacity(scope.spawned.len()); - for task in scope.spawned { - results.push(task.await); - } - - results + let mut scope = Scope { + executor, + spawned: Vec::new(), }; - // Pin the future on the stack. - pin!(fut); + f(&mut scope); + + if scope.spawned.is_empty() { + Vec::default() + } else if scope.spawned.len() == 1 { + vec![future::block_on(&mut scope.spawned[0])] + } else { + let fut = async move { + let mut results = Vec::with_capacity(scope.spawned.len()); + for task in scope.spawned { + results.push(task.await); + } - // SAFETY: This function blocks until all futures complete, so we do not read/write the - // data from futures outside of the 'scope lifetime. However, rust has no way of knowing - // this so we must convert to 'static here to appease the compiler as it is unable to - // validate safety. - let fut: Pin<&mut (dyn Future> + Send)> = fut; - let fut: Pin<&'static mut (dyn Future> + Send + 'static)> = - unsafe { mem::transmute(fut) }; + results + }; - future::block_on(self.executor.spawn(fut)) + // Pin the future on the stack. + pin!(fut); + + // SAFETY: This function blocks until all futures complete, so we do not read/write the + // data from futures outside of the 'scope lifetime. However, rust has no way of knowing + // this so we must convert to 'static here to appease the compiler as it is unable to + // validate safety. + let fut: Pin<&mut (dyn Future> + Send)> = fut; + let fut: Pin<&'static mut (dyn Future> + Send + 'static)> = + unsafe { mem::transmute(fut) }; + + // The thread that calls scope() will participate in driving tasks in the pool forward + // until the tasks that are spawned by this scope() call complete. (If the caller of scope() + // happens to be a thread in this thread pool, and we only have one thread in the pool, then + // simply calling future::block_on(spawned) would deadlock.) + let mut spawned = self.executor.spawn(fut); + loop { + if let Some(result) = future::block_on(future::poll_once(&mut spawned)) { + break result; + } + + self.executor.try_tick(); + } + } } /// Spawns a static future onto the thread pool. The returned Task is a future. It can also be diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index e222578241872..81e93a34a27f1 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_text" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,15 +14,15 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_core = { path = "../bevy_core", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_render = { path = "../bevy_render", version = "0.3.0" } -bevy_sprite = { path = "../bevy_sprite", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_core = { path = "../bevy_core", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_render = { path = "../bevy_render", version = "0.4.0" } +bevy_sprite = { path = "../bevy_sprite", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other anyhow = "1.0" diff --git a/crates/bevy_text/src/draw.rs b/crates/bevy_text/src/draw.rs index d06212c7b5b1a..c92aa889d20d9 100644 --- a/crates/bevy_text/src/draw.rs +++ b/crates/bevy_text/src/draw.rs @@ -3,12 +3,10 @@ use bevy_render::{ color::Color, draw::{Draw, DrawContext, DrawError, Drawable}, mesh, + mesh::Mesh, pipeline::{PipelineSpecialization, VertexBufferDescriptor}, prelude::Msaa, - renderer::{ - AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings, - RenderResourceId, - }, + renderer::{BindGroup, RenderResourceBindings, RenderResourceId}, }; use bevy_sprite::TextureAtlasSprite; use glyph_brush_layout::{HorizontalAlign, VerticalAlign}; @@ -49,7 +47,6 @@ impl Default for TextStyle { pub struct DrawableText<'a> { pub render_resource_bindings: &'a mut RenderResourceBindings, - pub asset_render_resource_bindings: &'a mut AssetRenderResourceBindings, pub position: Vec3, pub style: &'a TextStyle, pub text_glyphs: &'a Vec, @@ -61,7 +58,7 @@ impl<'a> Drawable for DrawableText<'a> { fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> { context.set_pipeline( draw, - &bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE, + &bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE.typed(), &PipelineSpecialization { sample_count: self.msaa.samples, vertex_buffer_descriptor: self.font_quad_vertex_descriptor.clone(), @@ -72,22 +69,28 @@ impl<'a> Drawable for DrawableText<'a> { let render_resource_context = &**context.render_resource_context; if let Some(RenderResourceId::Buffer(vertex_attribute_buffer_id)) = render_resource_context - .get_asset_resource(&bevy_sprite::QUAD_HANDLE, mesh::VERTEX_ATTRIBUTE_BUFFER_ID) + .get_asset_resource( + &bevy_sprite::QUAD_HANDLE.typed::(), + mesh::VERTEX_ATTRIBUTE_BUFFER_ID, + ) { draw.set_vertex_buffer(0, vertex_attribute_buffer_id, 0); } else { - println!("could not find vertex buffer for bevy_sprite::QUAD_HANDLE") + println!("Could not find vertex buffer for `bevy_sprite::QUAD_HANDLE`.") } let mut indices = 0..0; if let Some(RenderResourceId::Buffer(quad_index_buffer)) = render_resource_context - .get_asset_resource(&bevy_sprite::QUAD_HANDLE, mesh::INDEX_BUFFER_ASSET_INDEX) + .get_asset_resource( + &bevy_sprite::QUAD_HANDLE.typed::(), + mesh::INDEX_BUFFER_ASSET_INDEX, + ) { draw.set_index_buffer(quad_index_buffer, 0); if let Some(buffer_info) = render_resource_context.get_buffer_info(quad_index_buffer) { indices = 0..(buffer_info.size / 4) as u32; } else { - panic!("expected buffer type"); + panic!("Expected buffer type."); } } @@ -95,11 +98,7 @@ impl<'a> Drawable for DrawableText<'a> { context.set_bind_groups_from_bindings(draw, &mut [self.render_resource_bindings])?; for tv in self.text_glyphs { - let atlas_render_resource_bindings = self - .asset_render_resource_bindings - .get_mut(&tv.atlas_info.texture_atlas) - .unwrap(); - context.set_bind_groups_from_bindings(draw, &mut [atlas_render_resource_bindings])?; + context.set_asset_bind_groups(draw, &tv.atlas_info.texture_atlas)?; let sprite = TextureAtlasSprite { index: tv.atlas_info.glyph_index, @@ -108,14 +107,8 @@ impl<'a> Drawable for DrawableText<'a> { let transform = Mat4::from_translation(self.position + tv.position.extend(0.)); - let transform_buffer = context - .shared_buffers - .get_buffer(&transform, BufferUsage::UNIFORM) - .unwrap(); - let sprite_buffer = context - .shared_buffers - .get_buffer(&sprite, BufferUsage::UNIFORM) - .unwrap(); + let transform_buffer = context.get_uniform_buffer(&transform).unwrap(); + let sprite_buffer = context.get_uniform_buffer(&sprite).unwrap(); let sprite_bind_group = BindGroup::build() .add_binding(0, transform_buffer) .add_binding(1, sprite_buffer) diff --git a/crates/bevy_text/src/error.rs b/crates/bevy_text/src/error.rs index 10967f0e32cc0..1bb7cf1253581 100644 --- a/crates/bevy_text/src/error.rs +++ b/crates/bevy_text/src/error.rs @@ -3,8 +3,8 @@ use thiserror::Error; #[derive(Debug, PartialEq, Eq, Error)] pub enum TextError { - #[error("Font not found")] + #[error("font not found")] NoSuchFont, - #[error("Failed to add glyph to newly-created atlas {0:?}")] + #[error("failed to add glyph to newly-created atlas {0:?}")] FailedToAddGlyph(GlyphId), } diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index f701bff87c413..a9400632b634c 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -1,10 +1,9 @@ use ab_glyph::{FontArc, FontVec, InvalidFont, OutlinedGlyph}; -use bevy_math::Vec2; +use bevy_reflect::TypeUuid; use bevy_render::{ color::Color, - texture::{Texture, TextureFormat}, + texture::{Extent3d, Texture, TextureDimension, TextureFormat}, }; -use bevy_type_registry::TypeUuid; #[derive(Debug, TypeUuid)] #[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"] @@ -36,7 +35,8 @@ impl Font { (color.b() * 255.0) as u8, ]; Texture::new( - Vec2::new(width as f32, height as f32), + Extent3d::new(width as u32, height as u32, 1), + TextureDimension::D2, alpha .iter() .map(|a| { diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index bd6d7c6a16453..0120d70ea3682 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -1,7 +1,7 @@ use ab_glyph::GlyphId; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; -use bevy_render::texture::{Texture, TextureFormat}; +use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas}; use bevy_utils::HashMap; @@ -18,7 +18,8 @@ impl FontAtlas { size: Vec2, ) -> FontAtlas { let atlas_texture = textures.add(Texture::new_fill( - size, + Extent3d::new(size.x as u32, size.y as u32, 1), + TextureDimension::D2, &[0, 0, 0, 0], TextureFormat::Rgba8UnormSrgb, )); diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index decf5dd52491f..afdc7652c9e9f 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -3,9 +3,9 @@ use ab_glyph::{GlyphId, OutlinedGlyph}; use bevy_asset::{Assets, Handle}; use bevy_core::FloatOrd; use bevy_math::Vec2; +use bevy_reflect::TypeUuid; use bevy_render::texture::Texture; use bevy_sprite::TextureAtlas; -use bevy_type_registry::TypeUuid; use bevy_utils::{AHashExt, HashMap}; type FontSizeKey = FloatOrd; diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index 20d05373554a0..77570f9870ac7 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -55,7 +55,7 @@ impl GlyphBrush { return Ok(Vec::new()); } - let first_glyph = glyphs.first().expect("Must have at least one glyph"); + let first_glyph = glyphs.first().expect("Must have at least one glyph."); let font_id = first_glyph.font_id.0; let handle = &self.handles[font_id]; let font = fonts.get(handle).ok_or(TextError::NoSuchFont)?; diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index eddd4e69f8ac3..3f1584991cc69 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_transform" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,12 +14,11 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_property = { path = "../bevy_property", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other smallvec = { version = "1.4", features = ["serde"] } diff --git a/crates/bevy_transform/src/components/children.rs b/crates/bevy_transform/src/components/children.rs index 5b08a41b48f6d..e3a0c380f68a5 100644 --- a/crates/bevy_transform/src/components/children.rs +++ b/crates/bevy_transform/src/components/children.rs @@ -1,10 +1,11 @@ use bevy_ecs::{Entity, MapEntities}; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent, ReflectMapEntities}; use smallvec::SmallVec; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; -#[derive(Default, Clone, Properties, Debug)] -pub struct Children(pub SmallVec<[Entity; 8]>); +#[derive(Default, Clone, Debug, Reflect)] +#[reflect(Component, MapEntities)] +pub struct Children(pub(crate) SmallVec<[Entity; 8]>); impl MapEntities for Children { fn map_entities( @@ -23,18 +24,17 @@ impl Children { pub fn with(entity: &[Entity]) -> Self { Self(SmallVec::from_slice(entity)) } + + /// Swaps the child at `a_index` with the child at `b_index` + pub fn swap(&mut self, a_index: usize, b_index: usize) { + self.0.swap(a_index, b_index); + } } impl Deref for Children { - type Target = SmallVec<[Entity; 8]>; + type Target = [Entity]; fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Children { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + &self.0[..] } } diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 4292372081e7c..ad567e3e0e982 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -1,10 +1,10 @@ +use super::Transform; use bevy_math::{Mat3, Mat4, Quat, Vec3}; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use std::ops::Mul; -use super::Transform; - -#[derive(Debug, PartialEq, Clone, Copy, Properties)] +#[derive(Debug, PartialEq, Clone, Copy, Reflect)] +#[reflect(Component)] pub struct GlobalTransform { pub translation: Vec3, pub rotation: Quat, diff --git a/crates/bevy_transform/src/components/parent.rs b/crates/bevy_transform/src/components/parent.rs index bf9bdab9a67b2..3379988de426e 100644 --- a/crates/bevy_transform/src/components/parent.rs +++ b/crates/bevy_transform/src/components/parent.rs @@ -1,8 +1,9 @@ use bevy_ecs::{Entity, FromResources, MapEntities}; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent, ReflectMapEntities}; use std::ops::{Deref, DerefMut}; -#[derive(Debug, Copy, Clone, Eq, PartialEq, Properties)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Reflect)] +#[reflect(Component, MapEntities)] pub struct Parent(pub Entity); // TODO: We need to impl either FromResources or Default so Parent can be registered as Properties. @@ -25,9 +26,6 @@ impl MapEntities for Parent { } } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct PreviousParent(pub Entity); - impl Deref for Parent { type Target = Entity; @@ -41,3 +39,24 @@ impl DerefMut for Parent { &mut self.0 } } + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Reflect)] +#[reflect(Component, MapEntities)] +pub struct PreviousParent(pub(crate) Entity); + +impl MapEntities for PreviousParent { + fn map_entities( + &mut self, + entity_map: &bevy_ecs::EntityMap, + ) -> Result<(), bevy_ecs::MapEntitiesError> { + self.0 = entity_map.get(self.0)?; + Ok(()) + } +} + +// TODO: Better handle this case see `impl FromResources for Parent` +impl FromResources for PreviousParent { + fn from_resources(_resources: &bevy_ecs::Resources) -> Self { + PreviousParent(Entity::new(u32::MAX)) + } +} diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 85876ff6005af..388a996257714 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -1,10 +1,10 @@ +use super::GlobalTransform; use bevy_math::{Mat3, Mat4, Quat, Vec3}; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use std::ops::Mul; -use super::GlobalTransform; - -#[derive(Debug, PartialEq, Clone, Copy, Properties)] +#[derive(Debug, PartialEq, Clone, Copy, Reflect)] +#[reflect(Component)] pub struct Transform { pub translation: Vec3, pub rotation: Quat, diff --git a/crates/bevy_transform/src/hierarchy/child_builder.rs b/crates/bevy_transform/src/hierarchy/child_builder.rs index b42772cfb8bc7..81267f799e154 100644 --- a/crates/bevy_transform/src/hierarchy/child_builder.rs +++ b/crates/bevy_transform/src/hierarchy/child_builder.rs @@ -19,7 +19,7 @@ impl Command for InsertChildren { { let mut added = false; if let Ok(mut children) = world.get_mut::(self.parent) { - children.insert_from_slice(self.index, &self.children); + children.0.insert_from_slice(self.index, &self.children); added = true; } @@ -54,7 +54,7 @@ impl Command for PushChildren { { let mut added = false; if let Ok(mut children) = world.get_mut::(self.parent) { - children.extend(self.children.iter().cloned()); + children.0.extend(self.children.iter().cloned()); added = true; } @@ -81,6 +81,10 @@ impl<'a> ChildBuilder<'a> { self.commands.current_entity() } + pub fn parent_entity(&self) -> Entity { + self.push_children.parent + } + pub fn with_bundle( &mut self, components: impl DynamicBundle + Send + Sync + 'static, @@ -102,6 +106,11 @@ impl<'a> ChildBuilder<'a> { func(current_entity); self } + + pub fn add_command(&mut self, command: C) -> &mut Self { + self.commands.add_command(command); + self + } } pub trait BuildChildren { diff --git a/crates/bevy_transform/src/hierarchy/hierarchy.rs b/crates/bevy_transform/src/hierarchy/hierarchy.rs index 6509fa0ff7a0d..85343d3f37c14 100644 --- a/crates/bevy_transform/src/hierarchy/hierarchy.rs +++ b/crates/bevy_transform/src/hierarchy/hierarchy.rs @@ -1,48 +1,17 @@ use crate::components::{Children, Parent}; -use bevy_ecs::{Command, Commands, Entity, Query, Resources, World}; +use bevy_ecs::{Command, Commands, Entity, Resources, World}; use bevy_utils::tracing::debug; -pub fn run_on_hierarchy( - children_query: &Query<&Children>, - state: &mut S, - entity: Entity, - parent_result: Option, - mut previous_result: Option, - run: &mut dyn FnMut(&mut S, Entity, Option, Option) -> Option, -) -> Option -where - T: Clone, -{ - let parent_result = run(state, entity, parent_result, previous_result); - previous_result = None; - if let Ok(children) = children_query.get(entity) { - for child in children.iter().cloned() { - previous_result = run_on_hierarchy( - children_query, - state, - child, - parent_result.clone(), - previous_result, - run, - ); - } - } else { - previous_result = parent_result; - } - - previous_result -} - #[derive(Debug)] pub struct DespawnRecursive { entity: Entity, } -fn despawn_with_children_recursive(world: &mut World, entity: Entity) { +pub fn despawn_with_children_recursive(world: &mut World, entity: Entity) { // first, make the entity's own parent forget about it if let Ok(parent) = world.get::(entity).map(|parent| parent.0) { if let Ok(mut children) = world.get_mut::(parent) { - children.retain(|c| *c != entity); + children.0.retain(|c| *c != entity); } } @@ -52,12 +21,8 @@ fn despawn_with_children_recursive(world: &mut World, entity: Entity) { // Should only be called by `despawn_with_children_recursive`! fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity) { - if let Some(children) = world - .get::(entity) - .ok() - .map(|children| children.0.iter().cloned().collect::>()) - { - for e in children { + if let Ok(mut children) = world.get_mut::(entity) { + for e in std::mem::take(&mut children.0) { despawn_with_children_recursive(world, e); } } @@ -98,28 +63,32 @@ mod tests { let mut command_buffer = Commands::default(); command_buffer.set_entity_reserver(world.get_entity_reserver()); - command_buffer.spawn((0u32, 0u64)).with_children(|parent| { - parent.spawn((0u32, 0u64)); - }); + command_buffer + .spawn(("Another parent".to_owned(), 0u32)) + .with_children(|parent| { + parent.spawn(("Another child".to_owned(), 1u32)); + }); // Create a grandparent entity which will _not_ be deleted - command_buffer.spawn((1u32, 1u64)); + command_buffer.spawn(("Grandparent".to_owned(), 2u32)); let grandparent_entity = command_buffer.current_entity().unwrap(); command_buffer.with_children(|parent| { // Add a child to the grandparent (the "parent"), which will get deleted - parent.spawn((2u32, 2u64)); + parent.spawn(("Parent, to be deleted".to_owned(), 3u32)); // All descendents of the "parent" should also be deleted. parent.with_children(|parent| { - parent.spawn((3u32, 3u64)).with_children(|parent| { - // child - parent.spawn((4u32, 4u64)); - }); - parent.spawn((5u32, 5u64)); + parent + .spawn(("First Child, to be deleted".to_owned(), 4u32)) + .with_children(|parent| { + // child + parent.spawn(("First grand child, to be deleted".to_owned(), 5u32)); + }); + parent.spawn(("Second child, to be deleted".to_owned(), 6u32)); }); }); - command_buffer.spawn((0u32, 0u64)); + command_buffer.spawn(("An innocent bystander".to_owned(), 7u32)); command_buffer.apply(&mut world, &mut resources); let parent_entity = world.get::(grandparent_entity).unwrap()[0]; @@ -128,10 +97,11 @@ mod tests { command_buffer.despawn_recursive(parent_entity); // despawning the same entity twice should not panic command_buffer.apply(&mut world, &mut resources); - let results = world - .query::<(&u32, &u64)>() - .map(|(a, b)| (*a, *b)) + let mut results = world + .query::<(&String, &u32)>() + .map(|(a, b)| (a.clone(), *b)) .collect::>(); + results.sort_unstable_by_key(|(_, index)| *index); { let children = world.get::(grandparent_entity).unwrap(); @@ -142,11 +112,14 @@ mod tests { ); } - // parent_entity and its children should be deleted, - // the grandparent tuple (1, 1) and (0, 0) tuples remaining. assert_eq!( results, - vec![(0u32, 0u64), (0u32, 0u64), (0u32, 0u64), (1u32, 1u64)] + vec![ + ("Another parent".to_owned(), 0u32), + ("Another child".to_owned(), 1u32), + ("Grandparent".to_owned(), 2u32), + ("An innocent bystander".to_owned(), 7u32) + ] ); } } diff --git a/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs b/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs index 09f67b68e8b05..fadd9707bca9a 100644 --- a/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs +++ b/crates/bevy_transform/src/hierarchy/hierarchy_maintenance_system.rs @@ -1,15 +1,14 @@ use crate::components::*; -use bevy_ecs::{Changed, Commands, Entity, Query, Without}; +use bevy_ecs::{Commands, Entity, Query, Without}; use bevy_utils::HashMap; use smallvec::SmallVec; pub fn parent_update_system( commands: &mut Commands, removed_parent_query: Query<(Entity, &PreviousParent), Without>, - mut changed_parent_query: Query< - (Entity, &Parent, Option<&mut PreviousParent>), - Changed, - >, + // The next query could be run with a Changed filter. However, this would mean that modifications later in the frame are lost. + // See issue 891: https://github.com/bevyengine/bevy/issues/891 + mut parent_query: Query<(Entity, &Parent, Option<&mut PreviousParent>)>, mut children_query: Query<&mut Children>, ) { // Entities with a missing `Parent` (ie. ones that have a `PreviousParent`), remove @@ -25,7 +24,7 @@ pub fn parent_update_system( let mut children_additions = HashMap::>::default(); // Entities with a changed Parent (that also have a PreviousParent, even if None) - for (entity, parent, possible_previous_parent) in changed_parent_query.iter_mut() { + for (entity, parent, possible_previous_parent) in parent_query.iter_mut() { if let Some(mut previous_parent) = possible_previous_parent { // New and previous point to the same Entity, carry on, nothing to see here. if previous_parent.0 == parent.0 { @@ -47,6 +46,10 @@ pub fn parent_update_system( // `children_additions`). if let Ok(mut new_parent_children) = children_query.get_mut(parent.0) { // This is the parent + debug_assert!( + !(*new_parent_children).0.contains(&entity), + "children already added" + ); (*new_parent_children).0.push(entity); } else { // The parent doesn't have a children entity, lets add it @@ -68,7 +71,7 @@ pub fn parent_update_system( mod test { use super::*; use crate::{hierarchy::BuildChildren, transform_propagate_system::transform_propagate_system}; - use bevy_ecs::{Resources, Schedule, World}; + use bevy_ecs::{IntoSystem, Resources, Schedule, SystemStage, World}; use bevy_math::Vec3; #[test] @@ -76,10 +79,12 @@ mod test { let mut world = World::default(); let mut resources = Resources::default(); + let mut update_stage = SystemStage::parallel(); + update_stage.add_system(parent_update_system.system()); + update_stage.add_system(transform_propagate_system.system()); + let mut schedule = Schedule::default(); - schedule.add_stage("update"); - schedule.add_system_to_stage("update", parent_update_system); - schedule.add_system_to_stage("update", transform_propagate_system); + schedule.add_stage("update", update_stage); // Add parent entities let mut commands = Commands::default(); @@ -98,8 +103,7 @@ mod test { }); let parent = parent.unwrap(); commands.apply(&mut world, &mut resources); - schedule.initialize(&mut world, &mut resources); - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!( world @@ -115,7 +119,7 @@ mod test { // Parent `e1` to `e2`. (*world.get_mut::(children[0]).unwrap()).0 = children[1]; - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!( world @@ -139,7 +143,7 @@ mod test { world.despawn(children[0]).unwrap(); - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!( world diff --git a/crates/bevy_transform/src/hierarchy/world_child_builder.rs b/crates/bevy_transform/src/hierarchy/world_child_builder.rs index a658956c06e69..b2122fe135f5c 100644 --- a/crates/bevy_transform/src/hierarchy/world_child_builder.rs +++ b/crates/bevy_transform/src/hierarchy/world_child_builder.rs @@ -22,7 +22,7 @@ impl<'a, 'b> WorldChildBuilder<'a, 'b> { let world = &mut self.world_builder.world; let mut added = false; if let Ok(mut children) = world.get_mut::(parent_entity) { - children.push(entity); + children.0.push(entity); added = true; } @@ -52,6 +52,15 @@ impl<'a, 'b> WorldChildBuilder<'a, 'b> { pub fn current_entity(&self) -> Option { self.world_builder.current_entity } + + pub fn for_current_entity(&mut self, f: impl FnOnce(Entity)) -> &mut Self { + let current_entity = self + .world_builder + .current_entity + .expect("The 'current entity' is not set. You should spawn an entity first."); + f(current_entity); + self + } } pub trait BuildWorldChildren { diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index fe8dc696c996e..97dd09ed24bd2 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -6,26 +6,31 @@ pub mod prelude { pub use crate::{components::*, hierarchy::*, TransformPlugin}; } -use bevy_app::prelude::*; -use bevy_type_registry::RegisterType; -use prelude::{parent_update_system, Children, GlobalTransform, Parent, Transform}; +use bevy_app::{prelude::*, startup_stage}; +use bevy_ecs::IntoSystem; +use bevy_reflect::RegisterTypeBuilder; +use prelude::{parent_update_system, Children, GlobalTransform, Parent, PreviousParent, Transform}; #[derive(Default)] pub struct TransformPlugin; impl Plugin for TransformPlugin { fn build(&self, app: &mut AppBuilder) { - app.register_component_with::(|reg| reg.map_entities()) - .register_component_with::(|reg| reg.map_entities()) - .register_component::() - .register_component::() + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() // add transform systems to startup so the first update is "correct" - .add_startup_system(parent_update_system) - .add_startup_system(transform_propagate_system::transform_propagate_system) - .add_system_to_stage(stage::POST_UPDATE, parent_update_system) + .add_startup_system_to_stage(startup_stage::POST_STARTUP, parent_update_system.system()) + .add_startup_system_to_stage( + startup_stage::POST_STARTUP, + transform_propagate_system::transform_propagate_system.system(), + ) + .add_system_to_stage(stage::POST_UPDATE, parent_update_system.system()) .add_system_to_stage( stage::POST_UPDATE, - transform_propagate_system::transform_propagate_system, + transform_propagate_system::transform_propagate_system.system(), ); } } diff --git a/crates/bevy_transform/src/transform_propagate_system.rs b/crates/bevy_transform/src/transform_propagate_system.rs index 05e335edbf0e6..6914ad31ab0c1 100644 --- a/crates/bevy_transform/src/transform_propagate_system.rs +++ b/crates/bevy_transform/src/transform_propagate_system.rs @@ -3,22 +3,29 @@ use bevy_ecs::prelude::*; pub fn transform_propagate_system( mut root_query: Query< - (Option<&Children>, &Transform, &mut GlobalTransform), + (Entity, Option<&Children>, &Transform, &mut GlobalTransform), (Without, With), >, mut transform_query: Query<(&Transform, &mut GlobalTransform), With>, + changed_transform_query: Query>, children_query: Query, (With, With)>, ) { - for (children, transform, mut global_transform) in root_query.iter_mut() { - *global_transform = GlobalTransform::from(*transform); + for (entity, children, transform, mut global_transform) in root_query.iter_mut() { + let mut changed = false; + if changed_transform_query.get(entity).is_ok() { + *global_transform = GlobalTransform::from(*transform); + changed = true; + } if let Some(children) = children { for child in children.0.iter() { propagate_recursive( &global_transform, + &changed_transform_query, &mut transform_query, &children_query, *child, + changed, ); } } @@ -27,13 +34,19 @@ pub fn transform_propagate_system( fn propagate_recursive( parent: &GlobalTransform, + changed_transform_query: &Query>, transform_query: &mut Query<(&Transform, &mut GlobalTransform), With>, children_query: &Query, (With, With)>, entity: Entity, + mut changed: bool, ) { + changed |= changed_transform_query.get(entity).is_ok(); + let global_matrix = { if let Ok((transform, mut global_transform)) = transform_query.get_mut(entity) { - *global_transform = parent.mul_transform(*transform); + if changed { + *global_transform = parent.mul_transform(*transform); + } *global_transform } else { return; @@ -42,7 +55,14 @@ fn propagate_recursive( if let Ok(Some(children)) = children_query.get(entity) { for child in children.0.iter() { - propagate_recursive(&global_matrix, transform_query, children_query, *child); + propagate_recursive( + &global_matrix, + changed_transform_query, + transform_query, + children_query, + *child, + changed, + ); } } } @@ -50,8 +70,8 @@ fn propagate_recursive( #[cfg(test)] mod test { use super::*; - use crate::hierarchy::{parent_update_system, BuildChildren}; - use bevy_ecs::{Resources, Schedule, World}; + use crate::hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren}; + use bevy_ecs::{Resources, Schedule, SystemStage, World}; use bevy_math::Vec3; #[test] @@ -59,36 +79,40 @@ mod test { let mut world = World::default(); let mut resources = Resources::default(); + let mut update_stage = SystemStage::parallel(); + update_stage.add_system(parent_update_system.system()); + update_stage.add_system(transform_propagate_system.system()); + let mut schedule = Schedule::default(); - schedule.add_stage("update"); - schedule.add_system_to_stage("update", parent_update_system); - schedule.add_system_to_stage("update", transform_propagate_system); + schedule.add_stage("update", update_stage); // Root entity - let parent = world.spawn(( + world.spawn(( Transform::from_translation(Vec3::new(1.0, 0.0, 0.0)), GlobalTransform::identity(), )); - let children = world - .spawn_batch(vec![ - ( - Transform::from_translation(Vec3::new(0.0, 2.0, 0.)), - Parent(parent), - GlobalTransform::identity(), - ), - ( - Transform::from_translation(Vec3::new(0.0, 0.0, 3.)), - Parent(parent), - GlobalTransform::identity(), - ), - ]) - .collect::>(); - // we need to run the schedule two times because components need to be filled in - // to resolve this problem in code, just add the correct components, or use Commands - // which adds all of the components needed with the correct state (see next test) - schedule.initialize(&mut world, &mut resources); - schedule.run(&mut world, &mut resources); - schedule.run(&mut world, &mut resources); + + let mut children = Vec::new(); + world + .build() + .spawn(( + Transform::from_translation(Vec3::new(1.0, 0.0, 0.0)), + GlobalTransform::identity(), + )) + .with_children(|parent| { + parent + .spawn(( + Transform::from_translation(Vec3::new(0.0, 2.0, 0.)), + GlobalTransform::identity(), + )) + .for_current_entity(|entity| children.push(entity)) + .spawn(( + Transform::from_translation(Vec3::new(0.0, 0.0, 3.)), + GlobalTransform::identity(), + )) + .for_current_entity(|entity| children.push(entity)); + }); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!( *world.get::(children[0]).unwrap(), @@ -108,10 +132,12 @@ mod test { let mut world = World::default(); let mut resources = Resources::default(); + let mut update_stage = SystemStage::parallel(); + update_stage.add_system(parent_update_system.system()); + update_stage.add_system(transform_propagate_system.system()); + let mut schedule = Schedule::default(); - schedule.add_stage("update"); - schedule.add_system_to_stage("update", parent_update_system); - schedule.add_system_to_stage("update", transform_propagate_system); + schedule.add_stage("update", update_stage); // Root entity let mut commands = Commands::default(); @@ -136,8 +162,7 @@ mod test { .for_current_entity(|entity| children.push(entity)); }); commands.apply(&mut world, &mut resources); - schedule.initialize(&mut world, &mut resources); - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!( *world.get::(children[0]).unwrap(), diff --git a/crates/bevy_type_registry/Cargo.toml b/crates/bevy_type_registry/Cargo.toml deleted file mode 100644 index a71e73de9896d..0000000000000 --- a/crates/bevy_type_registry/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "bevy_type_registry" -version = "0.3.0" -edition = "2018" -authors = [ - "Bevy Contributors ", - "Carter Anderson ", -] -description = "Provides a type registry for Bevy Engine" -homepage = "https://bevyengine.org" -repository = "https://github.com/bevyengine/bevy" -license = "MIT" -keywords = ["bevy"] - -[dependencies] -# bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_derive = { path = "../bevy_derive", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_property = { path = "../bevy_property", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } - -# other -uuid = { version = "0.8", features = ["v4", "serde"] } -serde = { version = "1", features = ["derive"] } -parking_lot = "0.11.0" diff --git a/crates/bevy_type_registry/src/lib.rs b/crates/bevy_type_registry/src/lib.rs deleted file mode 100644 index 2f0fbe88cea53..0000000000000 --- a/crates/bevy_type_registry/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod register_type; -mod type_registry; -mod type_uuid; - -pub use register_type::*; -pub use type_registry::*; -pub use type_uuid::*; -pub use uuid::Uuid; - -use bevy_app::prelude::*; -use bevy_property::DynamicProperties; - -#[derive(Default)] -pub struct TypeRegistryPlugin; - -impl Plugin for TypeRegistryPlugin { - fn build(&self, app: &mut AppBuilder) { - app.init_resource::() - .register_property::(); - } -} diff --git a/crates/bevy_type_registry/src/register_type.rs b/crates/bevy_type_registry/src/register_type.rs deleted file mode 100644 index 40f1258a8f9ff..0000000000000 --- a/crates/bevy_type_registry/src/register_type.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::{ComponentRegistration, ComponentRegistrationBuilder, TypeRegistry}; -use bevy_app::AppBuilder; -use bevy_ecs::{Component, FromResources}; -use bevy_property::{DeserializeProperty, Properties, Property}; - -pub trait RegisterType { - fn register_component(&mut self) -> &mut Self - where - T: Properties + DeserializeProperty + Component + FromResources; - fn register_component_with( - &mut self, - build: fn(ComponentRegistrationBuilder) -> ComponentRegistrationBuilder, - ) -> &mut Self - where - T: Properties + DeserializeProperty + Component + FromResources; - fn register_properties(&mut self) -> &mut Self - where - T: Properties + DeserializeProperty + FromResources; - fn register_property(&mut self) -> &mut Self - where - T: Property + DeserializeProperty; -} - -impl RegisterType for AppBuilder { - fn register_component(&mut self) -> &mut Self - where - T: Properties + DeserializeProperty + Component + FromResources, - { - { - let type_registry = self.app.resources.get::().unwrap(); - type_registry.component.write().register::(); - type_registry.property.write().register::(); - } - self - } - - fn register_properties(&mut self) -> &mut Self - where - T: Properties + DeserializeProperty + Component + FromResources, - { - { - let type_registry = self.app.resources.get::().unwrap(); - type_registry.property.write().register::(); - } - self - } - - fn register_property(&mut self) -> &mut Self - where - T: Property + DeserializeProperty, - { - { - let type_registry = self.app.resources.get::().unwrap(); - type_registry.property.write().register::(); - } - self - } - - fn register_component_with( - &mut self, - build: fn(ComponentRegistrationBuilder) -> ComponentRegistrationBuilder, - ) -> &mut Self - where - T: Properties + DeserializeProperty + Component + FromResources, - { - { - let mut builder = ComponentRegistration::build::(); - builder = build(builder); - let type_registry = self.app.resources.get::().unwrap(); - type_registry - .component - .write() - .add_registration(builder.finish()); - type_registry.property.write().register::(); - } - self - } -} diff --git a/crates/bevy_type_registry/src/type_registry.rs b/crates/bevy_type_registry/src/type_registry.rs deleted file mode 100644 index 0a22562755fc5..0000000000000 --- a/crates/bevy_type_registry/src/type_registry.rs +++ /dev/null @@ -1,334 +0,0 @@ -use bevy_ecs::{ - Archetype, Component, Entity, EntityMap, FromResources, MapEntities, MapEntitiesError, - Resources, World, -}; -use bevy_property::{ - DeserializeProperty, Properties, Property, PropertyTypeRegistration, PropertyTypeRegistry, -}; -use bevy_utils::{HashMap, HashSet}; -use parking_lot::RwLock; -use std::{any::TypeId, marker::PhantomData, sync::Arc}; - -#[derive(Clone, Default)] -pub struct TypeRegistry { - pub property: Arc>, - pub component: Arc>, -} - -#[derive(Default)] -pub struct ComponentRegistry { - pub registrations: HashMap, - pub short_names: HashMap, - pub full_names: HashMap, - pub ambiguous_names: HashSet, -} - -impl ComponentRegistry { - pub fn register(&mut self) - where - T: Properties + Component + FromResources, - { - self.add_registration(ComponentRegistration::of::()); - } - - pub fn add_registration(&mut self, registration: ComponentRegistration) { - let short_name = registration.short_name.to_string(); - self.full_names - .insert(registration.long_name.to_string(), registration.ty); - if self.short_names.contains_key(&short_name) || self.ambiguous_names.contains(&short_name) - { - // name is ambiguous. fall back to long names for all ambiguous types - self.short_names.remove(&short_name); - self.ambiguous_names.insert(short_name); - } else { - self.short_names.insert(short_name, registration.ty); - } - self.registrations.insert(registration.ty, registration); - } - - pub fn get(&self, type_id: &TypeId) -> Option<&ComponentRegistration> { - self.registrations.get(type_id) - } - - pub fn get_with_full_name(&self, full_name: &str) -> Option<&ComponentRegistration> { - self.full_names - .get(full_name) - .and_then(|id| self.registrations.get(id)) - } - - pub fn get_with_short_name(&self, short_name: &str) -> Option<&ComponentRegistration> { - self.short_names - .get(short_name) - .and_then(|id| self.registrations.get(id)) - } - - pub fn get_with_name(&self, type_name: &str) -> Option<&ComponentRegistration> { - let mut registration = self.get_with_short_name(type_name); - if registration.is_none() { - registration = self.get_with_full_name(type_name); - if registration.is_none() && self.ambiguous_names.contains(type_name) { - panic!("Type name is ambiguous: {}", type_name); - } - } - registration - } - - pub fn iter(&self) -> impl Iterator { - self.registrations.values() - } -} - -#[derive(Clone)] -pub struct ComponentRegistration { - pub ty: TypeId, - pub short_name: String, - pub long_name: &'static str, - component_add_fn: fn(&mut World, resources: &Resources, Entity, &dyn Property), - component_apply_fn: fn(&mut World, Entity, &dyn Property), - component_properties_fn: fn(&Archetype, usize) -> &dyn Properties, - component_copy_fn: fn(&World, &mut World, &Resources, Entity, Entity), - copy_to_scene_fn: fn(&World, &mut World, &Resources, Entity, Entity), - copy_from_scene_fn: fn(&World, &mut World, &Resources, Entity, Entity), - map_entities_fn: fn(&mut World, &EntityMap) -> Result<(), MapEntitiesError>, -} - -struct ComponentRegistrationDefaults; - -impl ComponentRegistrationDefaults { - pub fn component_add( - world: &mut World, - resources: &Resources, - entity: Entity, - property: &dyn Property, - ) { - let mut component = T::from_resources(resources); - component.apply(property); - world.insert_one(entity, component).unwrap(); - } - - fn component_apply( - world: &mut World, - entity: Entity, - property: &dyn Property, - ) { - let mut component = world.get_mut::(entity).unwrap(); - component.apply(property); - } - - fn component_copy( - source_world: &World, - destination_world: &mut World, - resources: &Resources, - source_entity: Entity, - destination_entity: Entity, - ) { - let source_component = source_world.get::(source_entity).unwrap(); - let mut destination_component = T::from_resources(resources); - destination_component.apply(source_component); - destination_world - .insert_one(destination_entity, destination_component) - .unwrap(); - } - - fn component_properties( - archetype: &Archetype, - index: usize, - ) -> &dyn Properties { - // the type has been looked up by the caller, so this is safe - unsafe { - let ptr = archetype.get::().unwrap().as_ptr().add(index); - ptr.as_ref().unwrap() - } - } - - fn map_entities(_world: &mut World, _entity_map: &EntityMap) -> Result<(), MapEntitiesError> { - Ok(()) - } -} - -impl ComponentRegistration { - pub fn build() -> ComponentRegistrationBuilder - where - T: Properties + DeserializeProperty + Component + FromResources, - { - ComponentRegistrationBuilder { - registration: ComponentRegistration::of::(), - marker: PhantomData::default(), - } - } - - pub fn of() -> Self { - let ty = TypeId::of::(); - Self { - ty, - component_add_fn: ComponentRegistrationDefaults::component_add::, - component_apply_fn: ComponentRegistrationDefaults::component_apply::, - component_copy_fn: ComponentRegistrationDefaults::component_copy::, - component_properties_fn: ComponentRegistrationDefaults::component_properties::, - copy_from_scene_fn: ComponentRegistrationDefaults::component_copy::, - copy_to_scene_fn: ComponentRegistrationDefaults::component_copy::, - map_entities_fn: ComponentRegistrationDefaults::map_entities, - short_name: PropertyTypeRegistration::get_short_name(std::any::type_name::()), - long_name: std::any::type_name::(), - } - } - - pub fn add_property_to_entity( - &self, - world: &mut World, - resources: &Resources, - entity: Entity, - property: &dyn Property, - ) { - (self.component_add_fn)(world, resources, entity, property); - } - - pub fn apply_property_to_entity( - &self, - world: &mut World, - entity: Entity, - property: &dyn Property, - ) { - (self.component_apply_fn)(world, entity, property); - } - - pub fn get_component_properties<'a>( - &self, - archetype: &'a Archetype, - entity_index: usize, - ) -> &'a dyn Properties { - (self.component_properties_fn)(archetype, entity_index) - } - - pub fn component_copy( - &self, - source_world: &World, - destination_world: &mut World, - resources: &Resources, - source_entity: Entity, - destination_entity: Entity, - ) { - (self.component_copy_fn)( - source_world, - destination_world, - resources, - source_entity, - destination_entity, - ); - } - - pub fn copy_from_scene( - &self, - scene_world: &World, - destination_world: &mut World, - resources: &Resources, - source_entity: Entity, - destination_entity: Entity, - ) { - (self.component_copy_fn)( - scene_world, - destination_world, - resources, - source_entity, - destination_entity, - ); - } - - pub fn copy_to_scene( - &self, - source_world: &World, - scene_world: &mut World, - resources: &Resources, - source_entity: Entity, - destination_entity: Entity, - ) { - (self.copy_to_scene_fn)( - source_world, - scene_world, - resources, - source_entity, - destination_entity, - ); - } - - pub fn map_entities( - &self, - world: &mut World, - entity_map: &EntityMap, - ) -> Result<(), MapEntitiesError> { - (self.map_entities_fn)(world, entity_map) - } -} - -pub struct ComponentRegistrationBuilder { - registration: ComponentRegistration, - marker: PhantomData, -} - -impl ComponentRegistrationBuilder -where - T: Properties + DeserializeProperty + Component + FromResources, -{ - pub fn map_entities(mut self) -> Self - where - T: MapEntities, - { - self.registration.map_entities_fn = |world: &mut World, entity_map: &EntityMap| { - // TODO: add UntrackedMut pointer that returns &mut T. This will avoid setting the "mutated" state - for mut component in &mut world.query_mut::<&mut T>() { - component.map_entities(entity_map)?; - } - - Ok(()) - }; - self - } - - pub fn into_scene_component(mut self) -> Self - where - T: IntoComponent, - { - self.registration.copy_to_scene_fn = - |source_world: &World, - scene_world: &mut World, - resources: &Resources, - source_entity: Entity, - scene_entity: Entity| { - let source_component = source_world.get::(source_entity).unwrap(); - let scene_component = source_component.into_component(resources); - scene_world - .insert_one(scene_entity, scene_component) - .unwrap(); - }; - - self - } - - pub fn into_runtime_component(mut self) -> Self - where - T: IntoComponent, - { - self.registration.copy_from_scene_fn = - |scene_world: &World, - destination_world: &mut World, - resources: &Resources, - scene_entity: Entity, - destination_entity: Entity| { - let scene_component = scene_world.get::(scene_entity).unwrap(); - let destination_component = scene_component.into_component(resources); - destination_world - .insert_one(destination_entity, destination_component) - .unwrap(); - }; - - self - } - - pub fn finish(self) -> ComponentRegistration { - self.registration - } -} - -pub trait IntoComponent { - fn into_component(&self, resources: &Resources) -> ToComponent; -} diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index b361ad2ff284d..7c39664d9f14d 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_ui" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,20 +14,21 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_core = { path = "../bevy_core", version = "0.3.0" } -bevy_derive = { path = "../bevy_derive", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_input = { path = "../bevy_input", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_render = { path = "../bevy_render", version = "0.3.0" } -bevy_sprite = { path = "../bevy_sprite", version = "0.3.0" } -bevy_text = { path = "../bevy_text", version = "0.3.0" } -bevy_transform = { path = "../bevy_transform", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_window = { path = "../bevy_window", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_core = { path = "../bevy_core", version = "0.4.0" } +bevy_derive = { path = "../bevy_derive", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_input = { path = "../bevy_input", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_render = { path = "../bevy_render", version = "0.4.0" } +bevy_sprite = { path = "../bevy_sprite", version = "0.4.0" } +bevy_text = { path = "../bevy_text", version = "0.4.0" } +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_window = { path = "../bevy_window", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other stretch = "0.3" +serde = {version = "1", features = ["derive"]} diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 41c01b30e1175..f4bd26b7080e4 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -12,6 +12,7 @@ use bevy_render::{ draw::Draw, mesh::Mesh, pipeline::{RenderPipeline, RenderPipelines}, + prelude::Visible, }; use bevy_sprite::{ColorMaterial, QUAD_HANDLE}; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -23,6 +24,7 @@ pub struct NodeBundle { pub mesh: Handle, // TODO: maybe abstract this out pub material: Handle, pub draw: Draw, + pub visible: Visible, pub render_pipelines: RenderPipelines, pub transform: Transform, pub global_transform: GlobalTransform, @@ -31,10 +33,14 @@ pub struct NodeBundle { impl Default for NodeBundle { fn default() -> Self { NodeBundle { - mesh: QUAD_HANDLE, + mesh: QUAD_HANDLE.typed(), render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - UI_PIPELINE_HANDLE, + UI_PIPELINE_HANDLE.typed(), )]), + visible: Visible { + is_transparent: true, + ..Default::default() + }, node: Default::default(), style: Default::default(), material: Default::default(), @@ -54,6 +60,7 @@ pub struct ImageBundle { pub mesh: Handle, // TODO: maybe abstract this out pub material: Handle, pub draw: Draw, + pub visible: Visible, pub render_pipelines: RenderPipelines, pub transform: Transform, pub global_transform: GlobalTransform, @@ -62,9 +69,9 @@ pub struct ImageBundle { impl Default for ImageBundle { fn default() -> Self { ImageBundle { - mesh: QUAD_HANDLE, + mesh: QUAD_HANDLE.typed(), render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - UI_PIPELINE_HANDLE, + UI_PIPELINE_HANDLE.typed(), )]), node: Default::default(), image: Default::default(), @@ -72,6 +79,10 @@ impl Default for ImageBundle { style: Default::default(), material: Default::default(), draw: Default::default(), + visible: Visible { + is_transparent: true, + ..Default::default() + }, transform: Default::default(), global_transform: Default::default(), } @@ -83,6 +94,7 @@ pub struct TextBundle { pub node: Node, pub style: Style, pub draw: Draw, + pub visible: Visible, pub text: Text, pub calculated_size: CalculatedSize, pub focus_policy: FocusPolicy, @@ -95,6 +107,9 @@ impl Default for TextBundle { TextBundle { focus_policy: FocusPolicy::Pass, draw: Draw { + ..Default::default() + }, + visible: Visible { is_transparent: true, ..Default::default() }, @@ -118,6 +133,7 @@ pub struct ButtonBundle { pub mesh: Handle, // TODO: maybe abstract this out pub material: Handle, pub draw: Draw, + pub visible: Visible, pub render_pipelines: RenderPipelines, pub transform: Transform, pub global_transform: GlobalTransform, @@ -127,9 +143,9 @@ impl Default for ButtonBundle { fn default() -> Self { ButtonBundle { button: Button, - mesh: QUAD_HANDLE, + mesh: QUAD_HANDLE.typed(), render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - UI_PIPELINE_HANDLE, + UI_PIPELINE_HANDLE.typed(), )]), interaction: Default::default(), focus_policy: Default::default(), @@ -137,6 +153,10 @@ impl Default for ButtonBundle { style: Default::default(), material: Default::default(), draw: Default::default(), + visible: Visible { + is_transparent: true, + ..Default::default() + }, transform: Default::default(), global_transform: Default::default(), } @@ -144,7 +164,7 @@ impl Default for ButtonBundle { } #[derive(Bundle, Debug)] -pub struct UiCameraBundle { +pub struct CameraUiBundle { pub camera: Camera, pub orthographic_projection: OrthographicProjection, pub visible_entities: VisibleEntities, @@ -152,14 +172,14 @@ pub struct UiCameraBundle { pub global_transform: GlobalTransform, } -impl Default for UiCameraBundle { +impl Default for CameraUiBundle { fn default() -> Self { // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset // the camera's translation by far and use a right handed coordinate system let far = 1000.0; - UiCameraBundle { + CameraUiBundle { camera: Camera { - name: Some(crate::camera::UI_CAMERA.to_string()), + name: Some(crate::camera::CAMERA_UI.to_string()), ..Default::default() }, orthographic_projection: OrthographicProjection { diff --git a/crates/bevy_ui/src/flex/convert.rs b/crates/bevy_ui/src/flex/convert.rs index 30de4499983db..43bb7c839c0cb 100644 --- a/crates/bevy_ui/src/flex/convert.rs +++ b/crates/bevy_ui/src/flex/convert.rs @@ -4,68 +4,71 @@ use crate::{ }; use bevy_math::{Rect, Size}; -fn from_rect(rect: Rect) -> stretch::geometry::Rect -where - T: From, -{ +pub fn from_rect( + scale_factor: f64, + rect: Rect, +) -> stretch::geometry::Rect { stretch::geometry::Rect { - start: rect.left.into(), - end: rect.right.into(), + start: from_val(scale_factor, rect.left), + end: from_val(scale_factor, rect.right), // NOTE: top and bottom are intentionally flipped. stretch has a flipped y-axis - top: rect.bottom.into(), - bottom: rect.top.into(), + top: from_val(scale_factor, rect.bottom), + bottom: from_val(scale_factor, rect.top), } } -fn from_size(size: Size) -> stretch::geometry::Size -where - T: From, -{ +pub fn from_f32_size(scale_factor: f64, size: Size) -> stretch::geometry::Size { stretch::geometry::Size { - width: size.width.into(), - height: size.height.into(), + width: (scale_factor * size.width as f64) as f32, + height: (scale_factor * size.height as f64) as f32, } } -impl From<&Style> for stretch::style::Style { - fn from(value: &Style) -> Self { - Self { - overflow: stretch::style::Overflow::Visible, - display: value.display.into(), - position_type: value.position_type.into(), - direction: value.direction.into(), - flex_direction: value.flex_direction.into(), - flex_wrap: value.flex_wrap.into(), - align_items: value.align_items.into(), - align_self: value.align_self.into(), - align_content: value.align_content.into(), - justify_content: value.justify_content.into(), - position: from_rect(value.position), - margin: from_rect(value.margin), - padding: from_rect(value.padding), - border: from_rect(value.border), - flex_grow: value.flex_grow, - flex_shrink: value.flex_shrink, - flex_basis: value.flex_basis.into(), - size: from_size(value.size), - min_size: from_size(value.min_size), - max_size: from_size(value.max_size), - aspect_ratio: match value.aspect_ratio { - Some(value) => stretch::number::Number::Defined(value), - None => stretch::number::Number::Undefined, - }, - } +pub fn from_val_size( + scale_factor: f64, + size: Size, +) -> stretch::geometry::Size { + stretch::geometry::Size { + width: from_val(scale_factor, size.width), + height: from_val(scale_factor, size.height), } } -impl From for stretch::style::Dimension { - fn from(val: Val) -> Self { - match val { - Val::Auto => stretch::style::Dimension::Auto, - Val::Percent(value) => stretch::style::Dimension::Percent(value / 100.0), - Val::Px(value) => stretch::style::Dimension::Points(value), - Val::Undefined => stretch::style::Dimension::Undefined, - } +pub fn from_style(scale_factor: f64, value: &Style) -> stretch::style::Style { + stretch::style::Style { + overflow: stretch::style::Overflow::Visible, + display: value.display.into(), + position_type: value.position_type.into(), + direction: value.direction.into(), + flex_direction: value.flex_direction.into(), + flex_wrap: value.flex_wrap.into(), + align_items: value.align_items.into(), + align_self: value.align_self.into(), + align_content: value.align_content.into(), + justify_content: value.justify_content.into(), + position: from_rect(scale_factor, value.position), + margin: from_rect(scale_factor, value.margin), + padding: from_rect(scale_factor, value.padding), + border: from_rect(scale_factor, value.border), + flex_grow: value.flex_grow, + flex_shrink: value.flex_shrink, + flex_basis: from_val(scale_factor, value.flex_basis), + size: from_val_size(scale_factor, value.size), + min_size: from_val_size(scale_factor, value.min_size), + max_size: from_val_size(scale_factor, value.max_size), + aspect_ratio: match value.aspect_ratio { + Some(value) => stretch::number::Number::Defined(value), + None => stretch::number::Number::Undefined, + }, + } +} + +pub fn from_val(scale_factor: f64, val: Val) -> stretch::style::Dimension { + match val { + Val::Auto => stretch::style::Dimension::Auto, + Val::Percent(value) => stretch::style::Dimension::Percent(value / 100.0), + Val::Px(value) => stretch::style::Dimension::Points((scale_factor * value as f64) as f32), + Val::Undefined => stretch::style::Dimension::Undefined, } } diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index abc9bc6084c8c..5e514424f20af 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -35,10 +35,10 @@ impl Default for FlexSurface { } impl FlexSurface { - pub fn upsert_node(&mut self, entity: Entity, style: &Style) { + pub fn upsert_node(&mut self, entity: Entity, style: &Style, scale_factor: f64) { let mut added = false; let stretch = &mut self.stretch; - let stretch_style = style.into(); + let stretch_style = convert::from_style(scale_factor, style); let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| { added = true; stretch.new_node(stretch_style, Vec::new()).unwrap() @@ -51,14 +51,17 @@ impl FlexSurface { } } - pub fn upsert_leaf(&mut self, entity: Entity, style: &Style, calculated_size: CalculatedSize) { + pub fn upsert_leaf( + &mut self, + entity: Entity, + style: &Style, + calculated_size: CalculatedSize, + scale_factor: f64, + ) { let stretch = &mut self.stretch; - let stretch_style = style.into(); + let stretch_style = convert::from_style(scale_factor, style); let measure = Box::new(move |constraints: stretch::geometry::Size| { - let mut size = stretch::geometry::Size { - width: calculated_size.size.width, - height: calculated_size.size.height, - }; + let mut size = convert::from_f32_size(scale_factor, calculated_size.size); match (constraints.width, constraints.height) { (Number::Undefined, Number::Undefined) => {} (Number::Defined(width), Number::Undefined) => { @@ -116,8 +119,8 @@ impl FlexSurface { *node, stretch::style::Style { size: stretch::geometry::Size { - width: stretch::style::Dimension::Points(window.width() as f32), - height: stretch::style::Dimension::Points(window.height() as f32), + width: stretch::style::Dimension::Points(window.physical_width() as f32), + height: stretch::style::Dimension::Points(window.physical_height() as f32), }, ..Default::default() }, @@ -174,18 +177,25 @@ pub fn flex_node_system( flex_surface.update_window(window); } + // assume one window for time being... + let logical_to_physical_factor = if let Some(primary_window) = windows.get_primary() { + primary_window.scale_factor() + } else { + 1. + }; + // update changed nodes for (entity, style, calculated_size) in node_query.iter() { // TODO: remove node from old hierarchy if its root has changed if let Some(calculated_size) = calculated_size { - flex_surface.upsert_leaf(entity, &style, *calculated_size); + flex_surface.upsert_leaf(entity, &style, *calculated_size, logical_to_physical_factor); } else { - flex_surface.upsert_node(entity, &style); + flex_surface.upsert_node(entity, &style, logical_to_physical_factor); } } for (entity, style, calculated_size) in changed_size_query.iter() { - flex_surface.upsert_leaf(entity, &style, *calculated_size); + flex_surface.upsert_leaf(entity, &style, *calculated_size, logical_to_physical_factor); } // TODO: handle removed nodes @@ -203,16 +213,23 @@ pub fn flex_node_system( // compute layouts flex_surface.compute_window_layouts(); + let physical_to_logical_factor = 1. / logical_to_physical_factor; + + let to_logical = |v| (physical_to_logical_factor * v as f64) as f32; + for (entity, mut node, mut transform, parent) in node_transform_query.iter_mut() { let layout = flex_surface.get_layout(entity).unwrap(); - node.size = Vec2::new(layout.size.width, layout.size.height); + node.size = Vec2::new( + to_logical(layout.size.width), + to_logical(layout.size.height), + ); let position = &mut transform.translation; - position.x = layout.location.x + layout.size.width / 2.0; - position.y = layout.location.y + layout.size.height / 2.0; + position.x = to_logical(layout.location.x + layout.size.width / 2.0); + position.y = to_logical(layout.location.y + layout.size.height / 2.0); if let Some(parent) = parent { if let Ok(parent_layout) = flex_surface.get_layout(parent.0) { - position.x -= parent_layout.size.width / 2.0; - position.y -= parent_layout.size.height / 2.0; + position.x -= to_logical(parent_layout.size.width / 2.0); + position.y -= to_logical(parent_layout.size.height / 2.0); } } } diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 5ced61ebd9571..663b27b4c63db 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -56,7 +56,7 @@ pub fn ui_focus_system( state.cursor_position = cursor_moved.position; } if let Some(touch) = touches_input.get_pressed(0) { - state.cursor_position = touch.position; + state.cursor_position = touch.position(); } if mouse_button_input.just_released(MouseButton::Left) || touches_input.just_released(0) { diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 9152ac306e91a..9e62e520a9407 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -25,6 +25,7 @@ pub mod prelude { } use bevy_app::prelude::*; +use bevy_ecs::{IntoSystem, SystemStage}; use bevy_render::render_graph::RenderGraph; use update::ui_z_system; @@ -38,14 +39,18 @@ pub mod stage { impl Plugin for UiPlugin { fn build(&self, app: &mut AppBuilder) { app.init_resource::() - .add_stage_before(bevy_app::stage::POST_UPDATE, stage::UI) - .add_system_to_stage(bevy_app::stage::PRE_UPDATE, ui_focus_system) + .add_stage_before( + bevy_app::stage::POST_UPDATE, + stage::UI, + SystemStage::parallel(), + ) + .add_system_to_stage(bevy_app::stage::PRE_UPDATE, ui_focus_system.system()) // add these stages to front because these must run before transform update systems - .add_system_to_stage(stage::UI, widget::text_system) - .add_system_to_stage(stage::UI, widget::image_node_system) - .add_system_to_stage(stage::UI, ui_z_system) - .add_system_to_stage(stage::UI, flex_node_system) - .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system); + .add_system_to_stage(stage::UI, widget::text_system.system()) + .add_system_to_stage(stage::UI, widget::image_node_system.system()) + .add_system_to_stage(stage::UI, ui_z_system.system()) + .add_system_to_stage(stage::UI, flex_node_system.system()) + .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system()); let resources = app.resources(); let mut render_graph = resources.get_mut::().unwrap(); diff --git a/crates/bevy_ui/src/node.rs b/crates/bevy_ui/src/node.rs index 6d2a3a30536a0..feb2bc89bb367 100644 --- a/crates/bevy_ui/src/node.rs +++ b/crates/bevy_ui/src/node.rs @@ -1,13 +1,17 @@ use bevy_math::{Rect, Size, Vec2}; +use bevy_reflect::{Reflect, ReflectComponent, ReflectDeserialize}; use bevy_render::renderer::RenderResources; +use serde::{Deserialize, Serialize}; use std::ops::{Add, AddAssign}; -#[derive(Debug, Clone, Default, RenderResources)] +#[derive(Debug, Clone, Default, RenderResources, Reflect)] +#[reflect(Component)] pub struct Node { pub size: Vec2, } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum Val { Undefined, Auto, @@ -49,7 +53,7 @@ pub struct CalculatedSize { pub size: Size, } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Reflect)] pub struct Style { pub display: Display, pub position_type: PositionType, @@ -100,7 +104,8 @@ impl Default for Style { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum AlignItems { FlexStart, FlexEnd, @@ -115,7 +120,8 @@ impl Default for AlignItems { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum AlignSelf { Auto, FlexStart, @@ -131,7 +137,8 @@ impl Default for AlignSelf { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum AlignContent { FlexStart, FlexEnd, @@ -147,7 +154,8 @@ impl Default for AlignContent { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum Direction { Inherit, LTR, @@ -160,7 +168,8 @@ impl Default for Direction { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum Display { Flex, None, @@ -172,7 +181,8 @@ impl Default for Display { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum FlexDirection { Row, Column, @@ -186,7 +196,8 @@ impl Default for FlexDirection { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum JustifyContent { FlexStart, FlexEnd, @@ -216,7 +227,8 @@ impl Default for JustifyContent { // } // } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum PositionType { Relative, Absolute, @@ -228,7 +240,8 @@ impl Default for PositionType { } } -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] +#[reflect_value(PartialEq, Serialize, Deserialize)] pub enum FlexWrap { NoWrap, Wrap, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 1da738af8cba7..d95e41db4fb22 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -1,6 +1,7 @@ use crate::Node; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Assets, HandleUntyped}; use bevy_ecs::Resources; +use bevy_reflect::TypeUuid; use bevy_render::{ camera::ActiveCameras, pass::{ @@ -16,10 +17,9 @@ use bevy_render::{ shader::{Shader, ShaderStage, ShaderStages}, texture::TextureFormat, }; -use bevy_type_registry::TypeUuid; -pub const UI_PIPELINE_HANDLE: Handle = - Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 3234320022263993878); +pub const UI_PIPELINE_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 3234320022263993878); pub fn build_ui_pipeline(shaders: &mut Assets) -> PipelineDescriptor { PipelineDescriptor { @@ -70,13 +70,13 @@ pub fn build_ui_pipeline(shaders: &mut Assets) -> PipelineDescriptor { } pub mod node { - pub const UI_CAMERA: &str = "ui_camera"; + pub const CAMERA_UI: &str = "camera_ui"; pub const NODE: &str = "node"; pub const UI_PASS: &str = "ui_pass"; } pub mod camera { - pub const UI_CAMERA: &str = "UiCamera"; + pub const CAMERA_UI: &str = "CameraUi"; } pub trait UiRenderGraphBuilder { @@ -110,7 +110,7 @@ impl UiRenderGraphBuilder for RenderGraph { sample_count: msaa.samples, }); - ui_pass_node.add_camera(camera::UI_CAMERA); + ui_pass_node.add_camera(camera::CAMERA_UI); self.add_node(node::UI_PASS, ui_pass_node); self.add_slot_edge( @@ -148,12 +148,12 @@ impl UiRenderGraphBuilder for RenderGraph { .unwrap(); // setup ui camera - self.add_system_node(node::UI_CAMERA, CameraNode::new(camera::UI_CAMERA)); - self.add_node_edge(node::UI_CAMERA, node::UI_PASS).unwrap(); + self.add_system_node(node::CAMERA_UI, CameraNode::new(camera::CAMERA_UI)); + self.add_node_edge(node::CAMERA_UI, node::UI_PASS).unwrap(); self.add_system_node(node::NODE, RenderResourcesNode::::new(true)); self.add_node_edge(node::NODE, node::UI_PASS).unwrap(); let mut active_cameras = resources.get_mut::().unwrap(); - active_cameras.add(camera::UI_CAMERA); + active_cameras.add(camera::CAMERA_UI); self } } diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index b0dde1566eede..928bcc60b555d 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -1,49 +1,149 @@ use super::Node; use bevy_ecs::{Entity, Query, With, Without}; -use bevy_transform::{ - hierarchy, - prelude::{Children, Parent, Transform}, -}; +use bevy_transform::prelude::{Children, Parent, Transform}; pub const UI_Z_STEP: f32 = 0.001; pub fn ui_z_system( root_node_query: Query, Without)>, - mut node_query: Query<(Entity, &mut Transform), With>, + mut node_query: Query<&mut Transform, With>, children_query: Query<&Children>, ) { let mut current_global_z = 0.0; - for entity in root_node_query.iter() { - if let Some(result) = hierarchy::run_on_hierarchy( + current_global_z = update_hierarchy( &children_query, &mut node_query, entity, - Some(current_global_z), - Some(current_global_z), - &mut update_node_entity, - ) { - current_global_z = result; - } + current_global_z, + current_global_z, + ); } } -fn update_node_entity( - node_query: &mut Query<(Entity, &mut Transform), With>, +fn update_hierarchy( + children_query: &Query<&Children>, + node_query: &mut Query<&mut Transform, With>, entity: Entity, - parent_result: Option, - previous_result: Option, -) -> Option { - let mut z = UI_Z_STEP; - let parent_global_z = parent_result.unwrap(); - if let Some(previous_global_z) = previous_result { - z += previous_global_z - parent_global_z; - }; - let global_z = z + parent_global_z; - - if let Ok(mut transform) = node_query.get_component_mut::(entity) { - transform.translation.z = z; + parent_global_z: f32, + mut current_global_z: f32, +) -> f32 { + current_global_z += UI_Z_STEP; + if let Ok(mut transform) = node_query.get_mut(entity) { + transform.translation.z = current_global_z - parent_global_z; + } + if let Ok(children) = children_query.get(entity) { + let current_parent_global_z = current_global_z; + for child in children.iter().cloned() { + current_global_z = update_hierarchy( + children_query, + node_query, + child, + current_parent_global_z, + current_global_z, + ); + } + } + current_global_z +} +#[cfg(test)] +mod tests { + use bevy_ecs::{Commands, IntoSystem, Resources, Schedule, SystemStage, World}; + use bevy_transform::{components::Transform, hierarchy::BuildChildren}; + + use crate::Node; + + use super::{ui_z_system, UI_Z_STEP}; + + fn node_with_transform(name: &str) -> (String, Node, Transform) { + (name.to_owned(), Node::default(), Transform::default()) } - Some(global_z) + fn node_without_transform(name: &str) -> (String, Node) { + (name.to_owned(), Node::default()) + } + + fn get_steps(transform: &Transform) -> u32 { + (transform.translation.z / UI_Z_STEP).round() as u32 + } + + #[test] + fn test_ui_z_system() { + let mut world = World::default(); + let mut resources = Resources::default(); + let mut commands = Commands::default(); + commands.set_entity_reserver(world.get_entity_reserver()); + + commands.spawn(node_with_transform("0")); + + commands + .spawn(node_with_transform("1")) + .with_children(|parent| { + parent + .spawn(node_with_transform("1-0")) + .with_children(|parent| { + parent.spawn(node_with_transform("1-0-0")); + parent.spawn(node_without_transform("1-0-1")); + parent.spawn(node_with_transform("1-0-2")); + }); + parent.spawn(node_with_transform("1-1")); + parent + .spawn(node_without_transform("1-2")) + .with_children(|parent| { + parent.spawn(node_with_transform("1-2-0")); + parent.spawn(node_with_transform("1-2-1")); + parent + .spawn(node_with_transform("1-2-2")) + .with_children(|_| ()); + parent.spawn(node_with_transform("1-2-3")); + }); + parent.spawn(node_with_transform("1-3")); + }); + + commands + .spawn(node_without_transform("2")) + .with_children(|parent| { + parent + .spawn(node_with_transform("2-0")) + .with_children(|_parent| ()); + parent + .spawn(node_with_transform("2-1")) + .with_children(|parent| { + parent.spawn(node_with_transform("2-1-0")); + }); + }); + commands.apply(&mut world, &mut resources); + + let mut schedule = Schedule::default(); + let mut update_stage = SystemStage::parallel(); + update_stage.add_system(ui_z_system.system()); + schedule.add_stage("update", update_stage); + schedule.initialize_and_run(&mut world, &mut resources); + + let mut actual_result = world + .query::<(&String, &Transform)>() + .map(|(name, transform)| (name.clone(), get_steps(transform))) + .collect::>(); + actual_result.sort_unstable_by_key(|(name, _)| name.clone()); + let expected_result = vec![ + ("0".to_owned(), 1), + ("1".to_owned(), 1), + ("1-0".to_owned(), 1), + ("1-0-0".to_owned(), 1), + // 1-0-1 has no transform + ("1-0-2".to_owned(), 3), + ("1-1".to_owned(), 5), + // 1-2 has no transform + ("1-2-0".to_owned(), 1), + ("1-2-1".to_owned(), 2), + ("1-2-2".to_owned(), 3), + ("1-2-3".to_owned(), 4), + ("1-3".to_owned(), 11), + // 2 has no transform + ("2-0".to_owned(), 1), + ("2-1".to_owned(), 2), + ("2-1-0".to_owned(), 1), + ]; + assert_eq!(actual_result, expected_result); + } } diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index cde64fe0a320c..835d7a520b857 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -28,8 +28,8 @@ pub fn image_node_system( .and_then(|texture_handle| textures.get(texture_handle)) { calculated_size.size = Size { - width: texture.size.x, - height: texture.size.y, + width: texture.size.width as f32, + height: texture.size.height as f32, }; } } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index c2dc9113c1540..c5dd8d389d606 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -5,8 +5,8 @@ use bevy_math::Size; use bevy_render::{ draw::{Draw, DrawContext, Drawable}, mesh::Mesh, - prelude::Msaa, - renderer::{AssetRenderResourceBindings, RenderResourceBindings}, + prelude::{Msaa, Visible}, + renderer::RenderResourceBindings, texture::Texture, }; use bevy_sprite::{TextureAtlas, QUAD_HANDLE}; @@ -133,7 +133,7 @@ fn add_text_to_pipeline( ) { Err(TextError::NoSuchFont) => TextPipelineResult::Reschedule, Err(e @ TextError::FailedToAddGlyph(_)) => { - panic!("Fatal error when processing text: {}", e); + panic!("Fatal error when processing text: {}.", e); } Ok(()) => TextPipelineResult::Ok, } @@ -145,20 +145,22 @@ pub fn draw_text_system( msaa: Res, meshes: Res>, mut render_resource_bindings: ResMut, - mut asset_render_resource_bindings: ResMut, text_pipeline: Res, - mut query: Query<(Entity, &mut Draw, &Text, &Node, &GlobalTransform)>, + mut query: Query<(Entity, &mut Draw, &Visible, &Text, &Node, &GlobalTransform)>, ) { let font_quad = meshes.get(&QUAD_HANDLE).unwrap(); let vertex_buffer_descriptor = font_quad.get_vertex_buffer_descriptor(); - for (entity, mut draw, text, node, global_transform) in query.iter_mut() { + for (entity, mut draw, visible, text, node, global_transform) in query.iter_mut() { + if !visible.is_visible { + continue; + } + if let Some(text_glyphs) = text_pipeline.get_glyphs(&entity) { let position = global_transform.translation - (node.size / 2.0).extend(0.0); let mut drawable_text = DrawableText { render_resource_bindings: &mut render_resource_bindings, - asset_render_resource_bindings: &mut asset_render_resource_bindings, position, msaa: &msaa, text_glyphs: &text_glyphs.glyphs, diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 843b487a4e1b9..96ce6d8097bff 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_utils" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -13,9 +13,11 @@ license = "MIT" keywords = ["bevy"] [dependencies] -ahash = "0.5.3" +ahash = "0.6.1" tracing = {version = "0.1", features = ["release_max_level_info"]} instant = { version = "0.1", features = ["wasm-bindgen"] } +uuid = { version = "0.8", features = ["v4", "serde"] } [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = {version = "0.2.0", features = ["js"]} +uuid = { version = "0.8", features = ["wasm-bindgen"] } diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 64ebf1da9543e..c1afc20bd365b 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -3,6 +3,7 @@ use ahash::RandomState; pub use instant::{Duration, Instant}; use std::{future::Future, pin::Pin}; pub use tracing; +pub use uuid::Uuid; #[cfg(not(target_arch = "wasm32"))] pub type BoxedFuture<'a, T> = Pin + Send + 'a>>; diff --git a/crates/bevy_wgpu/Cargo.toml b/crates/bevy_wgpu/Cargo.toml index aa96a3d8c21cf..31d9cc1d92698 100644 --- a/crates/bevy_wgpu/Cargo.toml +++ b/crates/bevy_wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_wgpu" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -18,15 +18,15 @@ trace = ["wgpu/trace"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_core = { path = "../bevy_core", version = "0.3.0" } -bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_render = { path = "../bevy_render", version = "0.3.0" } -bevy_window = { path = "../bevy_window", version = "0.3.0" } -bevy_winit = { path = "../bevy_winit", optional = true, version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_core = { path = "../bevy_core", version = "0.4.0" } +bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_render = { path = "../bevy_render", version = "0.4.0" } +bevy_window = { path = "../bevy_window", version = "0.4.0" } +bevy_winit = { path = "../bevy_winit", optional = true, version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other wgpu = "0.6" diff --git a/crates/bevy_wgpu/src/diagnostic/wgpu_resource_diagnostics_plugin.rs b/crates/bevy_wgpu/src/diagnostic/wgpu_resource_diagnostics_plugin.rs index 0af3f94fc915c..079332c93cf89 100644 --- a/crates/bevy_wgpu/src/diagnostic/wgpu_resource_diagnostics_plugin.rs +++ b/crates/bevy_wgpu/src/diagnostic/wgpu_resource_diagnostics_plugin.rs @@ -1,7 +1,7 @@ use crate::renderer::WgpuRenderResourceContext; use bevy_app::prelude::*; use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics}; -use bevy_ecs::{Res, ResMut}; +use bevy_ecs::{IntoSystem, Res, ResMut}; use bevy_render::renderer::RenderResourceContext; #[derive(Default)] @@ -9,8 +9,8 @@ pub struct WgpuResourceDiagnosticsPlugin; impl Plugin for WgpuResourceDiagnosticsPlugin { fn build(&self, app: &mut AppBuilder) { - app.add_startup_system(Self::setup_system) - .add_system(Self::diagnostic_system); + app.add_startup_system(Self::setup_system.system()) + .add_system(Self::diagnostic_system.system()); } } diff --git a/crates/bevy_wgpu/src/lib.rs b/crates/bevy_wgpu/src/lib.rs index 27c1b3e802575..1a3ca89c2b29d 100644 --- a/crates/bevy_wgpu/src/lib.rs +++ b/crates/bevy_wgpu/src/lib.rs @@ -11,8 +11,8 @@ pub use wgpu_renderer::*; pub use wgpu_resources::*; use bevy_app::prelude::*; -use bevy_ecs::{Resources, World}; -use bevy_render::renderer::{free_shared_buffers_system, RenderResourceContext, SharedBuffers}; +use bevy_ecs::{IntoSystem, Resources, World}; +use bevy_render::renderer::{shared_buffers_update_system, RenderResourceContext, SharedBuffers}; use renderer::WgpuRenderResourceContext; #[derive(Default)] @@ -21,8 +21,11 @@ pub struct WgpuPlugin; impl Plugin for WgpuPlugin { fn build(&self, app: &mut AppBuilder) { let render_system = get_wgpu_render_system(app.resources_mut()); - app.add_system_to_stage(bevy_render::stage::RENDER, render_system) - .add_system_to_stage(bevy_render::stage::POST_RENDER, free_shared_buffers_system); + app.add_system_to_stage(bevy_render::stage::RENDER, render_system.system()) + .add_system_to_stage( + bevy_render::stage::POST_RENDER, + shared_buffers_update_system.system(), + ); } } @@ -32,8 +35,8 @@ pub fn get_wgpu_render_system(resources: &mut Resources) -> impl FnMut(&mut Worl .unwrap_or_else(WgpuOptions::default); let mut wgpu_renderer = future::block_on(WgpuRenderer::new(options)); let resource_context = WgpuRenderResourceContext::new(wgpu_renderer.device.clone()); - resources.insert::>(Box::new(resource_context.clone())); - resources.insert(SharedBuffers::new(Box::new(resource_context))); + resources.insert::>(Box::new(resource_context)); + resources.insert(SharedBuffers::new(4096)); move |world, resources| { wgpu_renderer.update(world, resources); } @@ -41,7 +44,43 @@ pub fn get_wgpu_render_system(resources: &mut Resources) -> impl FnMut(&mut Worl #[derive(Default, Clone)] pub struct WgpuOptions { - power_pref: WgpuPowerOptions, + pub backend: WgpuBackend, + pub power_pref: WgpuPowerOptions, +} + +#[derive(Clone)] +pub enum WgpuBackend { + Auto, + Vulkan, + Metal, + Dx12, + Dx11, + GL, + BrowserWgpu, +} + +impl WgpuBackend { + fn from_env() -> Self { + if let Ok(backend) = std::env::var("BEVY_WGPU_BACKEND") { + match backend.to_lowercase().as_str() { + "vulkan" => WgpuBackend::Vulkan, + "metal" => WgpuBackend::Metal, + "dx12" => WgpuBackend::Dx12, + "dx11" => WgpuBackend::Dx11, + "gl" => WgpuBackend::GL, + "webgpu" => WgpuBackend::BrowserWgpu, + other => panic!("Unknown backend: {}", other), + } + } else { + WgpuBackend::Auto + } + } +} + +impl Default for WgpuBackend { + fn default() -> Self { + Self::from_env() + } } #[derive(Clone)] diff --git a/crates/bevy_wgpu/src/renderer/wgpu_render_context.rs b/crates/bevy_wgpu/src/renderer/wgpu_render_context.rs index b42f9722b307b..f4efbcad7b2a1 100644 --- a/crates/bevy_wgpu/src/renderer/wgpu_render_context.rs +++ b/crates/bevy_wgpu/src/renderer/wgpu_render_context.rs @@ -188,11 +188,11 @@ fn get_texture_view<'a>( TextureAttachment::Name(name) => match global_render_resource_bindings.get(&name) { Some(RenderResourceBinding::Texture(resource)) => refs.textures.get(&resource).unwrap(), _ => { - panic!("Color attachment {} does not exist", name); + panic!("Color attachment {} does not exist.", name); } }, TextureAttachment::Id(render_resource) => refs.textures.get(&render_resource).unwrap_or_else(|| &refs.swap_chain_frames.get(&render_resource).unwrap().output.view), - TextureAttachment::Input(_) => panic!("Encountered unset TextureAttachment::Input. The RenderGraph executor should always set TextureAttachment::Inputs to TextureAttachment::RenderResource before running. This is a bug"), + TextureAttachment::Input(_) => panic!("Encountered unset `TextureAttachment::Input`. The `RenderGraph` executor should always set `TextureAttachment::Inputs` to `TextureAttachment::RenderResource` before running. This is a bug, please report it!"), } } diff --git a/crates/bevy_wgpu/src/renderer/wgpu_render_graph_executor.rs b/crates/bevy_wgpu/src/renderer/wgpu_render_graph_executor.rs index e8b864d9e38aa..b1fe146cfee74 100644 --- a/crates/bevy_wgpu/src/renderer/wgpu_render_graph_executor.rs +++ b/crates/bevy_wgpu/src/renderer/wgpu_render_graph_executor.rs @@ -60,14 +60,14 @@ impl WgpuRenderGraphExecutor { let outputs = if let Some(outputs) = node_outputs.get(output_node) { outputs } else { - panic!("node inputs not set") + panic!("Node inputs not set.") }; let output_resource = - outputs.get(*output_index).expect("output should be set"); + outputs.get(*output_index).expect("Output should be set."); input_slot.resource = Some(output_resource); } else { - panic!("no edge connected to input") + panic!("No edge connected to input.") } } node_state.node.update( diff --git a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs index a2a7ae5d20df2..f235c359ce036 100644 --- a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs +++ b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs @@ -12,7 +12,7 @@ use bevy_render::{ BindGroup, BufferId, BufferInfo, RenderResourceBinding, RenderResourceContext, RenderResourceId, SamplerId, TextureId, }, - shader::{glsl_to_spirv, Shader, ShaderSource}, + shader::{glsl_to_spirv, Shader, ShaderError, ShaderSource}, texture::{Extent3d, SamplerDescriptor, TextureDescriptor}, }; use bevy_utils::tracing::trace; @@ -88,7 +88,7 @@ impl WgpuRenderResourceContext { layout: wgpu::TextureDataLayout { offset: source_offset, bytes_per_row: source_bytes_per_row, - rows_per_image: 0, // NOTE: Example sets this to 0, but should it be height? + rows_per_image: size.height, }, }, wgpu::TextureCopyView { @@ -227,8 +227,8 @@ impl RenderResourceContext for WgpuRenderResourceContext { } fn remove_buffer(&self, buffer: BufferId) { - let mut buffers = self.resources.buffers.write(); let mut buffer_infos = self.resources.buffer_infos.write(); + let mut buffers = self.resources.buffers.write(); buffers.remove(&buffer); buffer_infos.remove(&buffer); @@ -251,7 +251,7 @@ impl RenderResourceContext for WgpuRenderResourceContext { fn create_shader_module_from_source(&self, shader_handle: &Handle, shader: &Shader) { let mut shader_modules = self.resources.shader_modules.write(); - let spirv: Cow<[u32]> = shader.get_spirv(None).into(); + let spirv: Cow<[u32]> = shader.get_spirv(None).unwrap().into(); let shader_module = self .device .create_shader_module(wgpu::ShaderModuleSource::SpirV(spirv)); @@ -279,7 +279,7 @@ impl RenderResourceContext for WgpuRenderResourceContext { let swap_chain_descriptor: wgpu::SwapChainDescriptor = window.wgpu_into(); let surface = surfaces .get(&window.id()) - .expect("No surface found for window"); + .expect("No surface found for window."); let swap_chain = self .device .create_swap_chain(surface, &swap_chain_descriptor); @@ -522,6 +522,10 @@ impl RenderResourceContext for WgpuRenderResourceContext { self.resources.bind_groups.write().clear(); } + fn remove_stale_bind_groups(&self) { + self.resources.remove_stale_bind_groups(); + } + fn get_buffer_info(&self, buffer: BufferId) -> Option { self.resources.buffer_infos.read().get(&buffer).cloned() } @@ -548,7 +552,7 @@ impl RenderResourceContext for WgpuRenderResourceContext { let data = buffer_slice.map_async(wgpu::MapMode::Write); self.device.poll(wgpu::Maintain::Wait); if future::block_on(data).is_err() { - panic!("failed to map buffer to host"); + panic!("Failed to map buffer to host."); } } @@ -570,14 +574,18 @@ impl RenderResourceContext for WgpuRenderResourceContext { } } - fn get_specialized_shader(&self, shader: &Shader, macros: Option<&[String]>) -> Shader { + fn get_specialized_shader( + &self, + shader: &Shader, + macros: Option<&[String]>, + ) -> Result { let spirv_data = match shader.source { ShaderSource::Spirv(ref bytes) => bytes.clone(), - ShaderSource::Glsl(ref source) => glsl_to_spirv(&source, shader.stage, macros), + ShaderSource::Glsl(ref source) => glsl_to_spirv(&source, shader.stage, macros)?, }; - Shader { + Ok(Shader { source: ShaderSource::Spirv(spirv_data), ..*shader - } + }) } } diff --git a/crates/bevy_wgpu/src/wgpu_render_pass.rs b/crates/bevy_wgpu/src/wgpu_render_pass.rs index ee94b0189986c..e68337df2b7fb 100644 --- a/crates/bevy_wgpu/src/wgpu_render_pass.rs +++ b/crates/bevy_wgpu/src/wgpu_render_pass.rs @@ -74,6 +74,10 @@ impl<'a> RenderPass for WgpuRenderPass<'a> { } else { EMPTY }; + self.wgpu_resources + .used_bind_group_sender + .send(bind_group) + .unwrap(); trace!( "set bind group {:?} {:?}: {:?}", @@ -93,7 +97,7 @@ impl<'a> RenderPass for WgpuRenderPass<'a> { .render_pipelines .get(pipeline_handle) .expect( - "Attempted to use a pipeline that does not exist in this RenderPass's RenderContext", + "Attempted to use a pipeline that does not exist in this `RenderPass`'s `RenderContext`.", ); self.render_pass.set_pipeline(pipeline); } diff --git a/crates/bevy_wgpu/src/wgpu_renderer.rs b/crates/bevy_wgpu/src/wgpu_renderer.rs index 7b39c2f5e6559..acd80b5f81776 100644 --- a/crates/bevy_wgpu/src/wgpu_renderer.rs +++ b/crates/bevy_wgpu/src/wgpu_renderer.rs @@ -1,6 +1,6 @@ use crate::{ renderer::{WgpuRenderGraphExecutor, WgpuRenderResourceContext}, - WgpuOptions, WgpuPowerOptions, + WgpuBackend, WgpuOptions, WgpuPowerOptions, }; use bevy_app::prelude::*; use bevy_ecs::{Resources, World}; @@ -22,7 +22,16 @@ pub struct WgpuRenderer { impl WgpuRenderer { pub async fn new(options: WgpuOptions) -> Self { - let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); + let backend = match options.backend { + WgpuBackend::Auto => wgpu::BackendBit::PRIMARY, + WgpuBackend::Vulkan => wgpu::BackendBit::VULKAN, + WgpuBackend::Metal => wgpu::BackendBit::METAL, + WgpuBackend::Dx12 => wgpu::BackendBit::DX12, + WgpuBackend::Dx11 => wgpu::BackendBit::DX11, + WgpuBackend::GL => wgpu::BackendBit::GL, + WgpuBackend::BrowserWgpu => wgpu::BackendBit::BROWSER_WEBGPU, + }; + let instance = wgpu::Instance::new(backend); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { @@ -78,7 +87,7 @@ impl WgpuRenderer { { let window = windows .get(window_created_event.id) - .expect("Received window created event for non-existent window"); + .expect("Received window created event for non-existent window."); #[cfg(feature = "bevy_winit")] { let winit_windows = resources.get::().unwrap(); @@ -115,6 +124,6 @@ impl WgpuRenderer { let render_resource_context = resources.get::>().unwrap(); render_resource_context.drop_all_swap_chain_textures(); - render_resource_context.clear_bind_groups(); + render_resource_context.remove_stale_bind_groups(); } } diff --git a/crates/bevy_wgpu/src/wgpu_resources.rs b/crates/bevy_wgpu/src/wgpu_resources.rs index f36f3644882a3..aa3e76c6beb37 100644 --- a/crates/bevy_wgpu/src/wgpu_resources.rs +++ b/crates/bevy_wgpu/src/wgpu_resources.rs @@ -7,6 +7,7 @@ use bevy_render::{ }; use bevy_utils::HashMap; use bevy_window::WindowId; +use crossbeam_channel::{Receiver, Sender, TryRecvError}; use parking_lot::{RwLock, RwLockReadGuard}; use std::sync::Arc; @@ -45,6 +46,7 @@ pub struct WgpuResourcesReadLock<'a> { pub render_pipelines: RwLockReadGuard<'a, HashMap, wgpu::RenderPipeline>>, pub bind_groups: RwLockReadGuard<'a, HashMap>, + pub used_bind_group_sender: Sender, } impl<'a> WgpuResourcesReadLock<'a> { @@ -55,6 +57,7 @@ impl<'a> WgpuResourcesReadLock<'a> { swap_chain_frames: &self.swap_chain_frames, render_pipelines: &self.render_pipelines, bind_groups: &self.bind_groups, + used_bind_group_sender: &self.used_bind_group_sender, } } } @@ -67,6 +70,7 @@ pub struct WgpuResourceRefs<'a> { pub swap_chain_frames: &'a HashMap, pub render_pipelines: &'a HashMap, wgpu::RenderPipeline>, pub bind_groups: &'a HashMap, + pub used_bind_group_sender: &'a Sender, } #[derive(Default, Clone, Debug)] @@ -85,6 +89,7 @@ pub struct WgpuResources { pub bind_groups: Arc>>, pub bind_group_layouts: Arc>>, pub asset_resources: Arc>>, + pub bind_group_counter: BindGroupCounter, } impl WgpuResources { @@ -95,6 +100,7 @@ impl WgpuResources { swap_chain_frames: self.swap_chain_frames.read(), render_pipelines: self.render_pipelines.read(), bind_groups: self.bind_groups.read(), + used_bind_group_sender: self.bind_group_counter.used_bind_group_sender.clone(), } } @@ -109,4 +115,64 @@ impl WgpuResources { false } } + + pub fn remove_stale_bind_groups(&self) { + let mut bind_groups = self.bind_groups.write(); + self.bind_group_counter + .remove_stale_bind_groups(&mut bind_groups); + } +} + +#[derive(Clone, Debug)] +pub struct BindGroupCounter { + pub used_bind_group_sender: Sender, + pub used_bind_group_receiver: Receiver, + pub bind_group_usage_counts: Arc>>, +} + +impl BindGroupCounter { + pub fn remove_stale_bind_groups( + &self, + bind_groups: &mut HashMap, + ) { + let mut bind_group_usage_counts = self.bind_group_usage_counts.write(); + loop { + let bind_group = match self.used_bind_group_receiver.try_recv() { + Ok(bind_group) => bind_group, + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => panic!("used bind group channel disconnected"), + }; + + let count = bind_group_usage_counts.entry(bind_group).or_insert(0); + // free every two frames + *count = 2; + } + + for info in bind_groups.values_mut() { + info.bind_groups.retain(|id, _| { + let retain = { + // if a value hasn't been counted yet, give it two frames of leeway + let count = bind_group_usage_counts.entry(*id).or_insert(2); + *count -= 1; + *count > 0 + }; + if !retain { + bind_group_usage_counts.remove(&id); + } + + retain + }) + } + } +} + +impl Default for BindGroupCounter { + fn default() -> Self { + let (send, recv) = crossbeam_channel::unbounded(); + BindGroupCounter { + used_bind_group_sender: send, + used_bind_group_receiver: recv, + bind_group_usage_counts: Default::default(), + } + } } diff --git a/crates/bevy_wgpu/src/wgpu_type_converter.rs b/crates/bevy_wgpu/src/wgpu_type_converter.rs index 8ffc846b63f44..e6e176263e045 100644 --- a/crates/bevy_wgpu/src/wgpu_type_converter.rs +++ b/crates/bevy_wgpu/src/wgpu_type_converter.rs @@ -564,10 +564,10 @@ impl WgpuFrom<&Window> for wgpu::SwapChainDescriptor { wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, format: TextureFormat::default().wgpu_into(), - width: window.width(), - height: window.height(), + width: window.physical_width(), + height: window.physical_height(), present_mode: if window.vsync() { - wgpu::PresentMode::Fifo + wgpu::PresentMode::Mailbox } else { wgpu::PresentMode::Immediate }, diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index cb211b1bb4c39..6c340562decfe 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_window" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,14 +14,12 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other -uuid = { version = "0.8", features = ["v4", "serde"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -uuid = { version = "0.8", features = ["wasm-bindgen"] } web-sys = "0.3" diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index f441bdd6f7bd5..ddb77f666cdc0 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -5,8 +5,8 @@ use bevy_math::Vec2; #[derive(Debug, Clone)] pub struct WindowResized { pub id: WindowId, - pub width: usize, - pub height: usize, + pub width: f32, + pub height: f32, } /// An event that indicates that a new window should be created. @@ -41,9 +41,26 @@ pub struct CursorMoved { pub position: Vec2, } +#[derive(Debug, Clone)] +pub struct CursorEntered { + pub id: WindowId, +} + +#[derive(Debug, Clone)] +pub struct CursorLeft { + pub id: WindowId, +} + /// An event that is sent whenever a window receives a character from the OS or underlying system. #[derive(Debug, Clone)] pub struct ReceivedCharacter { pub id: WindowId, pub char: char, } + +/// An event that indicates a window has received or lost focus. +#[derive(Debug, Clone)] +pub struct WindowFocused { + pub id: WindowId, + pub focused: bool, +} diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index 0c07496c0028c..0ebfe3ad576b6 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -3,13 +3,17 @@ mod system; mod window; mod windows; +use bevy_ecs::IntoSystem; pub use event::*; pub use system::*; pub use window::*; pub use windows::*; pub mod prelude { - pub use crate::{CursorMoved, ReceivedCharacter, Window, WindowDescriptor, Windows}; + pub use crate::{ + CursorEntered, CursorLeft, CursorMoved, ReceivedCharacter, Window, WindowDescriptor, + Windows, + }; } use bevy_app::prelude::*; @@ -36,7 +40,10 @@ impl Plugin for WindowPlugin { .add_event::() .add_event::() .add_event::() + .add_event::() + .add_event::() .add_event::() + .add_event::() .init_resource::(); if self.add_primary_window { @@ -53,7 +60,7 @@ impl Plugin for WindowPlugin { } if self.exit_on_close { - app.add_system(exit_on_window_close_system); + app.add_system(exit_on_window_close_system.system()); } } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 0b6c45bdff2ad..ab173dd006e6f 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,4 +1,5 @@ -use uuid::Uuid; +use bevy_math::Vec2; +use bevy_utils::Uuid; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct WindowId(Uuid); @@ -31,17 +32,36 @@ impl Default for WindowId { } } +/// An operating system window that can present content and receive user input. +/// +/// ## Window Sizes +/// +/// There are three sizes associated with a window. The physical size which is +/// the height and width in physical pixels on the monitor. The logical size +/// which is the physical size scaled by an operating system provided factor to +/// account for monitors with differing pixel densities or user preference. And +/// the requested size, measured in logical pixels, which is the value submitted +/// to the API when creating the window, or requesting that it be resized. +/// +/// The actual size, in logical pixels, of the window may not match the +/// requested size due to operating system limits on the window size, or the +/// quantization of the logical size when converting the physical size to the +/// logical size through the scaling factor. #[derive(Debug)] pub struct Window { id: WindowId, - width: u32, - height: u32, + requested_width: f32, + requested_height: f32, + physical_width: u32, + physical_height: u32, + scale_factor: f64, title: String, vsync: bool, resizable: bool, decorations: bool, cursor_visible: bool, cursor_locked: bool, + cursor_position: Option, mode: WindowMode, #[cfg(target_arch = "wasm32")] pub canvas: Option, @@ -58,8 +78,7 @@ pub enum WindowCommand { title: String, }, SetResolution { - width: u32, - height: u32, + resolution: (f32, f32), }, SetVsync { vsync: bool, @@ -76,6 +95,12 @@ pub enum WindowCommand { SetCursorVisibility { visible: bool, }, + SetCursorPosition { + position: Vec2, + }, + SetMaximized { + maximized: bool, + }, } /// Defines the way a window is displayed @@ -91,17 +116,27 @@ pub enum WindowMode { } impl Window { - pub fn new(id: WindowId, window_descriptor: &WindowDescriptor) -> Self { + pub fn new( + id: WindowId, + window_descriptor: &WindowDescriptor, + physical_width: u32, + physical_height: u32, + scale_factor: f64, + ) -> Self { Window { id, - height: window_descriptor.height, - width: window_descriptor.width, + requested_width: window_descriptor.width, + requested_height: window_descriptor.height, + physical_width, + physical_height, + scale_factor, title: window_descriptor.title.clone(), vsync: window_descriptor.vsync, resizable: window_descriptor.resizable, decorations: window_descriptor.decorations, cursor_visible: window_descriptor.cursor_visible, cursor_locked: window_descriptor.cursor_locked, + cursor_position: None, mode: window_descriptor.mode, #[cfg(target_arch = "wasm32")] canvas: window_descriptor.canvas.clone(), @@ -114,27 +149,85 @@ impl Window { self.id } + /// The current logical width of the window's client area. + #[inline] + pub fn width(&self) -> f32 { + (self.physical_width as f64 / self.scale_factor) as f32 + } + + /// The current logical height of the window's client area. #[inline] - pub fn width(&self) -> u32 { - self.width + pub fn height(&self) -> f32 { + (self.physical_height as f64 / self.scale_factor) as f32 } + /// The requested window client area width in logical pixels from window + /// creation or the last call to [set_resolution](Window::set_resolution). + /// + /// This may differ from the actual width depending on OS size limits and + /// the scaling factor for high DPI monitors. #[inline] - pub fn height(&self) -> u32 { - self.height + pub fn requested_width(&self) -> f32 { + self.requested_width } - pub fn set_resolution(&mut self, width: u32, height: u32) { - self.width = width; - self.height = height; + /// The requested window client area height in logical pixels from window + /// creation or the last call to [set_resolution](Window::set_resolution). + /// + /// This may differ from the actual width depending on OS size limits and + /// the scaling factor for high DPI monitors. + #[inline] + pub fn requested_height(&self) -> f32 { + self.requested_height + } + + /// The window's client area width in physical pixels. + #[inline] + pub fn physical_width(&self) -> u32 { + self.physical_width + } + + /// The window's client area height in physical pixels. + #[inline] + pub fn physical_height(&self) -> u32 { + self.physical_height + } + + #[inline] + pub fn set_maximized(&mut self, maximized: bool) { self.command_queue - .push(WindowCommand::SetResolution { width, height }); + .push(WindowCommand::SetMaximized { maximized }); + } + + /// Request the OS to resize the window such the the client area matches the + /// specified width and height. + pub fn set_resolution(&mut self, width: f32, height: f32) { + self.requested_width = width; + self.requested_height = height; + self.command_queue.push(WindowCommand::SetResolution { + resolution: (self.requested_width, self.requested_height), + }); } - #[doc(hidden)] - pub fn update_resolution_from_backend(&mut self, width: u32, height: u32) { - self.width = width; - self.height = height; + #[allow(missing_docs)] + #[inline] + pub fn update_scale_factor_from_backend(&mut self, scale_factor: f64) { + self.scale_factor = scale_factor; + } + + #[allow(missing_docs)] + #[inline] + pub fn update_actual_size_from_backend(&mut self, physical_width: u32, physical_height: u32) { + self.physical_width = physical_width; + self.physical_height = physical_height; + } + + /// The ratio of physical pixels to logical pixels + /// + /// `physical_pixels = logical_pixels * scale_factor` + #[inline] + pub fn scale_factor(&self) -> f64 { + self.scale_factor } #[inline] @@ -203,6 +296,22 @@ impl Window { }); } + #[inline] + pub fn cursor_position(&self) -> Option { + self.cursor_position + } + + pub fn set_cursor_position(&mut self, position: Vec2) { + self.command_queue + .push(WindowCommand::SetCursorPosition { position }); + } + + #[allow(missing_docs)] + #[inline] + pub fn update_cursor_position_from_backend(&mut self, cursor_position: Option) { + self.cursor_position = cursor_position; + } + #[inline] pub fn mode(&self) -> WindowMode { self.mode @@ -212,7 +321,7 @@ impl Window { self.mode = mode; self.command_queue.push(WindowCommand::SetWindowMode { mode, - resolution: (self.width, self.height), + resolution: (self.physical_width, self.physical_height), }); } @@ -224,8 +333,8 @@ impl Window { #[derive(Debug, Clone)] pub struct WindowDescriptor { - pub width: u32, - pub height: u32, + pub width: f32, + pub height: f32, pub title: String, pub vsync: bool, pub resizable: bool, @@ -241,8 +350,8 @@ impl Default for WindowDescriptor { fn default() -> Self { WindowDescriptor { title: "bevy".to_string(), - width: 1280, - height: 720, + width: 1280., + height: 720., vsync: true, resizable: true, decorations: true, diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 7403b52396189..f9ef5394d147a 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_winit" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -18,17 +18,17 @@ x11 = ["winit/x11"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_input = { path = "../bevy_input", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_window = { path = "../bevy_window", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_input = { path = "../bevy_input", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_window = { path = "../bevy_window", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } # other -winit = { version = "0.23.0", default-features = false } +winit = { version = "0.24.0", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] -winit = { version = "0.23.0", features = ["web-sys"], default-features = false } +winit = { version = "0.24.0", features = ["web-sys"], default-features = false } wasm-bindgen = { version = "0.2" } web-sys = "0.3" diff --git a/crates/bevy_winit/src/converters.rs b/crates/bevy_winit/src/converters.rs index 060096de401c1..19d6ed903be5d 100644 --- a/crates/bevy_winit/src/converters.rs +++ b/crates/bevy_winit/src/converters.rs @@ -30,7 +30,10 @@ pub fn convert_mouse_button(mouse_button: winit::event::MouseButton) -> MouseBut } } -pub fn convert_touch_input(touch_input: winit::event::Touch) -> TouchInput { +pub fn convert_touch_input( + touch_input: winit::event::Touch, + location: winit::dpi::LogicalPosition, +) -> TouchInput { TouchInput { phase: match touch_input.phase { winit::event::TouchPhase::Started => TouchPhase::Started, @@ -38,7 +41,7 @@ pub fn convert_touch_input(touch_input: winit::event::Touch) -> TouchInput { winit::event::TouchPhase::Ended => TouchPhase::Ended, winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled, }, - position: Vec2::new(touch_input.location.x as f32, touch_input.location.y as f32), + position: Vec2::new(location.x as f32, location.y as f32), force: touch_input.force.map(|f| match f { winit::event::Force::Calibrated { force, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index f5c05e8db6ee2..9302bf46696d9 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -10,12 +10,12 @@ pub use winit_config::*; pub use winit_windows::*; use bevy_app::{prelude::*, AppExit}; -use bevy_ecs::{Resources, World}; +use bevy_ecs::{IntoSystem, Resources, World}; use bevy_math::Vec2; -use bevy_utils::tracing::trace; +use bevy_utils::tracing::{error, trace}; use bevy_window::{ - CreateWindow, CursorMoved, ReceivedCharacter, Window, WindowCloseRequested, WindowCreated, - WindowResized, Windows, + CreateWindow, CursorEntered, CursorLeft, CursorMoved, ReceivedCharacter, WindowCloseRequested, + WindowCreated, WindowFocused, WindowResized, Windows, }; use winit::{ event::{self, DeviceEvent, Event, WindowEvent}, @@ -27,13 +27,9 @@ pub struct WinitPlugin; impl Plugin for WinitPlugin { fn build(&self, app: &mut AppBuilder) { - app - // TODO: It would be great to provide a raw winit WindowEvent here, but the lifetime on it is - // stopping us. there are plans to remove the lifetime: https://github.com/rust-windowing/winit/pull/1456 - // .add_event::() - .init_resource::() + app.init_resource::() .set_runner(winit_runner) - .add_system(change_window); + .add_system(change_window.system()); } } @@ -71,9 +67,14 @@ fn change_window(_: &mut World, resources: &mut Resources) { let window = winit_windows.get_window(id).unwrap(); window.set_title(&title); } - bevy_window::WindowCommand::SetResolution { width, height } => { + bevy_window::WindowCommand::SetResolution { + resolution: (logical_width, logical_height), + } => { let window = winit_windows.get_window(id).unwrap(); - window.set_inner_size(winit::dpi::PhysicalSize::new(width, height)); + window.set_inner_size(winit::dpi::LogicalSize::new( + logical_width, + logical_height, + )); } bevy_window::WindowCommand::SetVsync { .. } => (), bevy_window::WindowCommand::SetResizable { resizable } => { @@ -86,12 +87,28 @@ fn change_window(_: &mut World, resources: &mut Resources) { } bevy_window::WindowCommand::SetCursorLockMode { locked } => { let window = winit_windows.get_window(id).unwrap(); - window.set_cursor_grab(locked).unwrap(); + window + .set_cursor_grab(locked) + .unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e)); } bevy_window::WindowCommand::SetCursorVisibility { visible } => { let window = winit_windows.get_window(id).unwrap(); window.set_cursor_visible(visible); } + bevy_window::WindowCommand::SetCursorPosition { position } => { + let window = winit_windows.get_window(id).unwrap(); + let inner_size = window.inner_size().to_logical::(window.scale_factor()); + window + .set_cursor_position(winit::dpi::LogicalPosition::new( + position.x, + inner_size.height - position.y, + )) + .unwrap_or_else(|e| error!("Unable to set cursor position: {}", e)); + } + bevy_window::WindowCommand::SetMaximized { maximized } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_maximized(maximized) + } } } } @@ -120,7 +137,7 @@ fn run_return(event_loop: &mut EventLoop<()>, event_handler: F) where F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow), { - use winit::platform::desktop::EventLoopExtDesktop; + use winit::platform::run_return::EventLoopExtRunReturn; event_loop.run_return(event_handler) } @@ -147,8 +164,6 @@ pub fn winit_runner(mut app: App) { app.resources.insert_thread_local(event_loop.create_proxy()); - app.initialize(); - trace!("Entering winit event loop"); let should_return_from_run = app @@ -168,29 +183,25 @@ pub fn winit_runner(mut app: App) { } match event { - event::Event::WindowEvent { - event: WindowEvent::Resized(size), - window_id: winit_window_id, - .. - } => { - let winit_windows = app.resources.get_mut::().unwrap(); - let mut windows = app.resources.get_mut::().unwrap(); - let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); - let window = windows.get_mut(window_id).unwrap(); - window.update_resolution_from_backend(size.width, size.height); - - let mut resize_events = app.resources.get_mut::>().unwrap(); - resize_events.send(WindowResized { - id: window_id, - height: window.height() as usize, - width: window.width() as usize, - }); - } event::Event::WindowEvent { event, window_id: winit_window_id, .. } => match event { + WindowEvent::Resized(size) => { + let winit_windows = app.resources.get_mut::().unwrap(); + let mut windows = app.resources.get_mut::().unwrap(); + let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); + let window = windows.get_mut(window_id).unwrap(); + window.update_actual_size_from_backend(size.width, size.height); + let mut resize_events = + app.resources.get_mut::>().unwrap(); + resize_events.send(WindowResized { + id: window_id, + width: window.width(), + height: window.height(), + }); + } WindowEvent::CloseRequested => { let mut window_close_requested_events = app .resources @@ -209,16 +220,43 @@ pub fn winit_runner(mut app: App) { let mut cursor_moved_events = app.resources.get_mut::>().unwrap(); let winit_windows = app.resources.get_mut::().unwrap(); + let mut windows = app.resources.get_mut::().unwrap(); let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); - let window = winit_windows.get_window(window_id).unwrap(); - let inner_size = window.inner_size(); + let winit_window = winit_windows.get_window(window_id).unwrap(); + let window = windows.get_mut(window_id).unwrap(); + let position = position.to_logical(winit_window.scale_factor()); + let inner_size = winit_window + .inner_size() + .to_logical::(winit_window.scale_factor()); + // move origin to bottom left - let y_position = inner_size.height as f32 - position.y as f32; + let y_position = inner_size.height - position.y; + + let position = Vec2::new(position.x, y_position); + window.update_cursor_position_from_backend(Some(position)); + cursor_moved_events.send(CursorMoved { id: window_id, - position: Vec2::new(position.x as f32, y_position as f32), + position, }); } + WindowEvent::CursorEntered { .. } => { + let mut cursor_entered_events = + app.resources.get_mut::>().unwrap(); + let winit_windows = app.resources.get_mut::().unwrap(); + let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); + cursor_entered_events.send(CursorEntered { id: window_id }); + } + WindowEvent::CursorLeft { .. } => { + let mut cursor_left_events = + app.resources.get_mut::>().unwrap(); + let winit_windows = app.resources.get_mut::().unwrap(); + let mut windows = app.resources.get_mut::().unwrap(); + let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); + let window = windows.get_mut(window_id).unwrap(); + window.update_cursor_position_from_backend(None); + cursor_left_events.send(CursorLeft { id: window_id }); + } WindowEvent::MouseInput { state, button, .. } => { let mut mouse_button_input_events = app.resources.get_mut::>().unwrap(); @@ -247,16 +285,22 @@ pub fn winit_runner(mut app: App) { }); } }, - WindowEvent::Touch(mut touch) => { + WindowEvent::Touch(touch) => { let mut touch_input_events = app.resources.get_mut::>().unwrap(); + + let winit_windows = app.resources.get_mut::().unwrap(); let windows = app.resources.get_mut::().unwrap(); + let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); + let winit_window = winit_windows.get_window(window_id).unwrap(); + let mut location = touch.location.to_logical(winit_window.scale_factor()); + // FIXME?: On Android window start is top while on PC/Linux/OSX on bottom if cfg!(target_os = "android") { let window_height = windows.get_primary().unwrap().height(); - touch.location.y = window_height as f64 - touch.location.y; + location.y = window_height - location.y; } - touch_input_events.send(converters::convert_touch_input(touch)); + touch_input_events.send(converters::convert_touch_input(touch, location)); } WindowEvent::ReceivedCharacter(c) => { let mut char_input_events = app @@ -272,16 +316,51 @@ pub fn winit_runner(mut app: App) { char: c, }) } + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + } => { + let winit_windows = app.resources.get_mut::().unwrap(); + let mut windows = app.resources.get_mut::().unwrap(); + let window_id = winit_windows.get_window_id(winit_window_id).unwrap(); + let window = windows.get_mut(window_id).unwrap(); + window.update_actual_size_from_backend( + new_inner_size.width, + new_inner_size.height, + ); + window.update_scale_factor_from_backend(scale_factor); + // should we send a resize event to indicate the change in + // logical size? + } + WindowEvent::Focused(focused) => { + let mut focused_events = + app.resources.get_mut::>().unwrap(); + let winit_windows = app.resources.get_mut::().unwrap(); + match (winit_windows.get_window_id(winit_window_id), focused) { + (Some(window_id), _) => focused_events.send(WindowFocused { + id: window_id, + focused, + }), + // unfocus event for an unknown window, ignore it + (None, false) => (), + // focus event on an unknown window, this is an error + _ => panic!( + "Focused(true) event on unknown window {:?}", + winit_window_id + ), + } + } _ => {} }, - event::Event::DeviceEvent { ref event, .. } => { - if let DeviceEvent::MouseMotion { delta } = event { - let mut mouse_motion_events = - app.resources.get_mut::>().unwrap(); - mouse_motion_events.send(MouseMotion { - delta: Vec2::new(delta.0 as f32, delta.1 as f32), - }); - } + event::Event::DeviceEvent { + event: DeviceEvent::MouseMotion { delta }, + .. + } => { + let mut mouse_motion_events = + app.resources.get_mut::>().unwrap(); + mouse_motion_events.send(MouseMotion { + delta: Vec2::new(delta.0 as f32, delta.1 as f32), + }); } event::Event::MainEventsCleared => { handle_create_window_events( @@ -311,10 +390,14 @@ fn handle_create_window_events( let create_window_events = resources.get::>().unwrap(); let mut window_created_events = resources.get_mut::>().unwrap(); for create_window_event in create_window_event_reader.iter(&create_window_events) { - let window = Window::new(create_window_event.id, &create_window_event.descriptor); - winit_windows.create_window(event_loop, &window); - let window_id = window.id(); + let window = winit_windows.create_window( + event_loop, + create_window_event.id, + &create_window_event.descriptor, + ); windows.add(window); - window_created_events.send(WindowCreated { id: window_id }); + window_created_events.send(WindowCreated { + id: create_window_event.id, + }); } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index ec86a1a800dee..f2bee4c6d73f5 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -1,5 +1,5 @@ use bevy_utils::HashMap; -use bevy_window::{Window, WindowId, WindowMode}; +use bevy_window::{Window, WindowDescriptor, WindowId, WindowMode}; #[derive(Debug, Default)] pub struct WinitWindows { @@ -12,8 +12,9 @@ impl WinitWindows { pub fn create_window( &mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>, - window: &Window, - ) { + window_id: WindowId, + window_descriptor: &WindowDescriptor, + ) -> Window { #[cfg(target_os = "windows")] let mut winit_window_builder = { use winit::platform::windows::WindowBuilderExtWindows; @@ -23,7 +24,7 @@ impl WinitWindows { #[cfg(not(target_os = "windows"))] let mut winit_window_builder = winit::window::WindowBuilder::new(); - winit_window_builder = match window.mode() { + winit_window_builder = match window_descriptor.mode { WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some( winit::window::Fullscreen::Borderless(event_loop.primary_monitor()), )), @@ -31,64 +32,62 @@ impl WinitWindows { winit::window::Fullscreen::Exclusive(match use_size { true => get_fitting_videomode( &event_loop.primary_monitor().unwrap(), - window.width(), - window.height(), + window_descriptor.width as u32, + window_descriptor.height as u32, ), false => get_best_videomode(&event_loop.primary_monitor().unwrap()), }), )), _ => winit_window_builder - .with_inner_size(winit::dpi::PhysicalSize::new( - window.width(), - window.height(), + .with_inner_size(winit::dpi::LogicalSize::new( + window_descriptor.width, + window_descriptor.height, )) - .with_resizable(window.resizable()) - .with_decorations(window.decorations()), + .with_resizable(window_descriptor.resizable) + .with_decorations(window_descriptor.decorations), }; #[allow(unused_mut)] - let mut winit_window_builder = winit_window_builder.with_title(window.title()); + let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title); #[cfg(target_arch = "wasm32")] { use wasm_bindgen::JsCast; use winit::platform::web::WindowBuilderExtWebSys; - if let Some(selector) = &window.canvas { + if let Some(selector) = &window_descriptor.canvas { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let canvas = document .query_selector(&selector) - .expect("Cannot query for canvas element"); + .expect("Cannot query for canvas element."); if let Some(canvas) = canvas { let canvas = canvas.dyn_into::().ok(); winit_window_builder = winit_window_builder.with_canvas(canvas); } else { - panic!("Cannot find element: {}", selector); + panic!("Cannot find element: {}.", selector); } } } let winit_window = winit_window_builder.build(&event_loop).unwrap(); - match winit_window.set_cursor_grab(window.cursor_locked()) { + match winit_window.set_cursor_grab(window_descriptor.cursor_locked) { Ok(_) => {} Err(winit::error::ExternalError::NotSupported(_)) => {} Err(err) => Err(err).unwrap(), } - winit_window.set_cursor_visible(window.cursor_visible()); + winit_window.set_cursor_visible(window_descriptor.cursor_visible); - self.window_id_to_winit - .insert(window.id(), winit_window.id()); - self.winit_to_window_id - .insert(winit_window.id(), window.id()); + self.window_id_to_winit.insert(window_id, winit_window.id()); + self.winit_to_window_id.insert(winit_window.id(), window_id); #[cfg(target_arch = "wasm32")] { use winit::platform::web::WindowExtWebSys; - if window.canvas.is_none() { + if window_descriptor.canvas.is_none() { let canvas = winit_window.canvas(); let window = web_sys::window().unwrap(); @@ -96,11 +95,21 @@ impl WinitWindows { let body = document.body().unwrap(); body.append_child(&canvas) - .expect("Append canvas to HTML body"); + .expect("Append canvas to HTML body."); } } + let inner_size = winit_window.inner_size(); + let scale_factor = winit_window.scale_factor(); self.windows.insert(winit_window.id(), winit_window); + + Window::new( + window_id, + &window_descriptor, + inner_size.width, + inner_size.height, + scale_factor, + ) } pub fn get_window(&self, id: WindowId) -> Option<&winit::window::Window> { diff --git a/docs/linux_dependencies.md b/docs/linux_dependencies.md index 2992f1b9dcdf5..b620a1317e014 100644 --- a/docs/linux_dependencies.md +++ b/docs/linux_dependencies.md @@ -9,6 +9,11 @@ If you don't see your distro present in the list, feel free to add the instructi ```bash sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev ``` +If you want to Enable Fast Compiles +```bash +sudo apt-get install clang +``` + ### Windows Subsystem for Linux (WSL 2) Graphics and audio need to be configured for them to work with WSL 2 backend. diff --git a/examples/2d/contributors.rs b/examples/2d/contributors.rs index 5941c82dad43b..b1f562737219d 100644 --- a/examples/2d/contributors.rs +++ b/examples/2d/contributors.rs @@ -9,11 +9,11 @@ use std::{ fn main() { App::build() .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .add_system(velocity_system) - .add_system(move_system) - .add_system(collision_system) - .add_system(select_system) + .add_startup_system(setup.system()) + .add_system(velocity_system.system()) + .add_system(move_system.system()) + .add_system(collision_system.system()) + .add_system(select_system.system()) .run(); } @@ -56,7 +56,7 @@ fn setup( commands .spawn(Camera2dBundle::default()) - .spawn(UiCameraBundle::default()); + .spawn(CameraUiBundle::default()); let mut sel = ContributorSelection { order: vec![], @@ -134,10 +134,11 @@ fn select_system( mut dq: Query, With>, mut tq: Query, With>, mut q: Query<(&Contributor, &Handle, &mut Transform)>, + time: Res