From 66ad5cee375995ba6f1b5c9707051f4e1b1610f9 Mon Sep 17 00:00:00 2001 From: Philipp Knecht Date: Wed, 23 Dec 2020 11:18:57 +0100 Subject: [PATCH] refresh (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * do not spend time drawing text with is_visible = false (#893) do not draw text with is_visible = false * Extend the Texture asset type to support 3D data (#903) Extend the Texture asset type to support 3D data Textures are still loaded from images as 2D, but they can be reshaped according to how the render pipeline would like to use them. Also add an example of how this can be used with the texture2DArray uniform type. * Fix duplicated chilren in Scene spawn (#904) Fix duplicated chilren in Scene spawn * avoid exclusive lock during `update_asset_storage` (#909) avoid exclusive lock during `update_asset_storage` Co-authored-by: Jay * Improve timer ergonomics. Add tests (#923) * Improve ui depth system (#905) * Add test for ui-z system * Remove generic hierarchy runner and refactor ui z-system * Remove different handling for childless nodes Having an empty children list should be the same as having no child component. * Further simplify system after change * Allow despawning of hierarchies in threadlocal systems (#908) * Rename test components for easier understanding of failures * Make recursive despawn public This way, threadlocal systems can despawn hierarchies. * Swap children before despawning * wgpu: use mailbox instead of fifo for vsync (#920) * Add set_cursor_position method to Window (#917) * Create winit windows before app.initialize() (#916) This is required so startup systems have access to Windows and WinitWindows resources. * Remove Changed from parent update system (#907) System has to check for actual change of the value anyway. This way, children inserted after postupdate get synced in the next frame and are not lost. * temporarily suppress new clipply lint (#929) * Fix a deadlock that can occur when using scope() on ComputeTaskPool from within a system. (#892) * Update shaderc requirement from 0.6.3 to 0.7.0 (#823) Updates the requirements on [shaderc](https://github.com/google/shaderc-rs) to permit the latest version. - [Release notes](https://github.com/google/shaderc-rs/releases) - [Commits](https://github.com/google/shaderc-rs/compare/v0.6.3...v0.7.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Allow timers to be paused and encapsulate fields (#914) Allow timers to be paused and encapsulate fields * Timer Polishing (#931) * Pause stops ticks. Consistent getter method names. Update tests. * Add timing example * Format with the nightly formatter Co-authored-by: Amber Kowalski * BugFix: Archetype grow with defferent size. (#930) Co-authored-by: heshuai * When a task scope produces <= 1 task to run, run it on the calling thread immediately. (#932) While generally speaking the calling thread would have picked up the task first anyways, I don't think it makes much sense usually to block the calling thread until another thread wakes and does the work. * make the timer trigger (#935) Co-authored-by: Tony * Bevy Reflection (#926) Bevy Reflection * removed redundant v_Normal in shader (#938) * Refactor Time API and internals (#934) Refactor Time API and internals * store PipelineSpecialization.dynamic_bindings in HashSet (#936) * Updated changelog (#942) * Run parent-update and transform-propagation during the "post-startup" stage (instead of "startup") (#955) * Propagate transforms during the POST_STARTUP start-up stage * Update changelog * additional vertex attribute value types (#946) * Add removal_detection example (#945) Co-authored-by: Carter Anderson * Fix `RenderResources` index slicing (#948) * Change bevy_input::Touch API to match similar APIs (#952) * Fix examples in bevy_reflect/README.md (#963) * Update ahash requirement from 0.5.3 to 0.6.1 (#960) Updates the requirements on [ahash](https://github.com/tkaitchuck/ahash) to permit the latest version. - [Release notes](https://github.com/tkaitchuck/ahash/releases) - [Commits](https://github.com/tkaitchuck/ahash/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * switch winit size to logical to be dpi independent (#947) * switch winit size to logical * make scale factor available from bevy_window * Update Hexasphere & Usage. (#965) * Update glam requirement from 0.10.0 to 0.11.0 (#961) Updates the requirements on [glam](https://github.com/bitshifter/glam-rs) to permit the latest version. - [Release notes](https://github.com/bitshifter/glam-rs/releases) - [Changelog](https://github.com/bitshifter/glam-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/bitshifter/glam-rs/compare/0.10.0...0.11.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * consolidate find-crate (#964) * Test more areas of the codebase (#953) Test more areas of the codebase * Fix collision detection by calculating positive penetration depth. (#966) * Rename reflect 'hash' method to 'reflect_hash' and `partial_eq` to `reflect_partial_eq` (#954) * Rename reflect 'hash' method to 'reflect_hash' to avoid colliding with std::hash::Hash::hash to resolve #943. * Rename partial_eq to reflect_partial_eq to avoid collisions with implementations of PartialEq on primitives. * Renderer Optimization Round 1 (#958) * only update global transforms when they (or their ancestors) have changed * only update render resource nodes when they have changed (quality check plz) * only update entity mesh specialization when mesh (or mesh component) has changed * only update sprite size when changed * remove stale bind groups * fix setting size of loading sprites * store unmatched render resource binding results * reduce state changes * cargo fmt + clippy * remove cached "NoMatch" results when new bindings are added to RenderResourceBindings * inline current_entity in world_builder * try creating bind groups even when they havent changed * render_resources_node: update all entities when resized * fmt * hidpi swap chains (#973) hidpi swap chains * Fixed Hexasphere versioning. (#974) * Fix errors and panics to typical Rust conventions (#968) Fix errors and panics to typical Rust conventions * Add Archetype TypeInfo::type_name accessor (#980) * Optimize Text rendering / SharedBuffers (#972) optimize Text rendering / SharedBuffers * Revert "Add Archetype TypeInfo::type_name accessor (#980)" (#982) This reverts commit 4833c2a7f4ea16a6f19e06b2307fa0cf34973fbf. * fix changed meshes (#984) * Add Archetype TypeInfo::type_name accessor (#985) * Update examples readme (#983) * Tracing chrome span names (#979) * Update tracing-chrome to 0.3.0 * bevy_log: Add fields to span names for tracing-chrome * Conditionally import tracing_subscriber modules based on feature * Debug text example: render fps and frame time (#978) Display fps and frame time in text_debug example * Document part of bevy_ecs::Commands (#976) Document part of bevy_ecs::Commands * Change`TextureAtlasBuilder` into expected Builder conventions (#969) * Change`TextureAtlasBuilder` into expected Builder conventions * Store mouse cursor position in Window (#940) * Set cursor updates (#993) * update `Window::set_cursor_position` to take a `Vec2` instead of `i32`s this allows fractional coordinates to work correctly * optimize asset gpu data transfer (#987) * naming coherence for cameras (#995) naming coherence for cameras * fix scene loading (#988) * fix example bindings (#1001) * account for "still loading" textures in RenderResourceNodes (#1000) * Update Hexasphere to improve MSRV (#994) Assumes hexasphere will follow semver (I will try to make sure it does!) * Allow windows to maximized. (#1004) Adds a new `set_maximized` method to allow users to maximize windows. * Fix ci (#1024) * fix format * fix clippy * used fixed nightly * Live reloading of shaders (#937) * Add ShaderLoader, rebuild pipelines for modified shader assets * New example * Add shader_update_system, ShaderError, remove specialization assets * Don't panic on shader compilation failure * Add documentation for bevy::ecs::Query::removed (#950) Add documentation for bevy::ecs::Query::removed * Added WindowFocused event (#956) * attempt to deal with rounding issue when creating the swap chain (#997) attempt to deal with rounding issue when creating the swap chain on high DPI displays * Ensure default material is loaded (#1016) * only update components for entities in map (#1023) * can spawn a scene from a ChildBuilder, or directly set its parent when spawning it (#1026) can spawn a scene from a ChildBuilder, or directly set its parent when spawning one * Use shaderc for aarch64-apple-darwin. (#1027) * Break out Visible component from Draw (#1034) Break out Visible component from Draw * Don't panic when attempting to set shader defs from an asset that hasn't loaded yet (#1035) Don't panic when attempting to set shader defs from an asset that hasn't loaded yet * add ability to load `.dds`, `.tga`, and `.jpeg` texture formats (#1038) add ability to load `.dds`, `.tga`, and `.jpeg` texture formats * Just spawn one CameraUiBundle (not 4) (#1047) * add .cargo/config.toml to .gitignore * Schedule v2 (#1021) Schedule V2 * Add support for Apple Silicon by upgrading winit. (#1043) * fix contributors example (#1050) * Fix hang on missing state update handler (#1051) * More informative error message on missing stage (#1048) More informative error message on missing state * update `Window`'s `width` & `height` methods to return `f32` (#1033) update `Window`'s `width` & `height` methods to return `f32` * Improve usability of StateStage and cut down on "magic" (#1059) Improve usability of StateStage and cut down on "magic" * suppress wgpu warnings by default. they are generally unactionable and noisy (#1066) * Update dependencies for Enable Fast Compiles (#1065) Added clang as an optional dependency to avoid frustration of running into compile error the first time you run a cargo build * run stretch's layout on physical coordinates to fix pixel alignment (#1061) run stretch's layout on physical coordinates to fix pixel alignment of the results * Adopt a Fetch pattern for SystemParams (#1074) * add ability to provide custom a `AssetIo` implementation (#1037) make it easier to override the default asset IO instance * Fix example return_after_run (#1082) * ignore error when setting global tracing subscriber * ignore unfocus event on window closed previously * update example to show how to disable LogPlugin * set is_transparent to true by default for UI bundles (#1071) set is_transparent to true by default for UI bundles * fix multiple windows example (#1088) * add with_enter_stage (and other variants) (#1091) * update changelog (#1092) * release 0.4.0 (#1093) * Expose wgpu backend in WgpuOptions and allow it to be configured from the environment (#1042) * Fix lock order to remove the chance of deadlock (#1121) * Prevent double panic in the Drop of TaksPoolInner (#1064) * make wgpu options public (#1133) Co-authored-by: marius851000 Co-authored-by: Duncan Co-authored-by: Felipe Jorge Co-authored-by: bg <40744358+blamelessgames@users.noreply.github.com> Co-authored-by: Jay Co-authored-by: Nathan Stocks Co-authored-by: SvenTS <73037851+SvenTS@users.noreply.github.com> Co-authored-by: Carter Anderson Co-authored-by: Tomasz Sterna Co-authored-by: Philip Degarmo Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Amber Kowalski Co-authored-by: RiskLove Co-authored-by: heshuai Co-authored-by: rmsthebest <11652273+rmsthebest@users.noreply.github.com> Co-authored-by: Tony Co-authored-by: Nick Co-authored-by: Mariusz Kryński Co-authored-by: Jonathan Cornaz Co-authored-by: Lukas Orsvärn Co-authored-by: Joshua J. Bouw Co-authored-by: Toothbrush Co-authored-by: François Co-authored-by: Patrik Buhring Co-authored-by: Andre Kuehne Co-authored-by: memoryruins Co-authored-by: Robert Swain Co-authored-by: Michael Tang Co-authored-by: Nathan Jeffords Co-authored-by: Corey Farwell Co-authored-by: Al M Co-authored-by: thebluefish Co-authored-by: rod-salazar <51817379+rod-salazar@users.noreply.github.com> Co-authored-by: James R <71466513+ColdIce1605@users.noreply.github.com> Co-authored-by: sapir Co-authored-by: Alec Deason Co-authored-by: Tiago Ferreira Co-authored-by: Agorgianitis Loukas Co-authored-by: Martin Lavoie --- .github/workflows/ci.yml | 4 +- .gitignore | 3 +- CHANGELOG.md | 204 ++++- Cargo.toml | 55 +- assets/scenes/load_scene_example.scn | 54 +- assets/shaders/hot.frag | 11 + assets/shaders/hot.vert | 15 + assets/textures/array_texture.png | Bin 0 -> 470956 bytes crates/bevy_app/Cargo.toml | 8 +- crates/bevy_app/src/app.rs | 32 +- crates/bevy_app/src/app_builder.rs | 190 ++-- crates/bevy_app/src/plugin_group.rs | 14 +- crates/bevy_app/src/schedule_runner.rs | 4 +- crates/bevy_app/src/stage.rs | 7 +- crates/bevy_asset/Cargo.toml | 14 +- crates/bevy_asset/src/asset_server.rs | 35 +- crates/bevy_asset/src/assets.rs | 16 +- crates/bevy_asset/src/filesystem_watcher.rs | 4 +- crates/bevy_asset/src/handle.rs | 75 +- crates/bevy_asset/src/info.rs | 3 +- crates/bevy_asset/src/io/file_asset_io.rs | 2 +- crates/bevy_asset/src/io/mod.rs | 6 +- crates/bevy_asset/src/lib.rs | 83 +- crates/bevy_asset/src/loader.rs | 7 +- crates/bevy_asset/src/path.rs | 13 +- crates/bevy_audio/Cargo.toml | 12 +- crates/bevy_audio/src/audio.rs | 10 +- crates/bevy_audio/src/audio_source.rs | 2 +- crates/bevy_audio/src/lib.rs | 6 +- crates/bevy_core/Cargo.toml | 17 +- crates/bevy_core/src/label.rs | 5 +- crates/bevy_core/src/lib.rs | 22 +- crates/bevy_core/src/time/fixed_timestep.rs | 168 ++++ crates/bevy_core/src/time/mod.rs | 2 + crates/bevy_core/src/time/time.rs | 119 ++- crates/bevy_core/src/time/timer.rs | 181 +++- crates/bevy_derive/Cargo.toml | 3 +- crates/bevy_derive/src/bevy_main.rs | 2 +- crates/bevy_derive/src/bytes.rs | 2 +- crates/bevy_derive/src/lib.rs | 12 - crates/bevy_derive/src/modules.rs | 3 - crates/bevy_derive/src/render_resources.rs | 10 +- crates/bevy_derive/src/resource.rs | 2 +- crates/bevy_derive/src/shader_defs.rs | 2 +- crates/bevy_diagnostic/Cargo.toml | 11 +- crates/bevy_diagnostic/src/diagnostic.rs | 3 +- .../src/frame_time_diagnostics_plugin.rs | 10 +- .../src/print_diagnostics_plugin.rs | 13 +- crates/bevy_dylib/Cargo.toml | 4 +- crates/bevy_dynamic_plugin/Cargo.toml | 4 +- crates/bevy_ecs/Cargo.toml | 8 +- crates/bevy_ecs/macros/Cargo.toml | 4 +- crates/bevy_ecs/macros/src/lib.rs | 26 +- crates/bevy_ecs/src/core/archetype.rs | 22 +- crates/bevy_ecs/src/core/bundle.rs | 4 + crates/bevy_ecs/src/core/entities.rs | 2 +- crates/bevy_ecs/src/core/entity_map.rs | 2 +- crates/bevy_ecs/src/core/serde.rs | 28 +- crates/bevy_ecs/src/core/world.rs | 16 +- crates/bevy_ecs/src/core/world_builder.rs | 13 + crates/bevy_ecs/src/lib.rs | 1 + crates/bevy_ecs/src/resource/resources.rs | 10 +- crates/bevy_ecs/src/schedule/mod.rs | 525 ++++++++++- crates/bevy_ecs/src/schedule/schedule.rs | 242 ----- crates/bevy_ecs/src/schedule/stage.rs | 223 +++++ ...parallel_executor.rs => stage_executor.rs} | 518 ++--------- crates/bevy_ecs/src/schedule/state.rs | 276 ++++++ crates/bevy_ecs/src/system/commands.rs | 128 ++- crates/bevy_ecs/src/system/into_system.rs | 286 ++++-- .../bevy_ecs/src/system/into_thread_local.rs | 8 +- crates/bevy_ecs/src/system/query/mod.rs | 15 + crates/bevy_ecs/src/system/system.rs | 13 +- crates/bevy_ecs/src/system/system_chaining.rs | 45 +- crates/bevy_ecs/src/system/system_param.rs | 234 +++-- crates/bevy_gilrs/Cargo.toml | 10 +- crates/bevy_gilrs/src/lib.rs | 5 +- crates/bevy_gltf/Cargo.toml | 20 +- crates/bevy_gltf/src/loader.rs | 113 ++- crates/bevy_input/Cargo.toml | 10 +- crates/bevy_input/src/input.rs | 77 ++ crates/bevy_input/src/lib.rs | 15 +- crates/bevy_input/src/mouse.rs | 2 +- crates/bevy_input/src/touch.rs | 285 +++++- crates/bevy_internal/Cargo.toml | 58 +- crates/bevy_internal/src/default_plugins.rs | 4 +- crates/bevy_internal/src/lib.rs | 14 +- crates/bevy_internal/src/prelude.rs | 5 +- crates/bevy_log/Cargo.toml | 8 +- crates/bevy_log/src/lib.rs | 27 +- crates/bevy_math/Cargo.toml | 5 +- crates/bevy_math/src/face_toward.rs | 20 + crates/bevy_math/src/geometry.rs | 21 +- crates/bevy_pbr/Cargo.toml | 23 +- crates/bevy_pbr/src/entity.rs | 5 +- crates/bevy_pbr/src/lib.rs | 8 +- crates/bevy_pbr/src/light.rs | 5 +- crates/bevy_pbr/src/material.rs | 2 +- .../forward_pipeline/forward.vert | 1 - .../src/render_graph/forward_pipeline/mod.rs | 8 +- .../bevy_pbr/src/render_graph/lights_node.rs | 2 +- crates/bevy_property/Cargo.toml | 26 - .../bevy_property_derive/src/lib.rs | 431 --------- .../bevy_property/src/dynamic_properties.rs | 223 ----- .../impl_property/impl_property_bevy_ecs.rs | 24 - .../src/impl_property/impl_property_glam.rs | 8 - .../impl_property/impl_property_smallvec.rs | 50 - .../src/impl_property/impl_property_std.rs | 861 ------------------ crates/bevy_property/src/impl_property/mod.rs | 4 - crates/bevy_property/src/lib.rs | 21 - crates/bevy_property/src/properties.rs | 92 -- crates/bevy_property/src/property.rs | 55 -- crates/bevy_property/src/property_serde.rs | 545 ----------- crates/bevy_property/src/ron.rs | 15 - crates/bevy_property/src/type_registry.rs | 235 ----- crates/bevy_reflect/Cargo.toml | 36 + crates/bevy_reflect/README.md | 166 ++++ .../bevy_reflect_derive}/Cargo.toml | 9 +- .../bevy_reflect_derive/src/lib.rs | 751 +++++++++++++++ .../bevy_reflect_derive}/src/modules.rs | 26 +- .../bevy_reflect_derive/src/reflect_trait.rs | 66 ++ .../bevy_reflect_derive}/src/type_uuid.rs | 16 +- crates/bevy_reflect/src/impls/bevy_app.rs | 57 ++ crates/bevy_reflect/src/impls/bevy_ecs.rs | 219 +++++ crates/bevy_reflect/src/impls/glam.rs | 10 + crates/bevy_reflect/src/impls/smallvec.rs | 96 ++ crates/bevy_reflect/src/impls/std.rs | 201 ++++ crates/bevy_reflect/src/lib.rs | 323 +++++++ crates/bevy_reflect/src/list.rs | 178 ++++ crates/bevy_reflect/src/map.rs | 184 ++++ crates/bevy_reflect/src/path.rs | 385 ++++++++ crates/bevy_reflect/src/reflect.rs | 77 ++ crates/bevy_reflect/src/serde/de.rs | 403 ++++++++ crates/bevy_reflect/src/serde/mod.rs | 14 + crates/bevy_reflect/src/serde/ser.rs | 268 ++++++ crates/bevy_reflect/src/struct_trait.rs | 256 ++++++ crates/bevy_reflect/src/tuple_struct.rs | 210 +++++ crates/bevy_reflect/src/type_registry.rs | 324 +++++++ .../src/type_uuid.rs | 4 +- crates/bevy_render/Cargo.toml | 35 +- crates/bevy_render/src/camera/camera.rs | 11 +- crates/bevy_render/src/camera/projection.rs | 27 +- .../src/camera/visible_entities.rs | 21 +- crates/bevy_render/src/color.rs | 4 +- crates/bevy_render/src/colorspace.rs | 3 - crates/bevy_render/src/draw.rs | 147 ++- crates/bevy_render/src/entity.rs | 6 +- crates/bevy_render/src/lib.rs | 152 ++-- crates/bevy_render/src/mesh/mesh.rs | 179 +++- crates/bevy_render/src/mesh/shape.rs | 20 +- crates/bevy_render/src/pipeline/pipeline.rs | 2 +- .../src/pipeline/pipeline_compiler.rs | 119 ++- .../src/pipeline/pipeline_layout.rs | 2 +- .../src/pipeline/render_pipelines.rs | 34 +- .../src/pipeline/state_descriptors.rs | 6 +- .../src/pipeline/vertex_buffer_descriptor.rs | 5 +- crates/bevy_render/src/render_graph/base.rs | 27 +- crates/bevy_render/src/render_graph/graph.rs | 11 +- crates/bevy_render/src/render_graph/mod.rs | 16 +- crates/bevy_render/src/render_graph/node.rs | 4 +- .../src/render_graph/nodes/camera_node.rs | 2 +- .../src/render_graph/nodes/pass_node.rs | 52 +- .../nodes/render_resources_node.rs | 416 ++++++--- .../render_graph/nodes/shared_buffers_node.rs | 5 +- .../render_graph/nodes/texture_copy_node.rs | 16 +- .../nodes/window_swapchain_node.rs | 2 +- .../render_graph/nodes/window_texture_node.rs | 6 +- .../bevy_render/src/render_graph/schedule.rs | 7 +- crates/bevy_render/src/render_graph/system.rs | 7 +- .../headless_render_resource_context.rs | 12 +- .../src/renderer/render_resource/buffer.rs | 2 +- .../render_resource_bindings.rs | 127 ++- .../render_resource/shared_buffers.rs | 151 +-- .../src/renderer/render_resource/texture.rs | 2 +- .../src/renderer/render_resource_context.rs | 9 +- crates/bevy_render/src/shader/shader.rs | 154 +++- crates/bevy_render/src/shader/shader_defs.rs | 18 +- .../bevy_render/src/shader/shader_reflect.rs | 17 +- .../src/texture/hdr_texture_loader.rs | 6 +- .../src/texture/image_texture_loader.rs | 25 +- crates/bevy_render/src/texture/texture.rs | 75 +- .../src/texture/texture_descriptor.rs | 8 +- .../src/texture/texture_dimension.rs | 20 + crates/bevy_scene/Cargo.toml | 14 +- crates/bevy_scene/src/command.rs | 29 +- crates/bevy_scene/src/dynamic_scene.rs | 63 +- crates/bevy_scene/src/lib.rs | 9 +- crates/bevy_scene/src/scene.rs | 2 +- crates/bevy_scene/src/scene_loader.rs | 14 +- crates/bevy_scene/src/scene_spawner.rs | 133 ++- crates/bevy_scene/src/serde.rs | 51 +- crates/bevy_sprite/Cargo.toml | 21 +- crates/bevy_sprite/src/collide_aabb.rs | 2 +- crates/bevy_sprite/src/color_material.rs | 2 +- .../src/dynamic_texture_atlas_builder.rs | 6 +- crates/bevy_sprite/src/entity.rs | 20 +- crates/bevy_sprite/src/lib.rs | 13 +- crates/bevy_sprite/src/render/mod.rs | 12 +- crates/bevy_sprite/src/sprite.rs | 14 +- crates/bevy_sprite/src/texture_atlas.rs | 2 +- .../bevy_sprite/src/texture_atlas_builder.rs | 80 +- crates/bevy_tasks/Cargo.toml | 2 +- crates/bevy_tasks/src/task_pool.rs | 75 +- crates/bevy_text/Cargo.toml | 20 +- crates/bevy_text/src/draw.rs | 39 +- crates/bevy_text/src/error.rs | 4 +- crates/bevy_text/src/font.rs | 8 +- crates/bevy_text/src/font_atlas.rs | 5 +- crates/bevy_text/src/font_atlas_set.rs | 2 +- crates/bevy_text/src/glyph_brush.rs | 2 +- crates/bevy_transform/Cargo.toml | 13 +- .../bevy_transform/src/components/children.rs | 24 +- .../src/components/global_transform.rs | 8 +- .../bevy_transform/src/components/parent.rs | 29 +- .../src/components/transform.rs | 8 +- .../src/hierarchy/child_builder.rs | 13 +- .../bevy_transform/src/hierarchy/hierarchy.rs | 87 +- .../hierarchy/hierarchy_maintenance_system.rs | 32 +- .../src/hierarchy/world_child_builder.rs | 11 +- crates/bevy_transform/src/lib.rs | 27 +- .../src/transform_propagate_system.rs | 97 +- crates/bevy_type_registry/Cargo.toml | 26 - crates/bevy_type_registry/src/lib.rs | 21 - .../bevy_type_registry/src/register_type.rs | 78 -- .../bevy_type_registry/src/type_registry.rs | 334 ------- crates/bevy_ui/Cargo.toml | 31 +- crates/bevy_ui/src/entity.rs | 40 +- crates/bevy_ui/src/flex/convert.rs | 103 ++- crates/bevy_ui/src/flex/mod.rs | 53 +- crates/bevy_ui/src/focus.rs | 2 +- crates/bevy_ui/src/lib.rs | 19 +- crates/bevy_ui/src/node.rs | 37 +- crates/bevy_ui/src/render/mod.rs | 20 +- crates/bevy_ui/src/update.rs | 156 +++- crates/bevy_ui/src/widget/image.rs | 4 +- crates/bevy_ui/src/widget/text.rs | 16 +- crates/bevy_utils/Cargo.toml | 6 +- crates/bevy_utils/src/lib.rs | 1 + crates/bevy_wgpu/Cargo.toml | 20 +- .../wgpu_resource_diagnostics_plugin.rs | 6 +- crates/bevy_wgpu/src/lib.rs | 53 +- .../src/renderer/wgpu_render_context.rs | 4 +- .../renderer/wgpu_render_graph_executor.rs | 6 +- .../renderer/wgpu_render_resource_context.rs | 28 +- crates/bevy_wgpu/src/wgpu_render_pass.rs | 6 +- crates/bevy_wgpu/src/wgpu_renderer.rs | 17 +- crates/bevy_wgpu/src/wgpu_resources.rs | 66 ++ crates/bevy_wgpu/src/wgpu_type_converter.rs | 6 +- crates/bevy_window/Cargo.toml | 12 +- crates/bevy_window/src/event.rs | 21 +- crates/bevy_window/src/lib.rs | 11 +- crates/bevy_window/src/window.rs | 159 +++- crates/bevy_winit/Cargo.toml | 18 +- crates/bevy_winit/src/converters.rs | 7 +- crates/bevy_winit/src/lib.rs | 189 ++-- crates/bevy_winit/src/winit_windows.rs | 55 +- docs/linux_dependencies.md | 5 + examples/2d/contributors.rs | 33 +- examples/2d/sprite.rs | 2 +- examples/2d/sprite_sheet.rs | 10 +- examples/2d/texture_atlas.rs | 103 ++- examples/3d/3d_scene.rs | 2 +- examples/3d/load_gltf.rs | 2 +- examples/3d/msaa.rs | 2 +- examples/3d/parenting.rs | 6 +- examples/3d/spawner.rs | 8 +- examples/3d/texture.rs | 8 +- examples/3d/z_sort_debug.rs | 8 +- examples/README.md | 15 +- examples/android/android.rs | 2 +- examples/app/custom_loop.rs | 4 +- examples/app/headless.rs | 4 +- examples/app/logs.rs | 2 +- examples/app/plugin.rs | 5 +- examples/app/plugin_group.rs | 4 +- examples/app/return_after_run.rs | 14 +- examples/asset/asset_loading.rs | 2 +- examples/asset/custom_asset.rs | 6 +- examples/asset/custom_asset_io.rs | 104 +++ examples/asset/hot_asset_reloading.rs | 2 +- examples/audio/audio.rs | 2 +- examples/diagnostics/custom_diagnostic.rs | 4 +- examples/ecs/ecs_guide.rs | 19 +- examples/ecs/event.rs | 6 +- examples/ecs/fixed_timestep.rs | 46 + examples/ecs/hierarchy.rs | 21 +- examples/ecs/parallel_query.rs | 16 +- examples/ecs/removal_detection.rs | 74 ++ examples/ecs/startup_system.rs | 4 +- examples/ecs/state.rs | 169 ++++ examples/ecs/system_chaining.rs | 2 +- examples/ecs/timers.rs | 73 ++ examples/game/breakout.rs | 16 +- examples/hello_world.rs | 2 +- examples/input/char_input_events.rs | 2 +- examples/input/gamepad_input.rs | 4 +- examples/input/gamepad_input_events.rs | 2 +- examples/input/keyboard_input.rs | 2 +- examples/input/keyboard_input_events.rs | 2 +- examples/input/mouse_input.rs | 2 +- examples/input/mouse_input_events.rs | 2 +- examples/input/touch_input.rs | 12 +- examples/input/touch_input_events.rs | 2 +- examples/ios/src/lib.rs | 2 +- examples/reflection/generic_reflection.rs | 28 + examples/reflection/reflection.rs | 94 ++ examples/reflection/reflection_types.rs | 95 ++ examples/reflection/trait_reflection.rs | 56 ++ examples/scene/properties.rs | 102 --- examples/scene/scene.rs | 47 +- examples/shader/array_texture.rs | 153 ++++ examples/shader/hot_shader_reloading.rs | 80 ++ examples/shader/mesh_custom_attribute.rs | 4 +- examples/shader/shader_custom_material.rs | 6 +- examples/shader/shader_defs.rs | 11 +- examples/tools/bevymark.rs | 39 +- examples/ui/button.rs | 6 +- examples/ui/font_atlas_debug.rs | 10 +- examples/ui/text.rs | 6 +- examples/ui/text_debug.rs | 53 +- examples/ui/ui.rs | 20 +- examples/wasm/assets_wasm.rs | 6 +- examples/wasm/headless_wasm.rs | 4 +- examples/wasm/hello_wasm.rs | 2 +- examples/wasm/winit_wasm.rs | 12 +- examples/window/multiple_windows.rs | 49 +- examples/window/window_settings.rs | 10 +- tools/publish.sh | 11 +- 327 files changed, 11748 insertions(+), 6344 deletions(-) create mode 100644 assets/shaders/hot.frag create mode 100644 assets/shaders/hot.vert create mode 100644 assets/textures/array_texture.png create mode 100644 crates/bevy_core/src/time/fixed_timestep.rs delete mode 100644 crates/bevy_ecs/src/schedule/schedule.rs create mode 100644 crates/bevy_ecs/src/schedule/stage.rs rename crates/bevy_ecs/src/schedule/{parallel_executor.rs => stage_executor.rs} (54%) create mode 100644 crates/bevy_ecs/src/schedule/state.rs delete mode 100644 crates/bevy_property/Cargo.toml delete mode 100644 crates/bevy_property/bevy_property_derive/src/lib.rs delete mode 100644 crates/bevy_property/src/dynamic_properties.rs delete mode 100644 crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs delete mode 100644 crates/bevy_property/src/impl_property/impl_property_glam.rs delete mode 100644 crates/bevy_property/src/impl_property/impl_property_smallvec.rs delete mode 100644 crates/bevy_property/src/impl_property/impl_property_std.rs delete mode 100644 crates/bevy_property/src/impl_property/mod.rs delete mode 100644 crates/bevy_property/src/lib.rs delete mode 100644 crates/bevy_property/src/properties.rs delete mode 100644 crates/bevy_property/src/property.rs delete mode 100644 crates/bevy_property/src/property_serde.rs delete mode 100644 crates/bevy_property/src/ron.rs delete mode 100644 crates/bevy_property/src/type_registry.rs create mode 100644 crates/bevy_reflect/Cargo.toml create mode 100644 crates/bevy_reflect/README.md rename crates/{bevy_property/bevy_property_derive => bevy_reflect/bevy_reflect_derive}/Cargo.toml (65%) create mode 100644 crates/bevy_reflect/bevy_reflect_derive/src/lib.rs rename crates/{bevy_property/bevy_property_derive => bevy_reflect/bevy_reflect_derive}/src/modules.rs (57%) create mode 100644 crates/bevy_reflect/bevy_reflect_derive/src/reflect_trait.rs rename crates/{bevy_derive => bevy_reflect/bevy_reflect_derive}/src/type_uuid.rs (83%) create mode 100644 crates/bevy_reflect/src/impls/bevy_app.rs create mode 100644 crates/bevy_reflect/src/impls/bevy_ecs.rs create mode 100644 crates/bevy_reflect/src/impls/glam.rs create mode 100644 crates/bevy_reflect/src/impls/smallvec.rs create mode 100644 crates/bevy_reflect/src/impls/std.rs create mode 100644 crates/bevy_reflect/src/lib.rs create mode 100644 crates/bevy_reflect/src/list.rs create mode 100644 crates/bevy_reflect/src/map.rs create mode 100644 crates/bevy_reflect/src/path.rs create mode 100644 crates/bevy_reflect/src/reflect.rs create mode 100644 crates/bevy_reflect/src/serde/de.rs create mode 100644 crates/bevy_reflect/src/serde/mod.rs create mode 100644 crates/bevy_reflect/src/serde/ser.rs create mode 100644 crates/bevy_reflect/src/struct_trait.rs create mode 100644 crates/bevy_reflect/src/tuple_struct.rs create mode 100644 crates/bevy_reflect/src/type_registry.rs rename crates/{bevy_type_registry => bevy_reflect}/src/type_uuid.rs (78%) delete mode 100644 crates/bevy_type_registry/Cargo.toml delete mode 100644 crates/bevy_type_registry/src/lib.rs delete mode 100644 crates/bevy_type_registry/src/register_type.rs delete mode 100644 crates/bevy_type_registry/src/type_registry.rs create mode 100644 examples/asset/custom_asset_io.rs create mode 100644 examples/ecs/fixed_timestep.rs create mode 100644 examples/ecs/removal_detection.rs create mode 100644 examples/ecs/state.rs create mode 100644 examples/ecs/timers.rs create mode 100644 examples/reflection/generic_reflection.rs create mode 100644 examples/reflection/reflection.rs create mode 100644 examples/reflection/reflection_types.rs create mode 100644 examples/reflection/trait_reflection.rs delete mode 100644 examples/scene/properties.rs create mode 100644 examples/shader/array_texture.rs create mode 100644 examples/shader/hot_shader_reloading.rs 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 0000000000000000000000000000000000000000..ab2c144e1124759990cc9d78bc0502f616a0a579 GIT binary patch literal 470956 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

lPolD&1Mj6lrJTmQcwZeoHQC;sSz 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 _X6h|LFI1QsH3?g^VR#uH){`}K^2X5S`-h64% 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

{tIqu>CtYXTqVJe5*;XPbgl^rsb}ZdJXU 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", @@ -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::, Changed)>>, - Query<(), Or<(Added, Added)>>, - Query<(), Or<(Mutated, Mutated)>>, - )>| { + fn query_system( + mut ran: ResMut, + set: QuerySet<( + Query<(), Or<(Changed, Changed)>>, + Query<(), Or<(Added, Added)>>, + Query<(), Or<(Mutated, Mutated)>>, + )>, + ) { let changed = set.q0().iter().count(); let added = set.q1().iter().count(); let mutated = set.q2().iter().count(); @@ -317,7 +432,7 @@ mod tests { resources.insert(false); world.spawn((A, B)); - run_system(&mut world, &mut resources, query_system); + run_system(&mut world, &mut resources, query_system.system()); assert!(*resources.get::().unwrap(), "system ran"); } @@ -336,18 +451,22 @@ mod tests { let ent = world.spawn((0,)); let mut schedule = Schedule::default(); - schedule.add_stage("update"); - schedule.add_system_to_stage("update", incr_e_on_flip); - schedule.initialize(&mut world, &mut resources); - - schedule.run(&mut world, &mut resources); + let mut update = SystemStage::parallel(); + update.add_system(incr_e_on_flip.system()); + schedule.add_stage("update", update); + schedule.add_stage( + "clear_trackers", + SystemStage::single(clear_trackers_system.system()), + ); + + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); *resources.get_mut::().unwrap() = true; - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 2); } @@ -369,25 +488,29 @@ mod tests { let ent = world.spawn((0,)); let mut schedule = Schedule::default(); - schedule.add_stage("update"); - schedule.add_system_to_stage("update", incr_e_on_flip); - schedule.initialize(&mut world, &mut resources); - - schedule.run(&mut world, &mut resources); + let mut update = SystemStage::parallel(); + update.add_system(incr_e_on_flip.system()); + schedule.add_stage("update", update); + schedule.add_stage( + "clear_trackers", + SystemStage::single(clear_trackers_system.system()), + ); + + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 1); *resources.get_mut::().unwrap() = true; - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 2); - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 2); *resources.get_mut::().unwrap() = 20; - schedule.run(&mut world, &mut resources); + schedule.initialize_and_run(&mut world, &mut resources); assert_eq!(*(world.get::(ent).unwrap()), 3); } @@ -400,7 +523,7 @@ mod tests { let mut resources = Resources::default(); world.spawn((A,)); - run_system(&mut world, &mut resources, sys); + run_system(&mut world, &mut resources, sys.system()); } #[test] @@ -412,7 +535,7 @@ mod tests { let mut resources = Resources::default(); world.spawn((A,)); - run_system(&mut world, &mut resources, sys); + run_system(&mut world, &mut resources, sys.system()); } #[test] @@ -423,7 +546,7 @@ mod tests { let mut resources = Resources::default(); world.spawn((A,)); - run_system(&mut world, &mut resources, sys); + run_system(&mut world, &mut resources, sys.system()); } #[test] @@ -435,7 +558,7 @@ mod tests { let mut resources = Resources::default(); world.spawn((A,)); - run_system(&mut world, &mut resources, sys); + run_system(&mut world, &mut resources, sys.system()); } #[test] @@ -446,24 +569,19 @@ mod tests { let mut world = World::default(); let mut resources = Resources::default(); world.spawn((A,)); - run_system(&mut world, &mut resources, sys); + run_system(&mut world, &mut resources, sys.system()); } - fn run_system< - Params, - SystemType: System, - Sys: IntoSystem, - >( + fn run_system>( world: &mut World, resources: &mut Resources, - system: Sys, + system: S, ) { let mut schedule = Schedule::default(); - schedule.add_stage("update"); - schedule.add_system_to_stage("update", system); - - schedule.initialize(world, resources); - schedule.run(world, resources); + let mut update = SystemStage::parallel(); + update.add_system(system); + schedule.add_stage("update", update); + schedule.initialize_and_run(world, resources); } #[derive(Default)] @@ -471,40 +589,34 @@ mod tests { _buffer: Vec, } - fn test_for_conflicting_resources< - Params, - SystemType: System, - Sys: IntoSystem, - >( - sys: Sys, - ) { + fn test_for_conflicting_resources>(sys: S) { let mut world = World::default(); let mut resources = Resources::default(); resources.insert(BufferRes::default()); resources.insert(A); resources.insert(B); - run_system(&mut world, &mut resources, sys); + run_system(&mut world, &mut resources, sys.system()); } #[test] #[should_panic] fn conflicting_system_resources() { fn sys(_: ResMut, _: Res) {} - test_for_conflicting_resources(sys) + test_for_conflicting_resources(sys.system()) } #[test] #[should_panic] fn conflicting_system_resources_reverse_order() { fn sys(_: Res, _: ResMut) {} - test_for_conflicting_resources(sys) + test_for_conflicting_resources(sys.system()) } #[test] #[should_panic] fn conflicting_system_resources_multiple_mutable() { fn sys(_: ResMut, _: ResMut) {} - test_for_conflicting_resources(sys) + test_for_conflicting_resources(sys.system()) } #[test] @@ -512,19 +624,19 @@ mod tests { fn conflicting_changed_and_mutable_resource() { // A tempting pattern, but unsound if allowed. fn sys(_: ResMut, _: ChangedRes) {} - test_for_conflicting_resources(sys) + test_for_conflicting_resources(sys.system()) } #[test] #[should_panic] fn conflicting_system_local_resources() { fn sys(_: Local, _: Local) {} - test_for_conflicting_resources(sys) + test_for_conflicting_resources(sys.system()) } #[test] fn nonconflicting_system_resources() { fn sys(_: Local, _: ResMut, _: Local, _: ResMut) {} - test_for_conflicting_resources(sys) + test_for_conflicting_resources(sys.system()) } } diff --git a/crates/bevy_ecs/src/system/into_thread_local.rs b/crates/bevy_ecs/src/system/into_thread_local.rs index 4fe61b3da74b0..d08f33a200d7a 100644 --- a/crates/bevy_ecs/src/system/into_thread_local.rs +++ b/crates/bevy_ecs/src/system/into_thread_local.rs @@ -15,8 +15,8 @@ pub struct ThreadLocalSystemFn { } impl System for ThreadLocalSystemFn { - type Input = (); - type Output = (); + type In = (); + type Out = (); fn name(&self) -> Cow<'static, str> { self.name.clone() @@ -54,10 +54,6 @@ impl System for ThreadLocalSystemFn { fn id(&self) -> SystemId { self.id } - - fn is_initialized(&self) -> bool { - true - } } impl IntoSystem<(&mut World, &mut Resources), ThreadLocalSystemFn> for F diff --git a/crates/bevy_ecs/src/system/query/mod.rs b/crates/bevy_ecs/src/system/query/mod.rs index 5fa89aa9d0f1d..60a309a029b76 100644 --- a/crates/bevy_ecs/src/system/query/mod.rs +++ b/crates/bevy_ecs/src/system/query/mod.rs @@ -181,6 +181,21 @@ impl<'a, Q: WorldQuery, F: QueryFilter> Query<'a, Q, F> { .map_err(QueryError::ComponentError) } + /// Returns an array containing the `Entity`s in this `Query` that had the given `Component` + /// removed in this update. + /// + /// `removed::()` only returns entities whose components were removed before the + /// current system started. + /// + /// Regular systems do not apply `Commands` until the end of their stage. This means component + /// removals in a regular system won't be accessible through `removed::()` in the same + /// stage, because the removal hasn't actually occurred yet. This can be solved by executing + /// `removed::()` in a later stage. `AppBuilder::add_system_to_stage()` can be used to + /// control at what stage a system runs. + /// + /// Thread local systems manipulate the world directly, so removes are applied immediately. This + /// means any system that runs after a thread local system in the same update will pick up + /// removals that happened in the thread local system, regardless of stages. pub fn removed(&self) -> &[Entity] { self.world.removed::() } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 85bb0cefd7ea5..fac4eab2e9442 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -20,11 +20,10 @@ impl SystemId { /// An ECS system that can be added to a [Schedule](crate::Schedule) pub trait System: Send + Sync + 'static { - type Input; - type Output; + type In; + type Out; fn name(&self) -> Cow<'static, str>; fn id(&self) -> SystemId; - fn is_initialized(&self) -> bool; fn update(&mut self, world: &World); fn archetype_component_access(&self) -> &TypeAccess; fn resource_access(&self) -> &TypeAccess; @@ -35,16 +34,16 @@ pub trait System: Send + Sync + 'static { /// 2. This system only runs in parallel with other systems that do not conflict with the `archetype_component_access()` or `resource_access()` unsafe fn run_unsafe( &mut self, - input: Self::Input, + input: Self::In, world: &World, resources: &Resources, - ) -> Option; + ) -> Option; fn run( &mut self, - input: Self::Input, + input: Self::In, world: &mut World, resources: &mut Resources, - ) -> Option { + ) -> Option { // SAFE: world and resources are exclusively borrowed unsafe { self.run_unsafe(input, world, resources) } } diff --git a/crates/bevy_ecs/src/system/system_chaining.rs b/crates/bevy_ecs/src/system/system_chaining.rs index 5d5397c80c654..b0cfd4c055a9b 100644 --- a/crates/bevy_ecs/src/system/system_chaining.rs +++ b/crates/bevy_ecs/src/system/system_chaining.rs @@ -1,6 +1,5 @@ use crate::{ - ArchetypeComponent, IntoSystem, Resources, System, SystemId, ThreadLocalExecution, TypeAccess, - World, + ArchetypeComponent, Resources, System, SystemId, ThreadLocalExecution, TypeAccess, World, }; use std::{any::TypeId, borrow::Cow}; @@ -13,11 +12,9 @@ pub struct ChainSystem { pub(crate) resource_access: TypeAccess, } -impl> System - for ChainSystem -{ - type Input = SystemA::Input; - type Output = SystemB::Output; +impl> System for ChainSystem { + type In = SystemA::In; + type Out = SystemB::Out; fn name(&self) -> Cow<'static, str> { self.name.clone() @@ -27,10 +24,6 @@ impl> System self.id } - fn is_initialized(&self) -> bool { - self.system_a.is_initialized() && self.system_b.is_initialized() - } - fn update(&mut self, world: &World) { self.archetype_component_access.clear(); self.resource_access.clear(); @@ -56,10 +49,10 @@ impl> System unsafe fn run_unsafe( &mut self, - input: Self::Input, + input: Self::In, world: &World, resources: &Resources, - ) -> Option { + ) -> Option { let out = self.system_a.run_unsafe(input, world, resources).unwrap(); self.system_b.run_unsafe(out, world, resources) } @@ -75,31 +68,23 @@ impl> System } } -pub trait IntoChainSystem: - IntoSystem + Sized +pub trait IntoChainSystem: System + Sized where - IntoB: IntoSystem, - SystemA: System, - SystemB: System, + SystemB: System, { - fn chain(self, system: IntoB) -> ChainSystem; + fn chain(self, system: SystemB) -> ChainSystem; } -impl - IntoChainSystem for IntoA +impl IntoChainSystem for SystemA where SystemA: System, - SystemB: System, - IntoA: IntoSystem, - IntoB: IntoSystem, + SystemB: System, { - fn chain(self, system: IntoB) -> ChainSystem { - let system_a = self.system(); - let system_b = system.system(); + fn chain(self, system: SystemB) -> ChainSystem { ChainSystem { - name: Cow::Owned(format!("Chain({}, {})", system_a.name(), system_b.name())), - system_a, - system_b, + name: Cow::Owned(format!("Chain({}, {})", self.name(), system.name())), + system_a: self, + system_b: system, archetype_component_access: Default::default(), resource_access: Default::default(), id: SystemId::new(), diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 2a29afc9f554f..0b29e91371e0c 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -4,50 +4,43 @@ use crate::{ SystemState, TypeAccess, World, WorldQuery, }; use parking_lot::Mutex; -use std::{any::TypeId, sync::Arc}; - -pub struct In(pub Input); - -impl SystemParam for In { - #[inline] - unsafe fn get_param( - input: &mut Option, - _system_state: &mut SystemState, - _world: &World, - _resources: &Resources, - ) -> Option { - Some(In(input.take().unwrap())) - } - - fn init(_system_state: &mut SystemState, _world: &World, _resources: &mut Resources) {} +use std::{any::TypeId, marker::PhantomData, sync::Arc}; +pub trait SystemParam: Sized { + type Fetch: for<'a> FetchSystemParam<'a>; } -pub trait SystemParam: Sized { +pub trait FetchSystemParam<'a> { + type Item; fn init(system_state: &mut SystemState, world: &World, resources: &mut Resources); /// # Safety /// This call might access any of the input parameters in an unsafe way. Make sure the data access is safe in /// the context of the system scheduler unsafe fn get_param( - input: &mut Option, - system_state: &mut SystemState, - world: &World, - resources: &Resources, - ) -> Option; + system_state: &'a SystemState, + world: &'a World, + resources: &'a Resources, + ) -> Option; +} + +pub struct FetchQuery(PhantomData<(Q, F)>); + +impl<'a, Q: WorldQuery, F: QueryFilter> SystemParam for Query<'a, Q, F> { + type Fetch = FetchQuery; } -impl<'a, Q: WorldQuery, F: QueryFilter, Input> SystemParam for Query<'a, Q, F> { +impl<'a, Q: WorldQuery, F: QueryFilter> FetchSystemParam<'a> for FetchQuery { + type Item = Query<'a, Q, F>; + #[inline] unsafe fn get_param( - _input: &mut Option, - system_state: &mut SystemState, - world: &World, - _resources: &Resources, - ) -> Option { - let query_index = system_state.current_query_index; - let world: &'a World = std::mem::transmute(world); + system_state: &'a SystemState, + world: &'a World, + _resources: &'a Resources, + ) -> Option { + let query_index = *system_state.current_query_index.get(); let archetype_component_access: &'a TypeAccess = - std::mem::transmute(&system_state.query_archetype_component_accesses[query_index]); - system_state.current_query_index += 1; + &system_state.query_archetype_component_accesses[query_index]; + *system_state.current_query_index.get() += 1; Some(Query::new(world, archetype_component_access)) } @@ -63,16 +56,23 @@ impl<'a, Q: WorldQuery, F: QueryFilter, Input> SystemParam for Query<'a, } } -impl SystemParam for QuerySet { +pub struct FetchQuerySet(PhantomData); + +impl SystemParam for QuerySet { + type Fetch = FetchQuerySet; +} + +impl<'a, T: QueryTuple> FetchSystemParam<'a> for FetchQuerySet { + type Item = QuerySet; + #[inline] unsafe fn get_param( - _input: &mut Option, - system_state: &mut SystemState, - world: &World, - _resources: &Resources, - ) -> Option { - let query_index = system_state.current_query_index; - system_state.current_query_index += 1; + system_state: &'a SystemState, + world: &'a World, + _resources: &'a Resources, + ) -> Option { + let query_index = *system_state.current_query_index.get(); + *system_state.current_query_index.get() += 1; Some(QuerySet::new( world, &system_state.query_archetype_component_accesses[query_index], @@ -90,26 +90,39 @@ impl SystemParam for QuerySet { } } -impl<'a, Input> SystemParam for &'a mut Commands { +pub struct FetchCommands; + +impl<'a> SystemParam for &'a mut Commands { + type Fetch = FetchCommands; +} +impl<'a> FetchSystemParam<'a> for FetchCommands { + type Item = &'a mut Commands; + fn init(system_state: &mut SystemState, world: &World, _resources: &mut Resources) { - system_state - .commands - .set_entity_reserver(world.get_entity_reserver()) + // SAFE: this is called with unique access to SystemState + unsafe { + (&mut *system_state.commands.get()).set_entity_reserver(world.get_entity_reserver()) + } } #[inline] unsafe fn get_param( - _input: &mut Option, - system_state: &mut SystemState, - _world: &World, - _resources: &Resources, - ) -> Option { - let commands: &'a mut Commands = std::mem::transmute(&mut system_state.commands); - Some(commands) + system_state: &'a SystemState, + _world: &'a World, + _resources: &'a Resources, + ) -> Option { + Some(&mut *system_state.commands.get()) } } -impl SystemParam for Arc> { +pub struct FetchArcCommands; +impl SystemParam for Arc> { + type Fetch = FetchArcCommands; +} + +impl<'a> FetchSystemParam<'a> for FetchArcCommands { + type Item = Arc>; + fn init(system_state: &mut SystemState, world: &World, _resources: &mut Resources) { system_state.arc_commands.get_or_insert_with(|| { let mut commands = Commands::default(); @@ -120,16 +133,23 @@ impl SystemParam for Arc> { #[inline] unsafe fn get_param( - _input: &mut Option, - system_state: &mut SystemState, + system_state: &SystemState, _world: &World, _resources: &Resources, - ) -> Option { + ) -> Option { Some(system_state.arc_commands.as_ref().unwrap().clone()) } } -impl<'a, T: Resource, Input> SystemParam for Res<'a, T> { +pub struct FetchRes(PhantomData); + +impl<'a, T: Resource> SystemParam for Res<'a, T> { + type Fetch = FetchRes; +} + +impl<'a, T: Resource> FetchSystemParam<'a> for FetchRes { + type Item = Res<'a, T>; + fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { if system_state.resource_access.is_write(&TypeId::of::()) { panic!( @@ -144,18 +164,24 @@ impl<'a, T: Resource, Input> SystemParam for Res<'a, T> { #[inline] unsafe fn get_param( - _input: &mut Option, - _system_state: &mut SystemState, - _world: &World, - resources: &Resources, - ) -> Option { + _system_state: &'a SystemState, + _world: &'a World, + resources: &'a Resources, + ) -> Option { Some(Res::new( resources.get_unsafe_ref::(ResourceIndex::Global), )) } } -impl<'a, T: Resource, Input> SystemParam for ResMut<'a, T> { +pub struct FetchResMut(PhantomData); + +impl<'a, T: Resource> SystemParam for ResMut<'a, T> { + type Fetch = FetchResMut; +} +impl<'a, T: Resource> FetchSystemParam<'a> for FetchResMut { + type Item = ResMut<'a, T>; + fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { // If a system already has access to the resource in another parameter, then we fail early. // e.g. `fn(Res, ResMut)` or `fn(ResMut, ResMut)` must not be allowed. @@ -175,18 +201,25 @@ impl<'a, T: Resource, Input> SystemParam for ResMut<'a, T> { #[inline] unsafe fn get_param( - _input: &mut Option, - _system_state: &mut SystemState, - _world: &World, - resources: &Resources, - ) -> Option { + _system_state: &'a SystemState, + _world: &'a World, + resources: &'a Resources, + ) -> Option { let (value, _added, mutated) = resources.get_unsafe_ref_with_added_and_mutated::(ResourceIndex::Global); Some(ResMut::new(value, mutated)) } } -impl<'a, T: Resource, Input> SystemParam for ChangedRes<'a, T> { +pub struct FetchChangedRes(PhantomData); + +impl<'a, T: Resource> SystemParam for ChangedRes<'a, T> { + type Fetch = FetchChangedRes; +} + +impl<'a, T: Resource> FetchSystemParam<'a> for FetchChangedRes { + type Item = ChangedRes<'a, T>; + fn init(system_state: &mut SystemState, _world: &World, _resources: &mut Resources) { if system_state.resource_access.is_write(&TypeId::of::()) { panic!( @@ -201,11 +234,10 @@ impl<'a, T: Resource, Input> SystemParam for ChangedRes<'a, T> { #[inline] unsafe fn get_param( - _input: &mut Option, - _system_state: &mut SystemState, - _world: &World, - resources: &Resources, - ) -> Option { + _system_state: &'a SystemState, + _world: &'a World, + resources: &'a Resources, + ) -> Option { let (value, added, mutated) = resources.get_unsafe_ref_with_added_and_mutated::(ResourceIndex::Global); if *added.as_ptr() || *mutated.as_ptr() { @@ -216,7 +248,14 @@ impl<'a, T: Resource, Input> SystemParam for ChangedRes<'a, T> { } } -impl<'a, T: Resource + FromResources, Input> SystemParam for Local<'a, T> { +pub struct FetchLocal(PhantomData); + +impl<'a, T: Resource + FromResources> SystemParam for Local<'a, T> { + type Fetch = FetchLocal; +} +impl<'a, T: Resource + FromResources> FetchSystemParam<'a> for FetchLocal { + type Item = Local<'a, T>; + fn init(system_state: &mut SystemState, _world: &World, resources: &mut Resources) { if system_state .local_resource_access @@ -244,52 +283,61 @@ impl<'a, T: Resource + FromResources, Input> SystemParam for Local<'a, T> #[inline] unsafe fn get_param( - _input: &mut Option, - system_state: &mut SystemState, - _world: &World, - resources: &Resources, - ) -> Option { + system_state: &'a SystemState, + _world: &'a World, + resources: &'a Resources, + ) -> Option { Some(Local::new(resources, system_state.id)) } } +pub struct FetchParamTuple(PhantomData); +pub struct FetchOr(PhantomData); + macro_rules! impl_system_param_tuple { ($($param: ident),*) => { + impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { + type Fetch = FetchParamTuple<($($param::Fetch,)*)>; + } #[allow(unused_variables)] - impl),*> SystemParam for ($($param,)*) { + impl<'a, $($param: FetchSystemParam<'a>),*> FetchSystemParam<'a> for FetchParamTuple<($($param,)*)> { + type Item = ($($param::Item,)*); fn init(system_state: &mut SystemState, world: &World, resources: &mut Resources) { $($param::init(system_state, world, resources);)* } #[inline] unsafe fn get_param( - input: &mut Option, - system_state: &mut SystemState, - world: &World, - resources: &Resources, - ) -> Option { - Some(($($param::get_param(input, system_state, world, resources)?,)*)) + system_state: &'a SystemState, + world: &'a World, + resources: &'a Resources, + ) -> Option { + Some(($($param::get_param(system_state, world, resources)?,)*)) } } + impl<$($param: SystemParam),*> SystemParam for Or<($(Option<$param>,)*)> { + type Fetch = FetchOr<($($param::Fetch,)*)>; + } + #[allow(unused_variables)] #[allow(unused_mut)] #[allow(non_snake_case)] - impl),*> SystemParam for Or<($(Option<$param>,)*)> { + impl<'a, $($param: FetchSystemParam<'a>),*> FetchSystemParam<'a> for FetchOr<($($param,)*)> { + type Item = Or<($(Option<$param::Item>,)*)>; fn init(system_state: &mut SystemState, world: &World, resources: &mut Resources) { $($param::init(system_state, world, resources);)* } #[inline] unsafe fn get_param( - input: &mut Option, - system_state: &mut SystemState, - world: &World, - resources: &Resources, - ) -> Option { + system_state: &'a SystemState, + world: &'a World, + resources: &'a Resources, + ) -> Option { let mut has_some = false; $( - let $param = $param::get_param(input, system_state, world, resources); + let $param = $param::get_param(system_state, world, resources); if $param.is_some() { has_some = true; } diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index 4adb09b0278c7..2480e7f7b2f51 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_gilrs" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = ["Bevy Contributors ", "Carter Anderson "] description = "Gamepad system made using Gilrs for Bevy Engine" @@ -11,10 +11,10 @@ keywords = ["bevy"] [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_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_utils = { path = "../bevy_utils", version = "0.4.0" } # other gilrs = "0.8.0" \ No newline at end of file diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index f07d925709f37..0149a16fd98e3 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -2,6 +2,7 @@ mod converter; mod gilrs_system; use bevy_app::{prelude::*, startup_stage::PRE_STARTUP}; +use bevy_ecs::IntoSystem; use bevy_utils::tracing::error; use gilrs::GilrsBuilder; use gilrs_system::{gilrs_event_startup_system, gilrs_event_system}; @@ -18,8 +19,8 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { app.add_thread_local_resource(gilrs) - .add_startup_system_to_stage(PRE_STARTUP, gilrs_event_startup_system) - .add_system_to_stage(stage::PRE_EVENT, gilrs_event_system); + .add_startup_system_to_stage(PRE_STARTUP, gilrs_event_startup_system.system()) + .add_system_to_stage(stage::PRE_EVENT, gilrs_event_system.system()); } Err(err) => error!("Failed to start Gilrs. {}", err), } diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 65f7f9563a8bd..1b0937e69a160 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_gltf" -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_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_pbr = { path = "../bevy_pbr", version = "0.3.0" } -bevy_render = { path = "../bevy_render", version = "0.3.0" } -bevy_transform = { path = "../bevy_transform", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_scene = { path = "../bevy_scene", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", 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_pbr = { path = "../bevy_pbr", 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_math = { path = "../bevy_math", version = "0.4.0" } +bevy_scene = { path = "../bevy_scene", version = "0.4.0" } # other gltf = { version = "0.15.2", default-features = false, features = ["utils"] } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index a6b7e1384951f..25bad7a423cc9 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -11,7 +11,9 @@ use bevy_render::{ pipeline::PrimitiveTopology, prelude::{Color, Texture}, render_graph::base, - texture::{AddressMode, FilterMode, SamplerDescriptor, TextureFormat}, + texture::{ + AddressMode, Extent3d, FilterMode, SamplerDescriptor, TextureDimension, TextureFormat, + }, }; use bevy_scene::Scene; use bevy_transform::{ @@ -21,7 +23,7 @@ use bevy_transform::{ use gltf::{ mesh::Mode, texture::{MagFilter, MinFilter, WrappingMode}, - Primitive, + Material, Primitive, }; use image::{GenericImageView, ImageFormat}; use std::path::Path; @@ -30,23 +32,23 @@ use thiserror::Error; /// An error that occurs when loading a GLTF file #[derive(Error, Debug)] pub enum GltfError { - #[error("Unsupported primitive mode.")] + #[error("unsupported primitive mode")] UnsupportedPrimitive { mode: Mode }, - #[error("Unsupported min filter.")] + #[error("unsupported min filter")] UnsupportedMinFilter { filter: MinFilter }, - #[error("Invalid GLTF file.")] + #[error("invalid GLTF file")] Gltf(#[from] gltf::Error), - #[error("Binary blob is missing.")] + #[error("binary blob is missing")] MissingBlob, - #[error("Failed to decode base64 mesh data.")] + #[error("failed to decode base64 mesh data")] Base64Decode(#[from] base64::DecodeError), - #[error("Unsupported buffer format.")] + #[error("unsupported buffer format")] BufferFormatUnsupported, - #[error("Invalid image mime type.")] + #[error("invalid image mime type")] InvalidImageMimeType(String), - #[error("Failed to load an image.")] + #[error("failed to load an image")] ImageError(#[from] image::ImageError), - #[error("Failed to load an asset path.")] + #[error("failed to load an asset path")] AssetIoError(#[from] AssetIoError), } @@ -136,7 +138,8 @@ async fn load_gltf<'a, 'b>( &texture_label, LoadedAsset::new(Texture { data: image.clone().into_vec(), - size: bevy_math::f32::vec2(size.0 as f32, size.1 as f32), + size: Extent3d::new(size.0, size.1, 1), + dimension: TextureDimension::D2, format: TextureFormat::Rgba8Unorm, sampler: texture_sampler(&texture)?, }), @@ -145,38 +148,7 @@ async fn load_gltf<'a, 'b>( } for material in gltf.materials() { - let material_label = material_label(&material); - let pbr = material.pbr_metallic_roughness(); - let mut dependencies = Vec::new(); - let texture_handle = if let Some(info) = pbr.base_color_texture() { - match info.texture().source().source() { - gltf::image::Source::View { .. } => { - let label = texture_label(&info.texture()); - let path = AssetPath::new_ref(load_context.path(), Some(&label)); - Some(load_context.get_handle(path)) - } - gltf::image::Source::Uri { uri, .. } => { - let parent = load_context.path().parent().unwrap(); - let image_path = parent.join(uri); - let asset_path = AssetPath::new(image_path, None); - let handle = load_context.get_handle(asset_path.clone()); - dependencies.push(asset_path); - Some(handle) - } - } - } else { - None - }; - let color = pbr.base_color_factor(); - load_context.set_labeled_asset( - &material_label, - LoadedAsset::new(StandardMaterial { - albedo: Color::rgba(color[0], color[1], color[2], color[3]), - albedo_texture: texture_handle, - ..Default::default() - }) - .with_dependencies(dependencies), - ) + load_material(&material, load_context); } for scene in gltf.scenes() { @@ -202,6 +174,42 @@ async fn load_gltf<'a, 'b>( Ok(()) } +fn load_material(material: &Material, load_context: &mut LoadContext) { + let material_label = material_label(&material); + let pbr = material.pbr_metallic_roughness(); + let mut dependencies = Vec::new(); + let texture_handle = if let Some(info) = pbr.base_color_texture() { + match info.texture().source().source() { + gltf::image::Source::View { .. } => { + let label = texture_label(&info.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } + gltf::image::Source::Uri { uri, .. } => { + let parent = load_context.path().parent().unwrap(); + let image_path = parent.join(uri); + let asset_path = AssetPath::new(image_path, None); + let handle = load_context.get_handle(asset_path.clone()); + dependencies.push(asset_path); + Some(handle) + } + } + } else { + None + }; + + let color = pbr.base_color_factor(); + load_context.set_labeled_asset( + &material_label, + LoadedAsset::new(StandardMaterial { + albedo: Color::rgba(color[0], color[1], color[2], color[3]), + albedo_texture: texture_handle, + ..Default::default() + }) + .with_dependencies(dependencies), + ) +} + fn load_node( gltf_node: &gltf::Node, world_builder: &mut WorldChildBuilder, @@ -236,7 +244,7 @@ fn load_node( }; node.with(Camera { - name: Some(base::camera::CAMERA2D.to_owned()), + name: Some(base::camera::CAMERA_2D.to_owned()), projection_matrix: orthographic_projection.get_projection_matrix(), ..Default::default() }); @@ -255,7 +263,7 @@ fn load_node( perspective_projection.aspect_ratio = aspect_ratio; } node.with(Camera { - name: Some(base::camera::CAMERA3D.to_owned()), + name: Some(base::camera::CAMERA_3D.to_owned()), projection_matrix: perspective_projection.get_projection_matrix(), ..Default::default() }); @@ -268,13 +276,22 @@ fn load_node( if let Some(mesh) = gltf_node.mesh() { // append primitives for primitive in mesh.primitives() { + let material = primitive.material(); + let material_label = material_label(&material); + + // This will make sure we load the default material now since it would not have been + // added when iterating over all the gltf materials (since the default material is + // not explicitly listed in the gltf). + if !load_context.has_labeled_asset(&material_label) { + load_material(&material, load_context); + } + let primitive_label = primitive_label(&mesh, &primitive); let mesh_asset_path = AssetPath::new_ref(load_context.path(), Some(&primitive_label)); - let material = primitive.material(); - let material_label = material_label(&material); let material_asset_path = AssetPath::new_ref(load_context.path(), Some(&material_label)); + parent.spawn(PbrBundle { mesh: load_context.get_handle(mesh_asset_path), material: load_context.get_handle(material_asset_path), diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 0c4e22807fe7b..5349a01dbb7cc 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_input" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -18,10 +18,10 @@ serialize = ["serde"] [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 serde = { version = "1", features = ["derive"], optional = true } diff --git a/crates/bevy_input/src/input.rs b/crates/bevy_input/src/input.rs index 72876dcd2da09..31e0b9980c4b0 100644 --- a/crates/bevy_input/src/input.rs +++ b/crates/bevy_input/src/input.rs @@ -71,3 +71,80 @@ where self.just_released.iter() } } + +#[cfg(test)] +mod test { + + #[test] + fn input_test() { + use crate::Input; + + /// Used for testing `Input` functionality + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + enum DummyInput { + Input1, + Input2, + } + + let mut input = Input::default(); + + // Test pressing + input.press(DummyInput::Input1); + input.press(DummyInput::Input2); + + // Check if they were "just pressed" (pressed on this update) + assert!(input.just_pressed(DummyInput::Input1)); + assert!(input.just_pressed(DummyInput::Input2)); + + // Check if they are also marked as pressed + assert!(input.pressed(DummyInput::Input1)); + assert!(input.pressed(DummyInput::Input2)); + + // Update the `Input` and check press state + input.update(); + + // Check if they're marked "just pressed" + assert!(!input.just_pressed(DummyInput::Input1)); + assert!(!input.just_pressed(DummyInput::Input2)); + + // Check if they're marked as pressed + assert!(input.pressed(DummyInput::Input1)); + assert!(input.pressed(DummyInput::Input2)); + + // Release the inputs and check state + + input.release(DummyInput::Input1); + input.release(DummyInput::Input2); + + // Check if they're marked as "just released" (released on this update) + assert!(input.just_released(DummyInput::Input1)); + assert!(input.just_released(DummyInput::Input2)); + + // Check that they're not incorrectly marked as pressed + assert!(!input.pressed(DummyInput::Input1)); + assert!(!input.pressed(DummyInput::Input2)); + + // Update the `Input` and check for removal from `just_released` + + input.update(); + + // Check that they're not incorrectly marked as just released + assert!(!input.just_released(DummyInput::Input1)); + assert!(!input.just_released(DummyInput::Input2)); + + // Set up an `Input` to test resetting. + let mut input = Input::default(); + + input.press(DummyInput::Input1); + input.release(DummyInput::Input2); + + // Reset the `Input` and test it was reset correctly. + input.reset(DummyInput::Input1); + input.reset(DummyInput::Input2); + + assert!(!input.just_pressed(DummyInput::Input1)); + assert!(!input.pressed(DummyInput::Input1)); + + assert!(!input.just_released(DummyInput::Input2)); + } +} diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index a21fbca1e4b69..ba81fc2bab987 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -7,6 +7,7 @@ pub mod system; pub mod touch; pub use axis::*; +use bevy_ecs::IntoSystem; pub use input::*; pub mod prelude { @@ -27,7 +28,6 @@ use keyboard::{keyboard_input_system, KeyCode, KeyboardInput}; use mouse::{mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion, MouseWheel}; use touch::{touch_screen_input_system, TouchInput, Touches}; -use bevy_app::startup_stage::STARTUP; use gamepad::{ gamepad_event_system, GamepadAxis, GamepadButton, GamepadEvent, GamepadEventRaw, GamepadSettings, @@ -44,20 +44,23 @@ impl Plugin for InputPlugin { .add_event::() .add_event::() .init_resource::>() - .add_system_to_stage(bevy_app::stage::EVENT, keyboard_input_system) + .add_system_to_stage(bevy_app::stage::EVENT, keyboard_input_system.system()) .init_resource::>() - .add_system_to_stage(bevy_app::stage::EVENT, mouse_button_input_system) + .add_system_to_stage(bevy_app::stage::EVENT, mouse_button_input_system.system()) .add_event::() .add_event::() .init_resource::() .init_resource::>() .init_resource::>() .init_resource::>() - .add_system_to_stage(bevy_app::stage::EVENT, gamepad_event_system) - .add_startup_system_to_stage(STARTUP, gamepad_event_system) + .add_system_to_stage(bevy_app::stage::EVENT, gamepad_event_system.system()) + .add_startup_system_to_stage( + bevy_app::startup_stage::STARTUP, + gamepad_event_system.system(), + ) .add_event::() .init_resource::() - .add_system_to_stage(bevy_app::stage::EVENT, touch_screen_input_system); + .add_system_to_stage(bevy_app::stage::EVENT, touch_screen_input_system.system()); } } diff --git a/crates/bevy_input/src/mouse.rs b/crates/bevy_input/src/mouse.rs index 3123785fb069a..f20cfb7331c6d 100644 --- a/crates/bevy_input/src/mouse.rs +++ b/crates/bevy_input/src/mouse.rs @@ -17,7 +17,7 @@ pub enum MouseButton { Left, Right, Middle, - Other(u8), + Other(u16), } /// A mouse motion event diff --git a/crates/bevy_input/src/touch.rs b/crates/bevy_input/src/touch.rs index 9e78f7d05bedf..31f65d2477924 100644 --- a/crates/bevy_input/src/touch.rs +++ b/crates/bevy_input/src/touch.rs @@ -2,7 +2,6 @@ use bevy_app::{EventReader, Events}; use bevy_ecs::{Local, Res, ResMut}; use bevy_math::Vec2; use bevy_utils::HashMap; -use core::ops::DerefMut; /// Represents a touch event /// @@ -85,13 +84,13 @@ pub struct TouchSystemState { #[derive(Debug, Clone, Copy)] pub struct Touch { - pub id: u64, - pub start_position: Vec2, - pub start_force: Option, - pub previous_position: Vec2, - pub previous_force: Option, - pub position: Vec2, - pub force: Option, + id: u64, + start_position: Vec2, + start_force: Option, + previous_position: Vec2, + previous_force: Option, + position: Vec2, + force: Option, } impl Touch { @@ -102,6 +101,36 @@ impl Touch { pub fn distance(&self) -> Vec2 { self.position - self.start_position } + + #[inline] + pub fn id(&self) -> u64 { + self.id + } + + #[inline] + pub fn start_position(&self) -> Vec2 { + self.start_position + } + + #[inline] + pub fn start_force(&self) -> Option { + self.start_force + } + + #[inline] + pub fn previous_position(&self) -> Vec2 { + self.previous_position + } + + #[inline] + pub fn position(&self) -> Vec2 { + self.position + } + + #[inline] + pub fn force(&self) -> Option { + self.force + } } impl From<&TouchInput> for Touch { @@ -139,10 +168,8 @@ impl Touches { self.just_pressed.contains_key(&id) } - pub fn iter_just_pressed(&self) -> impl Iterator + '_ { - self.just_pressed - .iter() - .map(move |(id, _)| self.pressed.get(id).unwrap()) + pub fn iter_just_pressed(&self) -> impl Iterator { + self.just_pressed.values() } pub fn get_released(&self, id: u64) -> Option<&Touch> { @@ -153,54 +180,234 @@ impl Touches { self.just_released.contains_key(&id) } - pub fn iter_just_released(&self) -> impl Iterator + '_ { - self.just_released - .iter() - .map(move |(id, _)| self.pressed.get(id).unwrap()) + pub fn iter_just_released(&self) -> impl Iterator { + self.just_released.values() } pub fn just_cancelled(&self, id: u64) -> bool { self.just_cancelled.contains_key(&id) } - pub fn iter_just_cancelled(&self) -> impl Iterator + '_ { - self.just_cancelled - .iter() - .map(move |(id, _)| self.pressed.get(id).unwrap()) + pub fn iter_just_cancelled(&self) -> impl Iterator { + self.just_cancelled.values() } -} -/// Updates the Touches resource with the latest TouchInput events -pub fn touch_screen_input_system( - mut state: Local, - mut touch_state: ResMut, - touch_input_events: Res>, -) { - let touch_state = touch_state.deref_mut(); - touch_state.just_pressed.clear(); - touch_state.just_released.clear(); - for event in state.touch_event_reader.iter(&touch_input_events) { + fn process_touch_event(&mut self, event: &TouchInput) { match event.phase { TouchPhase::Started => { - touch_state.pressed.insert(event.id, event.into()); - touch_state.just_pressed.insert(event.id, event.into()); + self.pressed.insert(event.id, event.into()); + self.just_pressed.insert(event.id, event.into()); } TouchPhase::Moved => { - let mut new_touch = touch_state.pressed.get(&event.id).cloned().unwrap(); + let mut new_touch = self.pressed.get(&event.id).cloned().unwrap(); new_touch.previous_position = new_touch.position; new_touch.previous_force = new_touch.force; new_touch.position = event.position; new_touch.force = event.force; - touch_state.pressed.insert(event.id, new_touch); + self.pressed.insert(event.id, new_touch); } TouchPhase::Ended => { - touch_state.just_released.insert(event.id, event.into()); - touch_state.pressed.remove_entry(&event.id); + self.just_released.insert(event.id, event.into()); + self.pressed.remove_entry(&event.id); } TouchPhase::Cancelled => { - touch_state.just_cancelled.insert(event.id, event.into()); - touch_state.pressed.remove_entry(&event.id); + self.just_cancelled.insert(event.id, event.into()); + self.pressed.remove_entry(&event.id); } }; } + + fn update(&mut self) { + self.just_pressed.clear(); + self.just_released.clear(); + self.just_cancelled.clear(); + } +} + +/// Updates the Touches resource with the latest TouchInput events +pub fn touch_screen_input_system( + mut state: Local, + mut touch_state: ResMut, + touch_input_events: Res>, +) { + touch_state.update(); + + for event in state.touch_event_reader.iter(&touch_input_events) { + touch_state.process_touch_event(event); + } +} + +#[cfg(test)] +mod test { + + #[test] + fn touch_update() { + use crate::{touch::Touch, Touches}; + use bevy_math::Vec2; + + let mut touches = Touches::default(); + + let touch_event = Touch { + id: 4, + start_position: Vec2::new(0.0, 0.0), + start_force: None, + previous_position: Vec2::new(0.0, 0.0), + previous_force: None, + position: Vec2::new(0.0, 0.0), + force: None, + }; + + // Add a touch to `just_pressed`, 'just_released', and 'just cancelled' + + touches.just_pressed.insert(4, touch_event); + touches.just_released.insert(4, touch_event); + touches.just_cancelled.insert(4, touch_event); + + touches.update(); + + // Verify that all the `just_x` maps are cleared + assert!(touches.just_pressed.is_empty()); + assert!(touches.just_released.is_empty()); + assert!(touches.just_cancelled.is_empty()); + } + + #[test] + fn touch_process() { + use crate::{touch::TouchPhase, TouchInput, Touches}; + use bevy_math::Vec2; + + let mut touches = Touches::default(); + + // Test adding a `TouchPhase::Started` + + let touch_event = TouchInput { + phase: TouchPhase::Started, + position: Vec2::new(4.0, 4.0), + force: None, + id: 4, + }; + + touches.update(); + touches.process_touch_event(&touch_event); + + assert!(touches.pressed.get(&touch_event.id).is_some()); + assert!(touches.just_pressed.get(&touch_event.id).is_some()); + + // Test adding a `TouchPhase::Moved` + + let moved_touch_event = TouchInput { + phase: TouchPhase::Moved, + position: Vec2::new(5.0, 5.0), + force: None, + id: touch_event.id, + }; + + touches.update(); + touches.process_touch_event(&moved_touch_event); + + assert_eq!( + touches + .pressed + .get(&moved_touch_event.id) + .expect("Missing from pressed after move.") + .previous_position, + touch_event.position + ); + + // Test cancelling an event + + let cancel_touch_event = TouchInput { + phase: TouchPhase::Cancelled, + position: Vec2::new(1.0, 1.0), + force: None, + id: touch_event.id, + }; + + touches.update(); + touches.process_touch_event(&cancel_touch_event); + + assert!(touches.just_cancelled.get(&cancel_touch_event.id).is_some()); + assert!(touches.pressed.get(&cancel_touch_event.id).is_none()); + + // Test ending an event + + let end_touch_event = TouchInput { + phase: TouchPhase::Ended, + position: Vec2::new(4.0, 4.0), + force: None, + id: 4, + }; + + touches.update(); + touches.process_touch_event(&touch_event); + touches.process_touch_event(&end_touch_event); + + assert!(touches.just_released.get(&touch_event.id).is_some()); + assert!(touches.pressed.get(&touch_event.id).is_none()); + } + + #[test] + fn touch_pressed() { + use crate::{touch::TouchPhase, TouchInput, Touches}; + use bevy_math::Vec2; + + let mut touches = Touches::default(); + + let touch_event = TouchInput { + phase: TouchPhase::Started, + position: Vec2::new(4.0, 4.0), + force: None, + id: 4, + }; + + // Register the touch and test that it was registered correctly + touches.process_touch_event(&touch_event); + + assert!(touches.get_pressed(touch_event.id).is_some()); + assert!(touches.just_pressed(touch_event.id)); + assert_eq!(touches.iter().count(), 1); + } + + #[test] + fn touch_released() { + use crate::{touch::TouchPhase, TouchInput, Touches}; + use bevy_math::Vec2; + + let mut touches = Touches::default(); + + let touch_event = TouchInput { + phase: TouchPhase::Ended, + position: Vec2::new(4.0, 4.0), + force: None, + id: 4, + }; + + // Register the touch and test that it was registered correctly + touches.process_touch_event(&touch_event); + + assert!(touches.get_released(touch_event.id).is_some()); + assert!(touches.just_released(touch_event.id)); + assert_eq!(touches.iter_just_released().count(), 1); + } + + #[test] + fn touch_cancelled() { + use crate::{touch::TouchPhase, TouchInput, Touches}; + use bevy_math::Vec2; + + let mut touches = Touches::default(); + + let touch_event = TouchInput { + phase: TouchPhase::Cancelled, + position: Vec2::new(4.0, 4.0), + force: None, + id: 4, + }; + + // Register the touch and test that it was registered correctly + touches.process_touch_event(&touch_event); + + assert!(touches.just_cancelled(touch_event.id)); + assert_eq!(touches.iter_just_cancelled().count(), 1); + } } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 41bc4ffdbab47..b2ee4dffb293f 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_internal" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -21,6 +21,9 @@ trace_chrome = [ "bevy_log/tracing-chrome" ] # Image format support for texture loading (PNG and HDR are enabled by default) hdr = ["bevy_render/hdr"] png = ["bevy_render/png"] +dds = ["bevy_render/dds"] +tga = ["bevy_render/tga"] +jpeg = ["bevy_render/jpeg"] # Audio format support (MP3 is enabled by default) flac = ["bevy_audio/flac"] @@ -36,34 +39,33 @@ x11 = ["bevy_winit/x11"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_asset = { path = "../bevy_asset", version = "0.3.0" } -bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" } -bevy_core = { path = "../bevy_core", version = "0.3.0" } -bevy_derive = { path = "../bevy_derive", version = "0.3.0" } -bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.3.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_input = { path = "../bevy_input", version = "0.3.0" } -bevy_log = { path = "../bevy_log", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_property = { path = "../bevy_property", version = "0.3.0" } -bevy_scene = { path = "../bevy_scene", version = "0.3.0" } -bevy_transform = { path = "../bevy_transform", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } -bevy_window = { path = "../bevy_window", version = "0.3.0" } -bevy_tasks = { path = "../bevy_tasks", 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_diagnostic = { path = "../bevy_diagnostic", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_input = { path = "../bevy_input", version = "0.4.0" } +bevy_log = { path = "../bevy_log", 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_scene = { path = "../bevy_scene", version = "0.4.0" } +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } +bevy_window = { path = "../bevy_window", version = "0.4.0" } +bevy_tasks = { path = "../bevy_tasks", version = "0.4.0" } # bevy (optional) -bevy_audio = { path = "../bevy_audio", optional = true, version = "0.3.0" } -bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.3.0" } -bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.3.0" } -bevy_render = { path = "../bevy_render", optional = true, version = "0.3.0" } -bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.3.0" } -bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.3.0" } -bevy_text = { path = "../bevy_text", optional = true, version = "0.3.0" } -bevy_ui = { path = "../bevy_ui", optional = true, version = "0.3.0" } -bevy_wgpu = { path = "../bevy_wgpu", optional = true, version = "0.3.0" } -bevy_winit = { path = "../bevy_winit", optional = true, version = "0.3.0" } -bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.3.0" } +bevy_audio = { path = "../bevy_audio", optional = true, version = "0.4.0" } +bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.4.0" } +bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.4.0" } +bevy_render = { path = "../bevy_render", optional = true, version = "0.4.0" } +bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.4.0" } +bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.4.0" } +bevy_text = { path = "../bevy_text", optional = true, version = "0.4.0" } +bevy_ui = { path = "../bevy_ui", optional = true, version = "0.4.0" } +bevy_wgpu = { path = "../bevy_wgpu", optional = true, version = "0.4.0" } +bevy_winit = { path = "../bevy_winit", optional = true, version = "0.4.0" } +bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.4.0" } [target.'cfg(target_os = "android")'.dependencies] ndk-glue = {version = "0.2", features = ["logger"]} diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 0af6f824ae583..e416c8b17ad7d 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -5,7 +5,7 @@ pub struct DefaultPlugins; impl PluginGroup for DefaultPlugins { fn build(&mut self, group: &mut PluginGroupBuilder) { group.add(bevy_log::LogPlugin::default()); - group.add(bevy_type_registry::TypeRegistryPlugin::default()); + group.add(bevy_reflect::ReflectPlugin::default()); group.add(bevy_core::CorePlugin::default()); group.add(bevy_transform::TransformPlugin::default()); group.add(bevy_diagnostic::DiagnosticsPlugin::default()); @@ -50,7 +50,7 @@ pub struct MinimalPlugins; impl PluginGroup for MinimalPlugins { fn build(&mut self, group: &mut PluginGroupBuilder) { - group.add(bevy_type_registry::TypeRegistryPlugin::default()); + group.add(bevy_reflect::ReflectPlugin::default()); group.add(bevy_core::CorePlugin::default()); group.add(bevy_app::ScheduleRunnerPlugin::default()); } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 4af34e8a8fe60..4f2450bfa66d1 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -44,9 +44,12 @@ pub mod math { pub use bevy_math::*; } -pub mod property { - //! Dynamically interact with struct fields and names. - pub use bevy_property::*; +pub mod reflect { + // TODO: remove these renames once TypeRegistryArc is no longer required + //! Type reflection used for dynamically interacting with rust types. + pub use bevy_reflect::{ + TypeRegistry as TypeRegistryInternal, TypeRegistryArc as TypeRegistry, *, + }; } pub mod scene { @@ -64,11 +67,6 @@ pub mod transform { pub use bevy_transform::*; } -pub mod type_registry { - //! Registered types and components can be used when loading scenes. - pub use bevy_type_registry::*; -} - pub mod utils { pub use bevy_utils::*; } diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 17a8cda72c935..354ea005f293f 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -1,8 +1,7 @@ pub use crate::{ app::prelude::*, asset::prelude::*, core::prelude::*, ecs::prelude::*, input::prelude::*, - log::prelude::*, math::prelude::*, property::prelude::*, scene::prelude::*, - transform::prelude::*, type_registry::RegisterType, window::prelude::*, DefaultPlugins, - MinimalPlugins, + log::prelude::*, math::prelude::*, reflect::prelude::*, scene::prelude::*, + transform::prelude::*, window::prelude::*, DefaultPlugins, MinimalPlugins, }; pub use bevy_derive::bevy_main; diff --git a/crates/bevy_log/Cargo.toml b/crates/bevy_log/Cargo.toml index d3c482932ae92..32c0ad3281a8e 100644 --- a/crates/bevy_log/Cargo.toml +++ b/crates/bevy_log/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_log" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -14,11 +14,11 @@ keywords = ["bevy"] [dependencies] -bevy_app = { path = "../bevy_app", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } tracing-subscriber = {version = "0.2.15", features = ["registry"]} -tracing-chrome = { version = "0.2.0", optional = true } +tracing-chrome = { version = "0.3.0", optional = true } [target.'cfg(target_os = "android")'.dependencies] android_log-sys = "0.2.0" diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 4bb1af857a472..397d4937d52b5 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -12,6 +12,8 @@ pub use bevy_utils::tracing::{ }; use bevy_app::{AppBuilder, Plugin}; +#[cfg(feature = "tracing-chrome")] +use tracing_subscriber::fmt::{format::DefaultFields, FormattedFields}; use tracing_subscriber::{prelude::*, registry::Registry, EnvFilter}; /// Adds logging to Apps. @@ -31,7 +33,7 @@ pub struct LogSettings { impl Default for LogSettings { fn default() -> Self { Self { - filter: "wgpu=warn".to_string(), + filter: "wgpu=error".to_string(), level: Level::INFO, } } @@ -55,17 +57,30 @@ impl Plugin for LogPlugin { let subscriber = subscriber.with(fmt_layer); #[cfg(feature = "tracing-chrome")] { - let (chrome_layer, guard) = tracing_chrome::ChromeLayerBuilder::new().build(); + let (chrome_layer, guard) = tracing_chrome::ChromeLayerBuilder::new() + .name_fn(Box::new(|event_or_span| match event_or_span { + tracing_chrome::EventOrSpan::Event(event) => event.metadata().name().into(), + tracing_chrome::EventOrSpan::Span(span) => { + if let Some(fields) = + span.extensions().get::>() + { + format!("{}: {}", span.metadata().name(), fields.fields.as_str()) + } else { + span.metadata().name().into() + } + } + })) + .build(); app.resources_mut().insert_thread_local(guard); let subscriber = subscriber.with(chrome_layer); bevy_utils::tracing::subscriber::set_global_default(subscriber) - .expect("Could not set global default tracing subscriber"); + .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); } #[cfg(not(feature = "tracing-chrome"))] { bevy_utils::tracing::subscriber::set_global_default(subscriber) - .expect("Could not set global default tracing subscriber"); + .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); } } @@ -76,14 +91,14 @@ impl Plugin for LogPlugin { tracing_wasm::WASMLayerConfig::default(), )); bevy_utils::tracing::subscriber::set_global_default(subscriber) - .expect("Could not set global default tracing subscriber"); + .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); } #[cfg(target_os = "android")] { let subscriber = subscriber.with(android_tracing::AndroidLayer::default()); bevy_utils::tracing::subscriber::set_global_default(subscriber) - .expect("Could not set global default tracing subscriber"); + .expect("Could not set global default tracing subscriber. If you've already set up a tracing subscriber, please disable LogPlugin from Bevy's DefaultPlugins"); } } } diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index 22870fc6677f7..469312111bd4f 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_math" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -13,4 +13,5 @@ license = "MIT" keywords = ["bevy"] [dependencies] -glam = { version = "0.10.0", features = ["serde"] } +glam = { version = "0.11.0", features = ["serde"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } diff --git a/crates/bevy_math/src/face_toward.rs b/crates/bevy_math/src/face_toward.rs index d090989fd4ccd..f873964b18afc 100644 --- a/crates/bevy_math/src/face_toward.rs +++ b/crates/bevy_math/src/face_toward.rs @@ -19,3 +19,23 @@ impl FaceToward for Mat4 { ) } } + +#[cfg(test)] +mod test { + #[test] + fn face_toward_mat4() { + use crate::{FaceToward, Mat4, Vec3, Vec4}; + + // Completely arbitrary arguments + let matrix = Mat4::face_toward( + Vec3::new(50.0, 60.0, 0.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, 1.0, 0.0), + ); + + assert_eq!(matrix.x_axis, Vec4::new(0.0, 0.0, -1.0, -0.0)); + assert_eq!(matrix.y_axis, Vec4::new(-0.7682213, 0.6401844, 0.0, 0.0)); + assert_eq!(matrix.z_axis, Vec4::new(0.6401844, 0.7682213, 0.0, 0.0)); + assert_eq!(matrix.w_axis, Vec4::new(50.0, 60.0, 0.0, 1.0)); + } +} diff --git a/crates/bevy_math/src/geometry.rs b/crates/bevy_math/src/geometry.rs index 2ded532e060c9..27a0073c1c6f3 100644 --- a/crates/bevy_math/src/geometry.rs +++ b/crates/bevy_math/src/geometry.rs @@ -1,20 +1,21 @@ +use bevy_reflect::Reflect; use glam::Vec2; use std::ops::{Add, AddAssign}; /// A two dimensional "size" as defined by a width and height -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct Size { +#[derive(Copy, Clone, PartialEq, Debug, Reflect)] +pub struct Size { pub width: T, pub height: T, } -impl Size { +impl Size { pub fn new(width: T, height: T) -> Self { Size { width, height } } } -impl Default for Size { +impl Default for Size { fn default() -> Self { Self { width: Default::default(), @@ -24,15 +25,15 @@ impl Default for Size { } /// A rect, as defined by its "side" locations -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct Rect { +#[derive(Copy, Clone, PartialEq, Debug, Reflect)] +pub struct Rect { pub left: T, pub right: T, pub top: T, pub bottom: T, } -impl Rect { +impl Rect { pub fn all(value: T) -> Self where T: Clone, @@ -46,7 +47,7 @@ impl Rect { } } -impl Default for Rect { +impl Default for Rect { fn default() -> Self { Self { left: Default::default(), @@ -57,7 +58,7 @@ impl Default for Rect { } } -impl Add for Size +impl Add for Size where T: Add, { @@ -71,7 +72,7 @@ where } } -impl AddAssign for Size +impl AddAssign for Size where T: AddAssign, { diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index d2591350dee14..8a09959024cb7 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_pbr" -version = "0.3.0" +version = "0.4.0" edition = "2018" authors = [ "Bevy Contributors ", @@ -13,14 +13,13 @@ license = "MIT" keywords = ["bevy"] [dependencies] -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_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_window = { path = "../bevy_window", 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_render = { path = "../bevy_render", version = "0.4.0" } +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_window = { path = "../bevy_window", version = "0.4.0" } diff --git a/crates/bevy_pbr/src/entity.rs b/crates/bevy_pbr/src/entity.rs index 4c355e90b2d89..5cf11e89844e4 100644 --- a/crates/bevy_pbr/src/entity.rs +++ b/crates/bevy_pbr/src/entity.rs @@ -5,6 +5,7 @@ use bevy_render::{ draw::Draw, mesh::Mesh, pipeline::{RenderPipeline, RenderPipelines}, + prelude::Visible, render_graph::base::MainPass, }; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -16,6 +17,7 @@ pub struct PbrBundle { pub material: Handle, pub main_pass: MainPass, pub draw: Draw, + pub visible: Visible, pub render_pipelines: RenderPipelines, pub transform: Transform, pub global_transform: GlobalTransform, @@ -25,9 +27,10 @@ impl Default for PbrBundle { fn default() -> Self { Self { render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - FORWARD_PIPELINE_HANDLE, + FORWARD_PIPELINE_HANDLE.typed(), )]), mesh: Default::default(), + visible: Default::default(), material: Default::default(), main_pass: Default::default(), draw: Default::default(), diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index bf9577efce75f..fc7a5b2c061ce 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -4,6 +4,7 @@ mod entity; mod light; mod material; +use bevy_ecs::IntoSystem; pub use entity::*; pub use light::*; pub use material::*; @@ -14,9 +15,8 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::{AddAsset, Assets, Handle}; +use bevy_reflect::RegisterTypeBuilder; use bevy_render::{prelude::Color, render_graph::RenderGraph, shader}; -use bevy_type_registry::RegisterType; -use light::Light; use material::StandardMaterial; use render_graph::add_pbr_graph; @@ -27,10 +27,10 @@ pub struct PbrPlugin; impl Plugin for PbrPlugin { fn build(&self, app: &mut AppBuilder) { app.add_asset::() - .register_component::() + .register_type::() .add_system_to_stage( stage::POST_UPDATE, - shader::asset_shader_defs_system::, + shader::asset_shader_defs_system::.system(), ) .init_resource::(); let resources = app.resources(); diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 40f81403846ad..c0a0bb65dd31f 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -1,5 +1,5 @@ use bevy_core::Byteable; -use bevy_property::Properties; +use bevy_reflect::{Reflect, ReflectComponent}; use bevy_render::{ camera::{CameraProjection, PerspectiveProjection}, color::Color, @@ -8,7 +8,8 @@ use bevy_transform::components::GlobalTransform; use std::ops::Range; /// A point light -#[derive(Debug, Properties)] +#[derive(Debug, Reflect)] +#[reflect(Component)] pub struct Light { pub color: Color, pub fov: f32, diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 6f63146730685..7973c0a6f659e 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/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; /// A material with "standard" properties used in PBR lighting #[derive(Debug, RenderResources, ShaderDefs, TypeUuid)] diff --git a/crates/bevy_pbr/src/render_graph/forward_pipeline/forward.vert b/crates/bevy_pbr/src/render_graph/forward_pipeline/forward.vert index 00cd153602e63..028a86389c012 100644 --- a/crates/bevy_pbr/src/render_graph/forward_pipeline/forward.vert +++ b/crates/bevy_pbr/src/render_graph/forward_pipeline/forward.vert @@ -17,7 +17,6 @@ layout(set = 2, binding = 0) uniform Transform { }; void main() { - v_Normal = (Model * vec4(Vertex_Normal, 1.0)).xyz; v_Normal = mat3(Model) * Vertex_Normal; v_Position = (Model * vec4(Vertex_Position, 1.0)).xyz; v_Uv = Vertex_Uv; diff --git a/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs b/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs index e87425ecb5f83..8a3c17e6e1718 100644 --- a/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs +++ b/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs @@ -1,4 +1,5 @@ -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Assets, HandleUntyped}; +use bevy_reflect::TypeUuid; use bevy_render::{ pipeline::{ BlendDescriptor, BlendFactor, BlendOperation, ColorStateDescriptor, ColorWrite, @@ -8,10 +9,9 @@ use bevy_render::{ shader::{Shader, ShaderStage, ShaderStages}, texture::TextureFormat, }; -use bevy_type_registry::TypeUuid; -pub const FORWARD_PIPELINE_HANDLE: Handle = - Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 13148362314012771389); +pub const FORWARD_PIPELINE_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 13148362314012771389); pub(crate) fn build_forward_pipeline(shaders: &mut Assets) -> PipelineDescriptor { PipelineDescriptor { diff --git a/crates/bevy_pbr/src/render_graph/lights_node.rs b/crates/bevy_pbr/src/render_graph/lights_node.rs index 12b720a973a1a..a4a5d4bbe1fdb 100644 --- a/crates/bevy_pbr/src/render_graph/lights_node.rs +++ b/crates/bevy_pbr/src/render_graph/lights_node.rs @@ -51,7 +51,7 @@ struct LightCount { unsafe impl Byteable for LightCount {} impl SystemNode for LightsNode { - fn get_system(&self, commands: &mut Commands) -> Box> { + fn get_system(&self, commands: &mut Commands) -> Box> { let system = lights_node_system.system(); commands.insert_local_resource( system.id(), diff --git a/crates/bevy_property/Cargo.toml b/crates/bevy_property/Cargo.toml deleted file mode 100644 index b4e503ee7000c..0000000000000 --- a/crates/bevy_property/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "bevy_property" -version = "0.3.0" -edition = "2018" -authors = [ - "Bevy Contributors ", - "Carter Anderson ", -] -description = "Dynamically interact with struct fields using their names" -homepage = "https://bevyengine.org" -repository = "https://github.com/bevyengine/bevy" -license = "MIT" -keywords = ["bevy"] - -[dependencies] -# bevy -bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" } -bevy_math = { path = "../bevy_math", version = "0.3.0" } -bevy_property_derive = { path = "bevy_property_derive", version = "0.3.0" } -bevy_utils = { path = "../bevy_utils", version = "0.3.0" } - -# other -erased-serde = "0.3" -ron = "0.6.2" -serde = "1" -smallvec = { version = "1.4", features = ["serde"] } diff --git a/crates/bevy_property/bevy_property_derive/src/lib.rs b/crates/bevy_property/bevy_property_derive/src/lib.rs deleted file mode 100644 index 6b16fda9048ea..0000000000000 --- a/crates/bevy_property/bevy_property_derive/src/lib.rs +++ /dev/null @@ -1,431 +0,0 @@ -extern crate proc_macro; - -mod modules; - -use find_crate::Manifest; -use modules::{get_modules, get_path}; -use proc_macro::TokenStream; -use quote::quote; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - punctuated::Punctuated, - token::{Comma, Where}, - Data, DataStruct, DeriveInput, Field, Fields, Generics, Ident, Index, Member, -}; - -#[derive(Default)] -struct PropAttributeArgs { - pub ignore: Option, -} - -static PROP_ATTRIBUTE_NAME: &str = "property"; - -#[proc_macro_derive(Properties, attributes(property, module))] -pub fn derive_properties(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let unit_struct_punctuated = Punctuated::new(); - let fields = match &ast.data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => &fields.named, - Data::Struct(DataStruct { - fields: Fields::Unnamed(fields), - .. - }) => &fields.unnamed, - Data::Struct(DataStruct { - fields: Fields::Unit, - .. - }) => &unit_struct_punctuated, - _ => panic!("expected a struct with named fields"), - }; - let fields_and_args = fields - .iter() - .enumerate() - .map(|(i, f)| { - ( - f, - f.attrs - .iter() - .find(|a| *a.path.get_ident().as_ref().unwrap() == PROP_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_property_path = get_path(&modules.bevy_property); - - 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 generics = ast.generics; - let (impl_generics, ty_generics, _where_clause) = generics.split_for_impl(); - - let struct_name = &ast.ident; - - TokenStream::from(quote! { - impl #impl_generics #bevy_property_path::Properties for #struct_name#ty_generics { - fn prop(&self, name: &str) -> Option<&dyn #bevy_property_path::Property> { - match name { - #(#field_names => Some(&self.#field_idents),)* - _ => None, - } - } - - fn prop_mut(&mut self, name: &str) -> Option<&mut dyn #bevy_property_path::Property> { - match name { - #(#field_names => Some(&mut self.#field_idents),)* - _ => None, - } - } - - fn prop_with_index(&self, index: usize) -> Option<&dyn #bevy_property_path::Property> { - match index { - #(#field_indices => Some(&self.#field_idents),)* - _ => None, - } - } - - fn prop_with_index_mut(&mut self, index: usize) -> Option<&mut dyn #bevy_property_path::Property> { - match index { - #(#field_indices => Some(&mut self.#field_idents),)* - _ => None, - } - } - - fn prop_name(&self, index: usize) -> Option<&str> { - match index { - #(#field_indices => Some(#field_names),)* - _ => None, - } - } - - fn prop_len(&self) -> usize { - #field_count - } - - fn iter_props(&self) -> #bevy_property_path::PropertyIter { - #bevy_property_path::PropertyIter::new(self) - } - } - - impl #impl_generics #bevy_property_path::DeserializeProperty for #struct_name#ty_generics { - fn deserialize( - deserializer: &mut dyn #bevy_property_path::erased_serde::Deserializer, - property_type_registry: &#bevy_property_path::PropertyTypeRegistry) -> - Result, #bevy_property_path::erased_serde::Error> { - use #bevy_property_path::serde::de::DeserializeSeed; - let dynamic_properties_deserializer = #bevy_property_path::property_serde::DynamicPropertiesDeserializer::new(property_type_registry); - let dynamic_properties: #bevy_property_path::DynamicProperties = dynamic_properties_deserializer.deserialize(deserializer)?; - Ok(Box::new(dynamic_properties)) - } - } - - impl #impl_generics #bevy_property_path::Property 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_prop(&self) -> Box { - Box::new(self.to_dynamic()) - } - #[inline] - fn set(&mut self, value: &dyn #bevy_property_path::Property) { - // TODO: type check - self.apply(value); - } - - #[inline] - fn apply(&mut self, value: &dyn #bevy_property_path::Property) { - if let Some(properties) = value.as_properties() { - if properties.property_type() != self.property_type() { - panic!( - "Properties type mismatch. This type is {:?} but the applied type is {:?}", - self.property_type(), - properties.property_type() - ); - } - for (i, prop) in properties.iter_props().enumerate() { - let name = properties.prop_name(i).unwrap(); - self.prop_mut(name).map(|p| p.apply(prop)); - } - } else { - panic!("attempted to apply non-Properties type to Properties type"); - } - } - - #[inline] - fn as_properties(&self) -> Option<&dyn #bevy_property_path::Properties> { - Some(self) - } - - fn serializable<'a>(&'a self, registry: &'a #bevy_property_path::PropertyTypeRegistry) -> #bevy_property_path::property_serde::Serializable<'a> { - #bevy_property_path::property_serde::Serializable::Owned(Box::new(#bevy_property_path::property_serde::MapSerializer::new(self, registry))) - } - - fn property_type(&self) -> #bevy_property_path::PropertyType { - #bevy_property_path::PropertyType::Map - } - } - }) -} - -#[proc_macro_derive(Property)] -pub fn derive_property(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let modules = get_modules(); - let bevy_property_path = get_path(&modules.bevy_property); - - let generics = ast.generics; - let (impl_generics, ty_generics, _where_clause) = generics.split_for_impl(); - - let struct_name = &ast.ident; - - TokenStream::from(quote! { - impl #impl_generics #bevy_property_path::Property 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_prop(&self) -> Box { - Box::new(self.clone()) - } - - #[inline] - fn apply(&mut self, value: &dyn #bevy_property_path::Property) { - self.set(value); - } - - #[inline] - fn set(&mut self, value: &dyn #bevy_property_path::Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = prop.clone(); - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - #[inline] - fn serializable<'a>(&'a self, registry: &'a #bevy_property_path::PropertyTypeRegistry) -> #bevy_property_path::property_serde::Serializable<'a> { - #bevy_property_path::property_serde::Serializable::Owned(Box::new(#bevy_property_path::property_serde::PropertyValueSerializer::new(self, registry))) - } - - fn property_type(&self) -> #bevy_property_path::PropertyType { - #bevy_property_path::PropertyType::Value - } - } - - impl #impl_generics #bevy_property_path::DeserializeProperty for #struct_name#ty_generics { - fn deserialize( - deserializer: &mut dyn #bevy_property_path::erased_serde::Deserializer, - property_type_registry: &#bevy_property_path::PropertyTypeRegistry) -> - Result, #bevy_property_path::erased_serde::Error> { - let property = <#struct_name#ty_generics as #bevy_property_path::serde::Deserialize>::deserialize(deserializer)?; - Ok(Box::new(property)) - } - } - }) -} - -struct PropertyDef { - type_name: Ident, - generics: Generics, - serialize_fn: Option, - deserialize_fn: Option, -} - -impl Parse for PropertyDef { - 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 serialize_fn = None; - if lookahead.peek(Comma) { - input.parse::()?; - serialize_fn = Some(input.parse::()?); - lookahead = input.lookahead1(); - } - - let mut deserialize_fn = None; - if lookahead.peek(Comma) { - input.parse::()?; - deserialize_fn = Some(input.parse::()?); - } - - Ok(PropertyDef { - type_name: type_ident, - generics: Generics { - where_clause, - ..generics - }, - serialize_fn, - deserialize_fn, - }) - } -} - -#[proc_macro] -pub fn impl_property(input: TokenStream) -> TokenStream { - let property_def = parse_macro_input!(input as PropertyDef); - - let manifest = Manifest::new().unwrap(); - let crate_path = if let Some(package) = manifest.find(|name| name == "bevy") { - format!("{}::property", package.name) - } else if let Some(package) = manifest.find(|name| name == "bevy_property") { - package.name - } else { - "crate".to_string() - }; - let bevy_property_path = get_path(&crate_path); - - let (impl_generics, ty_generics, where_clause) = property_def.generics.split_for_impl(); - let ty = &property_def.type_name; - let serialize_fn = if let Some(serialize_fn) = property_def.serialize_fn { - quote! { #serialize_fn(self) } - } else { - quote! { - #bevy_property_path::property_serde::Serializable::Owned(Box::new(#bevy_property_path::property_serde::PropertyValueSerializer::new(self, registry))) - } - }; - let deserialize_fn = if let Some(deserialize_fn) = property_def.deserialize_fn { - quote! { #deserialize_fn(deserializer, property_type_registry) } - } else { - quote! { - let property = <#ty#ty_generics as #bevy_property_path::serde::Deserialize>::deserialize(deserializer)?; - Ok(Box::new(property)) - } - }; - - TokenStream::from(quote! { - impl #impl_generics #bevy_property_path::Property for #ty#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_prop(&self) -> Box { - Box::new(self.clone()) - } - - #[inline] - fn apply(&mut self, value: &dyn #bevy_property_path::Property) { - self.set(value); - } - - #[inline] - fn set(&mut self, value: &dyn #bevy_property_path::Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = prop.clone(); - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - #[inline] - fn serializable<'a>(&'a self, registry: &'a #bevy_property_path::PropertyTypeRegistry) -> #bevy_property_path::property_serde::Serializable<'a> { - #serialize_fn - } - - fn property_type(&self) -> #bevy_property_path::PropertyType { - #bevy_property_path::PropertyType::Value - } - } - - impl #impl_generics #bevy_property_path::DeserializeProperty for #ty#ty_generics #where_clause { - fn deserialize( - deserializer: &mut dyn #bevy_property_path::erased_serde::Deserializer, - property_type_registry: &#bevy_property_path::PropertyTypeRegistry) -> - Result, #bevy_property_path::erased_serde::Error> { - #deserialize_fn - } - } - }) -} diff --git a/crates/bevy_property/src/dynamic_properties.rs b/crates/bevy_property/src/dynamic_properties.rs deleted file mode 100644 index 6312c8ebdbac6..0000000000000 --- a/crates/bevy_property/src/dynamic_properties.rs +++ /dev/null @@ -1,223 +0,0 @@ -use crate::{ - property_serde::{DynamicPropertiesDeserializer, DynamicPropertiesSerializer, Serializable}, - DeserializeProperty, Properties, Property, PropertyIter, PropertyType, PropertyTypeRegistry, -}; -use bevy_utils::HashMap; -use serde::de::DeserializeSeed; -use std::{any::Any, borrow::Cow, fmt}; - -pub struct DynamicProperties { - pub type_name: String, - pub props: Vec>, - pub prop_names: Vec>, - pub prop_indices: HashMap, usize>, - pub property_type: PropertyType, -} - -impl fmt::Debug for DynamicProperties { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let props = self - .props - .iter() - .map(|p| p.as_ref() as *const dyn Property) - .collect::>(); - - f.debug_struct("DynamicProperties") - .field("type_name", &self.type_name) - .field("props", &props) - .field("prop_names", &self.prop_names) - .field("prop_indices", &self.prop_indices) - .field("property_type", &self.property_type) - .finish() - } -} - -impl DynamicProperties { - pub fn map() -> Self { - DynamicProperties { - type_name: std::any::type_name::().to_string(), - props: Default::default(), - prop_names: Default::default(), - prop_indices: Default::default(), - property_type: PropertyType::Map, - } - } - - pub fn seq() -> Self { - DynamicProperties { - type_name: std::any::type_name::().to_string(), - props: Default::default(), - prop_names: Default::default(), - prop_indices: Default::default(), - property_type: PropertyType::Seq, - } - } - - pub fn push(&mut self, prop: Box, name: Option<&str>) { - // TODO: validate map / seq operations - self.props.push(prop); - if let Some(name) = name { - let cow_name: Cow<'static, str> = Cow::Owned(name.to_string()); // moo - self.prop_names.push(cow_name.clone()); - self.prop_indices.insert(cow_name, self.props.len() - 1); - } - } - - pub fn set(&mut self, name: &str, prop: T) { - // TODO: validate map / seq operations - if let Some(index) = self.prop_indices.get(name) { - self.props[*index] = Box::new(prop); - } else { - self.push(Box::new(prop), Some(name)); - } - } - - pub fn set_box(&mut self, name: &str, prop: Box) { - // TODO: validate map / seq operations - if let Some(index) = self.prop_indices.get(name) { - self.props[*index] = prop; - } else { - self.push(prop, Some(name)); - } - } -} - -impl Properties for DynamicProperties { - #[inline] - fn prop(&self, name: &str) -> Option<&dyn Property> { - if let Some(index) = self.prop_indices.get(name) { - Some(&*self.props[*index]) - } else { - None - } - } - - #[inline] - fn prop_mut(&mut self, name: &str) -> Option<&mut dyn Property> { - if let Some(index) = self.prop_indices.get(name) { - Some(&mut *self.props[*index]) - } else { - None - } - } - - #[inline] - fn prop_with_index(&self, index: usize) -> Option<&dyn Property> { - self.props.get(index).map(|prop| &**prop) - } - - #[inline] - fn prop_with_index_mut(&mut self, index: usize) -> Option<&mut dyn Property> { - self.props.get_mut(index).map(|prop| &mut **prop) - } - - #[inline] - fn prop_name(&self, index: usize) -> Option<&str> { - match self.property_type { - PropertyType::Seq => None, - PropertyType::Map => self.prop_names.get(index).map(|name| name.as_ref()), - _ => panic!("DynamicProperties cannot be Value types"), - } - } - - #[inline] - fn prop_len(&self) -> usize { - self.props.len() - } - - fn iter_props(&self) -> PropertyIter { - PropertyIter { - props: self, - index: 0, - } - } -} - -impl Property for DynamicProperties { - #[inline] - fn type_name(&self) -> &str { - &self.type_name - } - - #[inline] - fn any(&self) -> &dyn Any { - self - } - - #[inline] - fn any_mut(&mut self) -> &mut dyn Any { - self - } - - #[inline] - fn clone_prop(&self) -> Box { - Box::new(self.to_dynamic()) - } - - #[inline] - fn set(&mut self, value: &dyn Property) { - if let Some(properties) = value.as_properties() { - *self = properties.to_dynamic(); - } else { - panic!("attempted to apply non-Properties type to Properties type"); - } - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - if let Some(properties) = value.as_properties() { - if properties.property_type() != self.property_type { - panic!( - "Properties type mismatch. This type is {:?} but the applied type is {:?}", - self.property_type, - properties.property_type() - ); - } - match self.property_type { - PropertyType::Map => { - for (i, prop) in properties.iter_props().enumerate() { - let name = properties.prop_name(i).unwrap(); - if let Some(p) = self.prop_mut(name) { - p.apply(prop); - } - } - } - PropertyType::Seq => { - for (i, prop) in properties.iter_props().enumerate() { - if let Some(p) = self.prop_with_index_mut(i) { - p.apply(prop); - } - } - } - _ => panic!("DynamicProperties cannot be Value types"), - } - } else { - panic!("attempted to apply non-Properties type to Properties type"); - } - } - - fn as_properties(&self) -> Option<&dyn Properties> { - Some(self) - } - - fn serializable<'a>(&'a self, registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Owned(Box::new(DynamicPropertiesSerializer::new(self, registry))) - } - - fn property_type(&self) -> PropertyType { - self.property_type - } -} - -impl DeserializeProperty for DynamicProperties { - fn deserialize( - deserializer: &mut dyn erased_serde::Deserializer, - property_type_registry: &PropertyTypeRegistry, - ) -> Result, erased_serde::Error> { - let dynamic_properties_deserializer = - DynamicPropertiesDeserializer::new(property_type_registry); - let dynamic_properties: DynamicProperties = - dynamic_properties_deserializer.deserialize(deserializer)?; - Ok(Box::new(dynamic_properties)) - } -} diff --git a/crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs b/crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs deleted file mode 100644 index 466fb1fd10bf0..0000000000000 --- a/crates/bevy_property/src/impl_property/impl_property_bevy_ecs.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::{impl_property, property_serde::Serializable, Property, PropertyTypeRegistry}; -use bevy_ecs::Entity; -use erased_serde::Deserializer; -use serde::Deserialize; - -impl_property!(Entity, serialize_entity, deserialize_entity); - -mod private { - use serde::{Deserialize, Serialize}; - #[derive(Serialize, Deserialize)] - pub(super) struct Entity(pub(super) u32); -} - -fn serialize_entity(entity: &Entity) -> Serializable { - Serializable::Owned(Box::new(private::Entity(entity.id()))) -} - -fn deserialize_entity( - deserializer: &mut dyn Deserializer, - _registry: &PropertyTypeRegistry, -) -> Result, erased_serde::Error> { - let entity = private::Entity::deserialize(deserializer)?; - Ok(Box::new(Entity::new(entity.0))) -} diff --git a/crates/bevy_property/src/impl_property/impl_property_glam.rs b/crates/bevy_property/src/impl_property/impl_property_glam.rs deleted file mode 100644 index e01ab9557ed17..0000000000000 --- a/crates/bevy_property/src/impl_property/impl_property_glam.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::impl_property; -use bevy_math::{Mat3, Mat4, Quat, Vec2, Vec3}; - -impl_property!(Vec2); -impl_property!(Vec3); -impl_property!(Mat3); -impl_property!(Mat4); -impl_property!(Quat); diff --git a/crates/bevy_property/src/impl_property/impl_property_smallvec.rs b/crates/bevy_property/src/impl_property/impl_property_smallvec.rs deleted file mode 100644 index caf78a6aa792a..0000000000000 --- a/crates/bevy_property/src/impl_property/impl_property_smallvec.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::{property_serde::Serializable, Property, PropertyType, PropertyTypeRegistry}; -use serde::Serialize; -use smallvec::{Array, SmallVec}; -use std::any::Any; - -impl Property for SmallVec -where - T: Clone + Send + Sync + Serialize + 'static + Array, - I: Send + Sync + Clone + Serialize + 'static, -{ - #[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_prop(&self) -> Box { - Box::new(self.clone()) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = prop.clone(); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } - - fn property_type(&self) -> PropertyType { - PropertyType::Value - } -} diff --git a/crates/bevy_property/src/impl_property/impl_property_std.rs b/crates/bevy_property/src/impl_property/impl_property_std.rs deleted file mode 100644 index 819fdd4c049cf..0000000000000 --- a/crates/bevy_property/src/impl_property/impl_property_std.rs +++ /dev/null @@ -1,861 +0,0 @@ -use crate::{ - impl_property, - property_serde::{SeqSerializer, Serializable}, - Properties, Property, PropertyIter, PropertyType, PropertyTypeRegistry, -}; -use serde::{Deserialize, Serialize}; -use std::{ - any::Any, - collections::{BTreeMap, HashMap, HashSet}, - hash::{BuildHasher, Hash}, - ops::Range, -}; - -impl Properties for Vec -where - T: Property + Clone + Default, -{ - fn prop(&self, _name: &str) -> Option<&dyn Property> { - None - } - - fn prop_mut(&mut self, _name: &str) -> Option<&mut dyn Property> { - None - } - - fn prop_with_index(&self, index: usize) -> Option<&dyn Property> { - Some(&self[index]) - } - - fn prop_with_index_mut(&mut self, index: usize) -> Option<&mut dyn Property> { - Some(&mut self[index]) - } - - fn prop_name(&self, _index: usize) -> Option<&str> { - None - } - - fn prop_len(&self) -> usize { - self.len() - } - - fn iter_props(&self) -> PropertyIter { - PropertyIter::new(self) - } -} - -impl Property for Vec -where - T: Property + Clone + Default, -{ - 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 clone_prop(&self) -> Box { - Box::new(self.clone()) - } - - fn set(&mut self, value: &dyn Property) { - if let Some(properties) = value.as_properties() { - let len = properties.prop_len(); - self.resize_with(len, T::default); - - if properties.property_type() != self.property_type() { - panic!( - "Properties type mismatch. This type is {:?} but the applied type is {:?}", - self.property_type(), - properties.property_type() - ); - } - for (i, prop) in properties.iter_props().enumerate() { - if let Some(p) = self.prop_with_index_mut(i) { - p.apply(prop) - } - } - } else { - panic!("attempted to apply non-Properties type to Properties type"); - } - } - - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn as_properties(&self) -> Option<&dyn Properties> { - Some(self) - } - - fn serializable<'a>(&'a self, registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Owned(Box::new(SeqSerializer::new(self, registry))) - } - - fn property_type(&self) -> PropertyType { - PropertyType::Seq - } -} - -// impl_property!(SEQUENCE, VecDeque where T: Clone + Send + Sync + Serialize + 'static); -impl_property!(Option where T: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static); -impl_property!(HashSet where T: Clone + Eq + Send + Sync + Hash + Serialize + for<'de> Deserialize<'de> + 'static, H: Clone + Send + Sync + Default + BuildHasher + 'static); -impl_property!(HashMap where - K: Clone + Eq + Send + Sync + Hash + Serialize + for<'de> Deserialize<'de> + 'static, - V: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static, - H: Clone + Send + Sync + Default + BuildHasher + 'static); -impl_property!(BTreeMap where - K: Clone + Ord + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static, - V: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static); -impl_property!(Range where T: Clone + Send + Sync + Serialize + for<'de> Deserialize<'de> + 'static); - -// TODO: Implement lossless primitive types in RON and remove all of these primitive "cast checks" -impl Property for String { - #[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_prop(&self) -> Box { - Box::new(self.clone()) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = prop.clone(); - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for bool { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for usize { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for u64 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for u32 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for u16 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for u8 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for isize { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for i64 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for i32 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for i16 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for i8 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for f32 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} - -impl Property for f64 { - #[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_prop(&self) -> Box { - Box::new(*self) - } - - #[inline] - fn apply(&mut self, value: &dyn Property) { - self.set(value); - } - - fn set(&mut self, value: &dyn Property) { - let value = value.any(); - if let Some(prop) = value.downcast_ref::() { - *self = *prop; - } else if let Some(prop) = value.downcast_ref::() { - *self = *prop as Self; - } else { - panic!("prop value is not {}", std::any::type_name::()); - } - } - - fn serializable<'a>(&'a self, _registry: &'a PropertyTypeRegistry) -> Serializable<'a> { - Serializable::Borrowed(self) - } -} diff --git a/crates/bevy_property/src/impl_property/mod.rs b/crates/bevy_property/src/impl_property/mod.rs deleted file mode 100644 index a81be0bf6b181..0000000000000 --- a/crates/bevy_property/src/impl_property/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod impl_property_bevy_ecs; -mod impl_property_glam; -mod impl_property_smallvec; -mod impl_property_std; diff --git a/crates/bevy_property/src/lib.rs b/crates/bevy_property/src/lib.rs deleted file mode 100644 index 87c2953e49c7a..0000000000000 --- a/crates/bevy_property/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod impl_property; -pub mod property_serde; -pub mod ron; - -mod dynamic_properties; -mod properties; -mod property; -mod type_registry; - -pub use dynamic_properties::*; -pub use properties::*; -pub use property::*; -pub use type_registry::*; - -pub use bevy_property_derive::*; -pub use erased_serde; -pub use serde; - -pub mod prelude { - pub use crate::{DynamicProperties, Properties, PropertiesVal, Property, PropertyVal}; -} diff --git a/crates/bevy_property/src/properties.rs b/crates/bevy_property/src/properties.rs deleted file mode 100644 index 66b08c973ebe2..0000000000000 --- a/crates/bevy_property/src/properties.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::{DynamicProperties, Property, PropertyType, PropertyVal}; - -pub trait Properties: Property { - fn prop(&self, name: &str) -> Option<&dyn Property>; - fn prop_mut(&mut self, name: &str) -> Option<&mut dyn Property>; - fn prop_with_index(&self, index: usize) -> Option<&dyn Property>; - fn prop_with_index_mut(&mut self, index: usize) -> Option<&mut dyn Property>; - fn prop_name(&self, index: usize) -> Option<&str>; - fn prop_len(&self) -> usize; - fn iter_props(&self) -> PropertyIter; - fn set_prop(&mut self, name: &str, value: &dyn Property) { - if let Some(prop) = self.prop_mut(name) { - prop.set(value); - } else { - panic!("prop does not exist: {}", name); - } - } - fn to_dynamic(&self) -> DynamicProperties { - let mut dynamic_props = match self.property_type() { - PropertyType::Map => { - let mut dynamic_props = DynamicProperties::map(); - for (i, prop) in self.iter_props().enumerate() { - let name = self - .prop_name(i) - .expect("All properties in maps should have a name"); - dynamic_props.set_box(name, prop.clone_prop()); - } - dynamic_props - } - PropertyType::Seq => { - let mut dynamic_props = DynamicProperties::seq(); - for prop in self.iter_props() { - dynamic_props.push(prop.clone_prop(), None); - } - dynamic_props - } - _ => panic!("Properties cannot be Value types"), - }; - - dynamic_props.type_name = self.type_name().to_string(); - dynamic_props - } -} - -pub struct PropertyIter<'a> { - pub(crate) props: &'a dyn Properties, - pub(crate) index: usize, -} - -impl<'a> PropertyIter<'a> { - pub fn new(props: &'a dyn Properties) -> Self { - PropertyIter { props, index: 0 } - } -} - -impl<'a> Iterator for PropertyIter<'a> { - type Item = &'a dyn Property; - - fn next(&mut self) -> Option { - if self.index < self.props.prop_len() { - let prop = self.props.prop_with_index(self.index).unwrap(); - self.index += 1; - Some(prop) - } else { - None - } - } -} - -pub trait PropertiesVal { - fn prop_val(&self, name: &str) -> Option<&T>; - fn set_prop_val(&mut self, name: &str, value: T); -} - -impl

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