diff --git a/.cargo/config_fast_builds b/.cargo/config_fast_builds index eae5a85ea428a..b92e77e95884a 100644 --- a/.cargo/config_fast_builds +++ b/.cargo/config_fast_builds @@ -4,7 +4,7 @@ # If you are using rust stable, remove the "-Zshare-generics=y" below. [target.x86_64-unknown-linux-gnu] -linker = "/usr/bin/clang" +linker = "clang" rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] # NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager: diff --git a/.gitattributes b/.gitattributes index dc3e2bcdd0e38..e030b9d97e2d0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,10 +4,11 @@ # Explicitly declare text files you want to always be normalized and converted # to native line endings on checkout. -*.rs text eof=lf -*.toml text eof=lf -*.frag text -*.vert text +*.rs text eol=lf +*.toml text eol=lf +*.frag text eol=lf +*.vert text eol=lf +*.wgsl text eol=lf # Declare files that will always have CRLF line endings on checkout. *.sln text eol=crlf diff --git a/.github/contributing/engine_style_guide.md b/.github/contributing/engine_style_guide.md index 8c42fe120aaae..b19ad9120f5b5 100644 --- a/.github/contributing/engine_style_guide.md +++ b/.github/contributing/engine_style_guide.md @@ -1,6 +1,10 @@ # Style guide: Engine -For more advice on contributing to the engine, see the [relevant section](../../CONTRIBUTING.md#Contributing-your-own-ideas) of CONTRIBUTING.md. +## Contributing + +For more advice on contributing to the engine, see the [relevant section](../../CONTRIBUTING.md#Contributing-your-own-ideas) of `CONTRIBUTING.md`. + +## General guidelines 1. Prefer granular imports over glob imports of `bevy::prelude::*` and `bevy::sub_crate::*`. 2. Use a consistent comment style: @@ -10,3 +14,25 @@ For more advice on contributing to the engine, see the [relevant section](../../ 4. Use \`variable_name\` code blocks in comments to signify that you're referring to specific types and variables. 5. Start comments with capital letters. End them with a period if they are sentence-like. 3. Use comments to organize long and complex stretches of code that can't sensibly be refactored into separate functions. + +## Rust API guidelines + +As a reference for our API development we are using the [Rust API guidelines][Rust API guidelines]. Generally, these should be followed, except for the following areas of disagreement: + +### Areas of disagreements + +Some areas mentioned in the [Rust API guidelines][Rust API guidelines] we do not agree with. These areas will be expanded whenever we find something else we do not agree with, so be sure to check these from time to time. + +> All items have a rustdoc example + +- This guideline is too strong and not applicable for everything inside of the Bevy game engine. For functionality that requires more context or needs a more interactive demonstration (such as rendering or input features), make use of the `examples` folder instead. + +> Examples use ?, not try!, not unwrap + +- This guideline is usually reasonable, but not always required. + +> Only smart pointers implement Deref and DerefMut + +- Generally a good rule of thumb, but we're probably going to deliberately violate this for single-element wrapper types like `Life(u32)`. The behavior is still predictable and it significantly improves ergonomics / new user comprehension. + +[Rust API guidelines]: https://rust-lang.github.io/api-guidelines/about.html diff --git a/.github/contributing/example_style_guide.md b/.github/contributing/example_style_guide.md index a0d1ae211da55..782d5550c73c7 100644 --- a/.github/contributing/example_style_guide.md +++ b/.github/contributing/example_style_guide.md @@ -11,8 +11,8 @@ For more advice on writing examples, see the [relevant section](../../CONTRIBUTI 1. Imports 2. A `fn main()` block 3. Example logic - 4. \[Optional\] Tests 5. Try to structure app / plugin construction in the same fashion as the actual code. +6. Examples should typically not have tests, as they are not directly reusable by the Bevy user. ## Stylistic preferences diff --git a/.github/example-run/breakout.ron b/.github/example-run/breakout.ron index 78c040831eab9..1d78f6a73ad80 100644 --- a/.github/example-run/breakout.ron +++ b/.github/example-run/breakout.ron @@ -1,3 +1,3 @@ ( - exit_after: Some(1800) + exit_after: Some(900) ) diff --git a/.github/example-run/contributors.ron b/.github/example-run/contributors.ron index 78c040831eab9..1d78f6a73ad80 100644 --- a/.github/example-run/contributors.ron +++ b/.github/example-run/contributors.ron @@ -1,3 +1,3 @@ ( - exit_after: Some(1800) + exit_after: Some(900) ) diff --git a/.github/example-run/scene.ron b/.github/example-run/scene.ron index 78c040831eab9..22e43495b5e42 100644 --- a/.github/example-run/scene.ron +++ b/.github/example-run/scene.ron @@ -1,3 +1,3 @@ ( - exit_after: Some(1800) + exit_after: Some(100) ) diff --git a/.github/linters/markdown-link-check.json b/.github/linters/markdown-link-check.json index 49378a36ce651..67bd0974f2efa 100644 --- a/.github/linters/markdown-link-check.json +++ b/.github/linters/markdown-link-check.json @@ -2,6 +2,9 @@ "ignorePatterns": [ { "pattern": "^https?://github\\.com/" + }, + { + "pattern": "^https?://reddit\\.com/" } ], "replacementPatterns": [], diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd06f47fab5fc..5b7e1949a5bc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,10 +65,10 @@ jobs: components: rustfmt, clippy override: true - name: Install alsa and udev - run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev - name: CI job # See tools/ci/src/main.rs for the commands this runs - run: cargo run -p ci + run: cargo run -p ci -- nonlocal check-benches: runs-on: ubuntu-latest @@ -125,6 +125,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable - uses: actions/cache@v2 with: path: | @@ -213,11 +216,17 @@ jobs: run-examples: runs-on: ubuntu-latest steps: - - name: Install dependencies + - name: Install Bevy dependencies run: | sudo apt-get update; DEBIAN_FRONTEND=noninteractive sudo apt-get install --no-install-recommends -yq \ - libasound2-dev libudev-dev wget unzip xvfb; + libasound2-dev libudev-dev; + - name: install xvfb, llvmpipe and lavapipe + run: | + sudo apt-get update -y -qq + sudo add-apt-repository ppa:oibaf/graphics-drivers -y + sudo apt-get update + sudo apt install -y xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - uses: actions/checkout@v2 - uses: actions/cache@v2 with: @@ -231,23 +240,23 @@ jobs: - uses: actions-rs/toolchain@v1 with: toolchain: stable - - name: Setup swiftshader - run: | - wget https://github.com/qarmin/gtk_library_store/releases/download/3.24.0/swiftshader.zip; - unzip swiftshader.zip; - curr="$(pwd)/libvk_swiftshader.so"; - sed -i "s|PATH_TO_CHANGE|$curr|" vk_swiftshader_icd.json; - name: Build bevy run: | - cargo build --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_wgpu,bevy_winit,render,png,hdr,x11,bevy_ci_testing" + cargo build --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_winit,render,png,hdr,x11,bevy_ci_testing,trace,trace_chrome" - name: Run examples run: | for example in .github/example-run/*.ron; do example_name=`basename $example .ron` echo "running $example_name - "`date` - time CI_TESTING_CONFIG=$example VK_ICD_FILENAMES=$(pwd)/vk_swiftshader_icd.json DRI_PRIME=0 xvfb-run cargo run --example $example_name --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_wgpu,bevy_winit,render,png,hdr,x11,bevy_ci_testing" + time CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_winit,render,png,hdr,x11,bevy_ci_testing,trace,trace_chrome" sleep 10 done + zip traces.zip trace*.json + - name: save traces + uses: actions/upload-artifact@v1 + with: + name: example-traces.zip + path: traces.zip check-doc: runs-on: ubuntu-latest @@ -255,13 +264,16 @@ jobs: if: always() steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable - name: Install alsa and udev - run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev if: runner.os == 'linux' - name: Installs cargo-deadlinks run: cargo install --force cargo-deadlinks - name: Build and check doc - run: RUSTDOCFLAGS='-D warnings' cargo doc --all-features --no-deps + run: RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features --no-deps --document-private-items - name: Checks dead links run: cargo deadlinks --dir target/doc/bevy continue-on-error: true diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 078c643d6e953..619d2115a10a9 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -23,6 +23,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true - name: Install cargo-deny run: cargo install cargo-deny - name: Check for security advisories and unmaintained crates @@ -32,6 +36,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true - name: Install cargo-deny run: cargo install cargo-deny - name: Check for banned and duplicated dependencies @@ -41,6 +49,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true - name: Install cargo-deny run: cargo install cargo-deny - name: Check for unauthorized licenses @@ -50,6 +62,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true - name: Install cargo-deny run: cargo install cargo-deny - name: Checked for unauthorized crate sources diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 42c15a8fe5d26..4c24217d5e57d 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -24,11 +24,6 @@ jobs: target key: ${{ runner.os }}-cargo-check-test-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} - - uses: actions-rs/install@v0.1 - with: - crate: cargo-lipo - version: latest - - name: Add iOS targets run: rustup target add aarch64-apple-ios x86_64-apple-ios diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f9575c4c263..9275603a37875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,659 @@ 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.5.0...main +[git_tag_comparison]: https://github.com/bevyengine/bevy/compare/v0.6.0...main + +## Version 0.6.0 (2022-01-08) + +### Added + +- [New Renderer][3175] +- [Clustered forward rendering][3153] +- [Frustum culling][2861] +- [Sprite Batching][3060] +- [Materials and MaterialPlugin][3428] +- [2D Meshes and Materials][3460] +- [WebGL2 support][3039] +- [Pipeline Specialization, Shader Assets, and Shader Preprocessing][3031] +- [Modular Rendering][2831] +- [Directional light and shadow][c6] +- [Directional light][2112] +- [Use the infinite reverse right-handed perspective projection][2543] +- [Implement and require `#[derive(Component)]` on all component structs][2254] +- [Shader Imports. Decouple Mesh logic from PBR][3137] +- [Add support for opaque, alpha mask, and alpha blend modes][3072] +- [bevy_gltf: Load light names from gltf][3553] +- [bevy_gltf: Add support for loading lights][3506] +- [Spherical Area Lights][1901] +- [Shader Processor: process imported shader][3290] +- [Add support for not casting/receiving shadows][2726] +- [Add support for configurable shadow map sizes][2700] +- [Implement the `Overflow::Hidden` style property for UI][3296] +- [SystemState][2283] +- [Add a method `iter_combinations` on query to iterate over combinations of query results][1763] +- [Add FromReflect trait to convert dynamic types to concrete types][1395] +- [More pipelined-rendering shader examples][3041] +- [Configurable wgpu features/limits priority][3452] +- [Cargo feature for bevy UI][3546] +- [Spherical area lights example][3498] +- [Implement ReflectValue serialization for Duration][3318] +- [bevy_ui: register Overflow type][3443] +- [Add Visibility component to UI][3426] +- [Implement non-indexed mesh rendering][3415] +- [add tracing spans for parallel executor and system overhead][3416] +- [RemoveChildren command][1925] +- [report shader processing errors in `RenderPipelineCache`][3289] +- [enable Webgl2 optimisation in pbr under feature][3291] +- [Implement Sub-App Labels][2695] +- [Added `set_cursor_icon(...)` to `Window`][3395] +- [Support topologies other than TriangleList][3349] +- [Add an example 'showcasing' using multiple windows][3367] +- [Add an example to draw a rectangle][2957] +- [Added set_scissor_rect to tracked render pass.][3320] +- [Add RenderWorld to Extract step][2555] +- [re-export ClearPassNode][3336] +- [add default standard material in PbrBundle][3325] +- [add methods to get reads and writes of `Access`][3166] +- [Add despawn_children][2903] +- [More Bevy ECS schedule spans][3281] +- [Added transparency to window builder][3105] +- [Add Gamepads resource][3257] +- [Add support for #else for shader defs][3206] +- [Implement iter() for mutable Queries][2305] +- [add shadows in examples][3201] +- [Added missing wgpu image render resources.][3171] +- [Per-light toggleable shadow mapping][3126] +- [Support nested shader defs][3113] +- [use bytemuck crate instead of Byteable trait][2183] +- [`iter_mut()` for Assets type][3118] +- [EntityRenderCommand and PhaseItemRenderCommand][3111] +- [add position to WindowDescriptor][3070] +- [Add System Command apply and RenderGraph node spans][3069] +- [Support for normal maps including from glTF models][2741] +- [MSAA example][3049] +- [Add MSAA to new renderer][3042] +- [Add support for IndexFormat::Uint16][2990] +- [Apply labels to wgpu resources for improved debugging/profiling][2912] +- [Add tracing spans around render subapp and stages][2907] +- [Add set_stencil_reference to TrackedRenderPass][2885] +- [Add despawn_recursive to EntityMut][2855] +- [Add trace_tracy feature for Tracy profiling][2832] +- [Expose wgpu's StencilOperation with bevy][2819] +- [add get_single variant][2793] +- [Add builder methods to Transform][2778] +- [add get_history function to Diagnostic][2772] +- [Add convenience methods for checking a set of inputs][2760] +- [Add error messages for the spooky insertions][2581] +- [Add Deref implementation for ComputePipeline][2759] +- [Derive thiserror::Error for HexColorError][2740] +- [Spawn specific entities: spawn or insert operations, refactor spawn internals, world clearing][2673] +- [Add ClearColor Resource to Pipelined Renderer][2631] +- [remove_component for ReflectComponent][2682] +- [Added ComputePipelineDescriptor][2628] +- [Added StorageTextureAccess to the exposed wgpu API][2614] +- [Add sprite atlases into the new renderer.][2560] +- [Log adapter info on initialization][2542] +- [Add feature flag to enable wasm for bevy_audio][2397] +- [Allow `Option>` and `Option>` as SystemParam][2345] +- [Added helpful adders for systemsets][2366] +- [Derive Clone for Time][2360] +- [Implement Clone for Fetches][2641] +- [Implement IntoSystemDescriptor for SystemDescriptor][2718] +- [implement DetectChanges for NonSendMut][2326] +- [Log errors when loading textures from a gltf file][2260] +- [expose texture/image conversions as From/TryFrom][2175] +- [[ecs] implement is_empty for queries][2271] +- [Add audio to ios example][1007] +- [Example showing how to use AsyncComputeTaskPool and Tasks][2180] +- [Expose set_changed() on ResMut and Mut][2208] +- [Impl AsRef+AsMut for Res, ResMut, and Mut][2189] +- [Add exit_on_esc_system to examples with window][2121] +- [Implement rotation for Text2d][2084] +- [Mesh vertex attributes for skinning and animation][1831] +- [load zeroed UVs as fallback in gltf loader][1803] +- [Implement direct mutable dereferencing][2100] +- [add a span for frames][2053] +- [Add an alias mouse position -> cursor position][2038] +- [Adding `WorldQuery` for `WithBundle`][2024] +- [Automatic System Spans][2033] +- [Add system sets and run criteria example][1909] +- [EnumVariantMeta derive][1972] +- [Added TryFrom for VertexAttributeValues][1963] +- [add render_to_texture example][1927] +- [Added example of entity sorting by components][1817] +- [calculate flat normals for mesh if missing][1808] +- [Add animate shaders example][1765] +- [examples on how to tests systems][1714] +- [Add a UV sphere implementation][1887] +- [Add additional vertex formats][1878] +- [gltf-loader: support data url for images][1828] +- [glTF: added color attribute support][1775] +- [Add synonyms for transform relative vectors][1667] + +### Changed + +- [Relicense Bevy under the dual MIT or Apache-2.0 license][2509] +- [[ecs] Improve `Commands` performance][2332] +- [Merge AppBuilder into App][2531] +- [Use a special first depth slice for clustered forward rendering][3545] +- [Add a separate ClearPass][3209] +- [bevy_pbr2: Improve lighting units and documentation][2704] +- [gltf loader: do not use the taskpool for only one task][3577] +- [System Param Lifetime Split][2605] +- [Optional `.system`][2398] +- [Optional `.system()`, part 2][2403] +- [Optional `.system()`, part 3][2422] +- [Optional `.system()`, part 4 (run criteria)][2431] +- [Optional `.system()`, part 6 (chaining)][2494] +- [Make the `iter_combinators` examples prettier][3075] +- [Remove dead anchor.rs code][3551] +- [gltf: load textures asynchronously using io task pool][1767] +- [Use fully-qualified type names in Label derive.][3544] +- [Remove Bytes, FromBytes, Labels, EntityLabels][3521] +- [StorageType parameter removed from ComponentDescriptor::new_resource][3495] +- [remove dead code: ShaderDefs derive][3490] +- [Enable Msaa for webgl by default][3489] +- [Renamed Entity::new to Entity::from_raw][3465] +- [bevy::scene::Entity renamed to bevy::scene::DynamicEntity.][3448] +- [make `sub_app` return an `&App` and add `sub_app_mut() -> &mut App`][3309] +- [use ogg by default instead of mp3][3421] +- [enable `wasm-bindgen` feature on gilrs][3420] +- [Use EventWriter for gilrs_system][3413] +- [Add some of the missing methods to `TrackedRenderPass`][3401] +- [Only bevy_render depends directly on wgpu][3393] +- [Update wgpu to 0.12 and naga to 0.8][3375] +- [Improved bevymark: no bouncing offscreen and spawn waves from CLI][3364] +- [Rename render UiSystem to RenderUiSystem][3371] +- [Use updated window size in bevymark example][3335] +- [Enable trace feature for subfeatures using it][3337] +- [Schedule gilrs system before input systems][2989] +- [Rename fixed timestep state and add a test][3260] +- [Port bevy_ui to pipelined-rendering][2653] +- [update wireframe rendering to new renderer][3193] +- [Allow `String` and `&String` as `Id` for `AssetServer.get_handle(id)`][3280] +- [Ported WgpuOptions to new renderer][3282] +- [Down with the system!][2496] +- [Update dependencies `ron` `winit`& fix `cargo-deny` lists][3244] +- [Improve contributors example quality][3258] +- [Expose command encoders][3271] +- [Made Time::time_since_startup return from last tick.][3264] +- [Default image used in PipelinedSpriteBundle to be able to render without loading a texture][3270] +- [make texture from sprite pipeline filterable][3236] +- [iOS: replace cargo-lipo, and update for new macOS][3109] +- [increase light intensity in pbr example][3182] +- [Faster gltf loader][3189] +- [Use crevice std140_size_static everywhere][3168] +- [replace matrix swizzles in pbr shader with index accesses][3122] +- [Disable default features from `bevy_asset` and `bevy_ecs`][3097] +- [Update tracing-subscriber requirement from 0.2.22 to 0.3.1][3076] +- [Update vendored Crevice to 0.8.0 + PR for arrays][3059] +- [change texture atlas sprite indexing to usize][2887] +- [Update derive(DynamicPlugin) to edition 2021][3038] +- [Update to edition 2021 on master][3028] +- [Add entity ID to expect() message][2943] +- [Use RenderQueue in BufferVec][2847] +- [removed unused RenderResourceId and SwapChainFrame][2890] +- [Unique WorldId][2827] +- [add_texture returns index to texture][2864] +- [Update hexasphere requirement from 4.0.0 to 5.0.0][2880] +- [enable change detection for hierarchy maintenance][2411] +- [Make events reuse buffers][2850] +- [Replace `.insert_resource(T::default())` calls with `init_resource::()`][2807] +- [Improve many sprites example][2785] +- [Update glam requirement from 0.17.3 to 0.18.0][2748] +- [update ndk-glue to 0.4][2684] +- [Remove Need for Sprite Size Sync System][2632] +- [Pipelined separate shadow vertex shader][2727] +- [Sub app label changes][2717] +- [Use Explicit Names for Flex Direction][2672] +- [Make default near plane more sensible at 0.1][2703] +- [Reduce visibility of various types and fields][2690] +- [Cleanup FromResources][2601] +- [Better error message for unsupported shader features Fixes #869][2598] +- [Change definition of `ScheduleRunnerPlugin`][2606] +- [Re-implement Automatic Sprite Sizing][2613] +- [Remove with bundle filter][2623] +- [Remove bevy_dynamic_plugin as a default][2578] +- [Port bevy_gltf to pipelined-rendering][2537] +- [Bump notify to 5.0.0-pre.11][2564] +- [Add 's (state) lifetime to `Fetch`][2515] +- [move bevy_core_pipeline to its own plugin][2552] +- [Refactor ECS to reduce the dependency on a 1-to-1 mapping between components and real rust types][2490] +- [Inline world get][2520] +- [Dedupe move logic in remove_bundle and remove_bundle_intersection][2521] +- [remove .system from pipelined code][2538] +- [Scale normal bias by texel size][c26] +- [Make Remove Command's fields public][2449] +- [bevy_utils: Re-introduce `with_capacity()`.][2393] +- [Update rodio requirement from 0.13 to 0.14][2244] +- [Optimize Events::extend and impl std::iter::Extend][2207] +- [Bump winit to 0.25][2186] +- [Improve legibility of RunOnce::run_unsafe param][2181] +- [Update gltf requirement from 0.15.2 to 0.16.0][2196] +- [Move to smallvec v1.6][2074] +- [Update rectangle-pack requirement from 0.3 to 0.4][2086] +- [Make Commands public?][2034] +- [Monomorphize various things][1914] +- [Detect camera projection changes][2015] +- [support assets of any size][1997] +- [Separate Query filter access from fetch access during initial evaluation][1977] +- [Provide better error message when missing a render backend][1965] +- [par_for_each: split batches when iterating on a sparse query][1945] +- [Allow deriving `SystemParam` on private types][1936] +- [Angle bracket annotated types to support generics][1919] +- [More detailed errors when resource not found][1864] +- [Moved events to ECS][1823] +- [Use a sorted Map for vertex buffer attributes][1796] +- [Error message improvements for shader compilation/gltf loading][1786] +- [Rename Light => PointLight and remove unused properties][1778] +- [Override size_hint for all Iterators and add ExactSizeIterator where applicable][1734] +- [Change breakout to use fixed timestamp][1541] + +### Fixed + +- [Fix shadows for non-TriangleLists][3581] +- [Fix error message for the `Component` macro's `component` `storage` attribute.][3534] +- [do not add plugin ExtractComponentPlugin twice for StandardMaterial][3502] +- [load spirv using correct API][3466] +- [fix shader compilation error reporting for non-wgsl shaders][3441] +- [bevy_ui: Check clip when handling interactions][3461] +- [crevice derive macro: fix path to render_resource when importing from bevy][3438] +- [fix parenting of scenes][2410] +- [Do not panic on failed setting of GameOver state in AlienCakeAddict][3411] +- [Fix minimization crash because of cluster updates.][3369] +- [Fix custom mesh pipelines][3381] +- [Fix hierarchy example panic][3378] +- [Fix double drop in BlobVec::replace_unchecked (#2597)][2848] +- [Remove vestigial derives][3343] +- [Fix crash with disabled winit][3330] +- [Fix clustering for orthographic projections][3316] +- [Run a clear pass on Windows without any Views][3304] +- [Remove some superfluous unsafe code][3297] +- [clearpass: also clear views without depth (2d)][3286] +- [Check for NaN in `Camera::world_to_screen()`][3268] +- [Fix sprite hot reloading in new renderer][3207] +- [Fix path used by macro not considering that we can use a sub-crate][3178] +- [Fix torus normals][3549] +- [enable alpha mode for textures materials that are transparent][3202] +- [fix calls to as_rgba_linear][3200] +- [Fix shadow logic][3186] +- [fix: as_rgba_linear used wrong variant][3192] +- [Fix MIME type support for glTF buffer Data URIs][3101] +- [Remove wasm audio feature flag for 2021][3000] +- [use correct size of pixel instead of 4][2977] +- [Fix custom_shader_pipelined example shader][2992] +- [Fix scale factor for cursor position][2932] +- [fix window resize after wgpu 0.11 upgrade][2953] +- [Fix unsound lifetime annotation on `Query::get_component`][2964] +- [Remove double Events::update in bevy-gilrs][2894] +- [Fix bevy_ecs::schedule::executor_parallel::system span management][2905] +- [Avoid some format! into immediate format!][2913] +- [Fix panic on is_resource_* calls (#2828)][2863] +- [Fix window size change panic][2858] +- [fix `Default` implementation of `Image` so that size and data match][2833] +- [Fix scale_factor_override in the winit backend][2784] +- [Fix breakout example scoreboard][2770] +- [Fix `Option>` and `Option>`][2757] +- [fix missing paths in ECS SystemParam derive macro v2][2550] +- [Add missing bytemuck feature][2625] +- [Update EntityMut's location in push_children() and insert_children()][2604] +- [Fixed issue with how texture arrays were uploaded with write_texture.][c24] +- [Don't update when suspended to avoid GPU use on iOS.][2482] +- [update archetypes for run criterias][2177] +- [Fix AssetServer::get_asset_loader deadlock][2395] +- [Fix unsetting RenderLayers bit in without fn][2409] +- [Fix view vector in pbr frag to work in ortho][2370] +- [Fixes Timer Precision Error Causing Panic][2362] +- [[assets] Fix `AssetServer::get_handle_path`][2310] +- [Fix bad bounds for NonSend SystemParams][2325] +- [Add minimum sizes to textures to prevent crash][2300] +- [[assets] set LoadState properly and more testing!][2226] +- [[assets] properly set `LoadState` with invalid asset extension][2318] +- [Fix Bevy crashing if no audio device is found][2269] +- [Fixes dropping empty BlobVec][2295] +- [[assets] fix Assets being set as 'changed' each frame][2280] +- [drop overwritten component data on double insert][2227] +- [Despawn with children doesn't need to remove entities from parents children when parents are also removed][2278] +- [reduce tricky unsafety and simplify table structure][2221] +- [Use bevy_reflect as path in case of no direct references][1875] +- [Fix Events:: bug][2206] +- [small ecs cleanup and remove_bundle drop bugfix][2172] +- [Fix PBR regression for unlit materials][2197] +- [prevent memory leak when dropping ParallelSystemContainer][2176] +- [fix diagnostic length for asset count][2165] +- [Fixes incorrect `PipelineCompiler::compile_pipeline()` step_mode][2126] +- [Asset re-loading while it's being deleted][2011] +- [Bevy derives handling generics in impl definitions.][2044] +- [Fix unsoundness in `Query::for_each_mut`][2045] +- [Fix mesh with no vertex attributes causing panic][2036] +- [Fix alien_cake_addict: cake should not be at height of player's location][1954] +- [fix memory size for PointLightBundle][1940] +- [Fix unsoundness in query component access][1929] +- [fixing compilation error on macos aarch64][1905] +- [Fix SystemParam handling of Commands][1899] +- [Fix IcoSphere UV coordinates][1871] +- [fix 'attempted to subtract with overflow' for State::inactives][1668] + +[1007]: https://github.com/bevyengine/bevy/pull/1007 +[1395]: https://github.com/bevyengine/bevy/pull/1395 +[1541]: https://github.com/bevyengine/bevy/pull/1541 +[1667]: https://github.com/bevyengine/bevy/pull/1667 +[1668]: https://github.com/bevyengine/bevy/pull/1668 +[1714]: https://github.com/bevyengine/bevy/pull/1714 +[1734]: https://github.com/bevyengine/bevy/pull/1734 +[1763]: https://github.com/bevyengine/bevy/pull/1763 +[1765]: https://github.com/bevyengine/bevy/pull/1765 +[1767]: https://github.com/bevyengine/bevy/pull/1767 +[1775]: https://github.com/bevyengine/bevy/pull/1775 +[1778]: https://github.com/bevyengine/bevy/pull/1778 +[1786]: https://github.com/bevyengine/bevy/pull/1786 +[1796]: https://github.com/bevyengine/bevy/pull/1796 +[1803]: https://github.com/bevyengine/bevy/pull/1803 +[1808]: https://github.com/bevyengine/bevy/pull/1808 +[1817]: https://github.com/bevyengine/bevy/pull/1817 +[1823]: https://github.com/bevyengine/bevy/pull/1823 +[1828]: https://github.com/bevyengine/bevy/pull/1828 +[1831]: https://github.com/bevyengine/bevy/pull/1831 +[1864]: https://github.com/bevyengine/bevy/pull/1864 +[1871]: https://github.com/bevyengine/bevy/pull/1871 +[1875]: https://github.com/bevyengine/bevy/pull/1875 +[1878]: https://github.com/bevyengine/bevy/pull/1878 +[1887]: https://github.com/bevyengine/bevy/pull/1887 +[1899]: https://github.com/bevyengine/bevy/pull/1899 +[1901]: https://github.com/bevyengine/bevy/pull/1901 +[1905]: https://github.com/bevyengine/bevy/pull/1905 +[1909]: https://github.com/bevyengine/bevy/pull/1909 +[1914]: https://github.com/bevyengine/bevy/pull/1914 +[1919]: https://github.com/bevyengine/bevy/pull/1919 +[1925]: https://github.com/bevyengine/bevy/pull/1925 +[1927]: https://github.com/bevyengine/bevy/pull/1927 +[1929]: https://github.com/bevyengine/bevy/pull/1929 +[1936]: https://github.com/bevyengine/bevy/pull/1936 +[1940]: https://github.com/bevyengine/bevy/pull/1940 +[1945]: https://github.com/bevyengine/bevy/pull/1945 +[1954]: https://github.com/bevyengine/bevy/pull/1954 +[1963]: https://github.com/bevyengine/bevy/pull/1963 +[1965]: https://github.com/bevyengine/bevy/pull/1965 +[1972]: https://github.com/bevyengine/bevy/pull/1972 +[1977]: https://github.com/bevyengine/bevy/pull/1977 +[1997]: https://github.com/bevyengine/bevy/pull/1997 +[2011]: https://github.com/bevyengine/bevy/pull/2011 +[2015]: https://github.com/bevyengine/bevy/pull/2015 +[2024]: https://github.com/bevyengine/bevy/pull/2024 +[2033]: https://github.com/bevyengine/bevy/pull/2033 +[2034]: https://github.com/bevyengine/bevy/pull/2034 +[2036]: https://github.com/bevyengine/bevy/pull/2036 +[2038]: https://github.com/bevyengine/bevy/pull/2038 +[2044]: https://github.com/bevyengine/bevy/pull/2044 +[2045]: https://github.com/bevyengine/bevy/pull/2045 +[2053]: https://github.com/bevyengine/bevy/pull/2053 +[2074]: https://github.com/bevyengine/bevy/pull/2074 +[2084]: https://github.com/bevyengine/bevy/pull/2084 +[2086]: https://github.com/bevyengine/bevy/pull/2086 +[2100]: https://github.com/bevyengine/bevy/pull/2100 +[2112]: https://github.com/bevyengine/bevy/pull/2112 +[2121]: https://github.com/bevyengine/bevy/pull/2121 +[2126]: https://github.com/bevyengine/bevy/pull/2126 +[2165]: https://github.com/bevyengine/bevy/pull/2165 +[2172]: https://github.com/bevyengine/bevy/pull/2172 +[2175]: https://github.com/bevyengine/bevy/pull/2175 +[2176]: https://github.com/bevyengine/bevy/pull/2176 +[2177]: https://github.com/bevyengine/bevy/pull/2177 +[2180]: https://github.com/bevyengine/bevy/pull/2180 +[2181]: https://github.com/bevyengine/bevy/pull/2181 +[2183]: https://github.com/bevyengine/bevy/pull/2183 +[2186]: https://github.com/bevyengine/bevy/pull/2186 +[2189]: https://github.com/bevyengine/bevy/pull/2189 +[2196]: https://github.com/bevyengine/bevy/pull/2196 +[2197]: https://github.com/bevyengine/bevy/pull/2197 +[2206]: https://github.com/bevyengine/bevy/pull/2206 +[2207]: https://github.com/bevyengine/bevy/pull/2207 +[2208]: https://github.com/bevyengine/bevy/pull/2208 +[2221]: https://github.com/bevyengine/bevy/pull/2221 +[2226]: https://github.com/bevyengine/bevy/pull/2226 +[2227]: https://github.com/bevyengine/bevy/pull/2227 +[2244]: https://github.com/bevyengine/bevy/pull/2244 +[2254]: https://github.com/bevyengine/bevy/pull/2254 +[2260]: https://github.com/bevyengine/bevy/pull/2260 +[2269]: https://github.com/bevyengine/bevy/pull/2269 +[2271]: https://github.com/bevyengine/bevy/pull/2271 +[2278]: https://github.com/bevyengine/bevy/pull/2278 +[2280]: https://github.com/bevyengine/bevy/pull/2280 +[2283]: https://github.com/bevyengine/bevy/pull/2283 +[2295]: https://github.com/bevyengine/bevy/pull/2295 +[2300]: https://github.com/bevyengine/bevy/pull/2300 +[2305]: https://github.com/bevyengine/bevy/pull/2305 +[2310]: https://github.com/bevyengine/bevy/pull/2310 +[2318]: https://github.com/bevyengine/bevy/pull/2318 +[2325]: https://github.com/bevyengine/bevy/pull/2325 +[2326]: https://github.com/bevyengine/bevy/pull/2326 +[2332]: https://github.com/bevyengine/bevy/pull/2332 +[2345]: https://github.com/bevyengine/bevy/pull/2345 +[2360]: https://github.com/bevyengine/bevy/pull/2360 +[2362]: https://github.com/bevyengine/bevy/pull/2362 +[2366]: https://github.com/bevyengine/bevy/pull/2366 +[2370]: https://github.com/bevyengine/bevy/pull/2370 +[2393]: https://github.com/bevyengine/bevy/pull/2393 +[2395]: https://github.com/bevyengine/bevy/pull/2395 +[2397]: https://github.com/bevyengine/bevy/pull/2397 +[2398]: https://github.com/bevyengine/bevy/pull/2398 +[2403]: https://github.com/bevyengine/bevy/pull/2403 +[2409]: https://github.com/bevyengine/bevy/pull/2409 +[2410]: https://github.com/bevyengine/bevy/pull/2410 +[2411]: https://github.com/bevyengine/bevy/pull/2411 +[2422]: https://github.com/bevyengine/bevy/pull/2422 +[2431]: https://github.com/bevyengine/bevy/pull/2431 +[2449]: https://github.com/bevyengine/bevy/pull/2449 +[2482]: https://github.com/bevyengine/bevy/pull/2482 +[2490]: https://github.com/bevyengine/bevy/pull/2490 +[2494]: https://github.com/bevyengine/bevy/pull/2494 +[2496]: https://github.com/bevyengine/bevy/pull/2496 +[2509]: https://github.com/bevyengine/bevy/pull/2509 +[2515]: https://github.com/bevyengine/bevy/pull/2515 +[2520]: https://github.com/bevyengine/bevy/pull/2520 +[2521]: https://github.com/bevyengine/bevy/pull/2521 +[2531]: https://github.com/bevyengine/bevy/pull/2531 +[2537]: https://github.com/bevyengine/bevy/pull/2537 +[2538]: https://github.com/bevyengine/bevy/pull/2538 +[2542]: https://github.com/bevyengine/bevy/pull/2542 +[2543]: https://github.com/bevyengine/bevy/pull/2543 +[2550]: https://github.com/bevyengine/bevy/pull/2550 +[2552]: https://github.com/bevyengine/bevy/pull/2552 +[2555]: https://github.com/bevyengine/bevy/pull/2555 +[2560]: https://github.com/bevyengine/bevy/pull/2560 +[2564]: https://github.com/bevyengine/bevy/pull/2564 +[2578]: https://github.com/bevyengine/bevy/pull/2578 +[2581]: https://github.com/bevyengine/bevy/pull/2581 +[2598]: https://github.com/bevyengine/bevy/pull/2598 +[2601]: https://github.com/bevyengine/bevy/pull/2601 +[2604]: https://github.com/bevyengine/bevy/pull/2604 +[2605]: https://github.com/bevyengine/bevy/pull/2605 +[2606]: https://github.com/bevyengine/bevy/pull/2606 +[2613]: https://github.com/bevyengine/bevy/pull/2613 +[2614]: https://github.com/bevyengine/bevy/pull/2614 +[2623]: https://github.com/bevyengine/bevy/pull/2623 +[2625]: https://github.com/bevyengine/bevy/pull/2625 +[2628]: https://github.com/bevyengine/bevy/pull/2628 +[2631]: https://github.com/bevyengine/bevy/pull/2631 +[2632]: https://github.com/bevyengine/bevy/pull/2632 +[2641]: https://github.com/bevyengine/bevy/pull/2641 +[2653]: https://github.com/bevyengine/bevy/pull/2653 +[2672]: https://github.com/bevyengine/bevy/pull/2672 +[2673]: https://github.com/bevyengine/bevy/pull/2673 +[2682]: https://github.com/bevyengine/bevy/pull/2682 +[2684]: https://github.com/bevyengine/bevy/pull/2684 +[2690]: https://github.com/bevyengine/bevy/pull/2690 +[2695]: https://github.com/bevyengine/bevy/pull/2695 +[2700]: https://github.com/bevyengine/bevy/pull/2700 +[2703]: https://github.com/bevyengine/bevy/pull/2703 +[2704]: https://github.com/bevyengine/bevy/pull/2704 +[2717]: https://github.com/bevyengine/bevy/pull/2717 +[2718]: https://github.com/bevyengine/bevy/pull/2718 +[2726]: https://github.com/bevyengine/bevy/pull/2726 +[2727]: https://github.com/bevyengine/bevy/pull/2727 +[2740]: https://github.com/bevyengine/bevy/pull/2740 +[2741]: https://github.com/bevyengine/bevy/pull/2741 +[2748]: https://github.com/bevyengine/bevy/pull/2748 +[2757]: https://github.com/bevyengine/bevy/pull/2757 +[2759]: https://github.com/bevyengine/bevy/pull/2759 +[2760]: https://github.com/bevyengine/bevy/pull/2760 +[2770]: https://github.com/bevyengine/bevy/pull/2770 +[2772]: https://github.com/bevyengine/bevy/pull/2772 +[2778]: https://github.com/bevyengine/bevy/pull/2778 +[2784]: https://github.com/bevyengine/bevy/pull/2784 +[2785]: https://github.com/bevyengine/bevy/pull/2785 +[2793]: https://github.com/bevyengine/bevy/pull/2793 +[2807]: https://github.com/bevyengine/bevy/pull/2807 +[2819]: https://github.com/bevyengine/bevy/pull/2819 +[2827]: https://github.com/bevyengine/bevy/pull/2827 +[2831]: https://github.com/bevyengine/bevy/pull/2831 +[2832]: https://github.com/bevyengine/bevy/pull/2832 +[2833]: https://github.com/bevyengine/bevy/pull/2833 +[2847]: https://github.com/bevyengine/bevy/pull/2847 +[2848]: https://github.com/bevyengine/bevy/pull/2848 +[2850]: https://github.com/bevyengine/bevy/pull/2850 +[2855]: https://github.com/bevyengine/bevy/pull/2855 +[2858]: https://github.com/bevyengine/bevy/pull/2858 +[2861]: https://github.com/bevyengine/bevy/pull/2861 +[2863]: https://github.com/bevyengine/bevy/pull/2863 +[2864]: https://github.com/bevyengine/bevy/pull/2864 +[2880]: https://github.com/bevyengine/bevy/pull/2880 +[2885]: https://github.com/bevyengine/bevy/pull/2885 +[2887]: https://github.com/bevyengine/bevy/pull/2887 +[2890]: https://github.com/bevyengine/bevy/pull/2890 +[2894]: https://github.com/bevyengine/bevy/pull/2894 +[2903]: https://github.com/bevyengine/bevy/pull/2903 +[2905]: https://github.com/bevyengine/bevy/pull/2905 +[2907]: https://github.com/bevyengine/bevy/pull/2907 +[2912]: https://github.com/bevyengine/bevy/pull/2912 +[2913]: https://github.com/bevyengine/bevy/pull/2913 +[2932]: https://github.com/bevyengine/bevy/pull/2932 +[2943]: https://github.com/bevyengine/bevy/pull/2943 +[2953]: https://github.com/bevyengine/bevy/pull/2953 +[2957]: https://github.com/bevyengine/bevy/pull/2957 +[2964]: https://github.com/bevyengine/bevy/pull/2964 +[2977]: https://github.com/bevyengine/bevy/pull/2977 +[2989]: https://github.com/bevyengine/bevy/pull/2989 +[2990]: https://github.com/bevyengine/bevy/pull/2990 +[2992]: https://github.com/bevyengine/bevy/pull/2992 +[3000]: https://github.com/bevyengine/bevy/pull/3000 +[3028]: https://github.com/bevyengine/bevy/pull/3028 +[3031]: https://github.com/bevyengine/bevy/pull/3031 +[3038]: https://github.com/bevyengine/bevy/pull/3038 +[3039]: https://github.com/bevyengine/bevy/pull/3039 +[3041]: https://github.com/bevyengine/bevy/pull/3041 +[3042]: https://github.com/bevyengine/bevy/pull/3042 +[3049]: https://github.com/bevyengine/bevy/pull/3049 +[3059]: https://github.com/bevyengine/bevy/pull/3059 +[3060]: https://github.com/bevyengine/bevy/pull/3060 +[3069]: https://github.com/bevyengine/bevy/pull/3069 +[3070]: https://github.com/bevyengine/bevy/pull/3070 +[3072]: https://github.com/bevyengine/bevy/pull/3072 +[3075]: https://github.com/bevyengine/bevy/pull/3075 +[3076]: https://github.com/bevyengine/bevy/pull/3076 +[3097]: https://github.com/bevyengine/bevy/pull/3097 +[3101]: https://github.com/bevyengine/bevy/pull/3101 +[3105]: https://github.com/bevyengine/bevy/pull/3105 +[3109]: https://github.com/bevyengine/bevy/pull/3109 +[3111]: https://github.com/bevyengine/bevy/pull/3111 +[3113]: https://github.com/bevyengine/bevy/pull/3113 +[3118]: https://github.com/bevyengine/bevy/pull/3118 +[3122]: https://github.com/bevyengine/bevy/pull/3122 +[3126]: https://github.com/bevyengine/bevy/pull/3126 +[3137]: https://github.com/bevyengine/bevy/pull/3137 +[3153]: https://github.com/bevyengine/bevy/pull/3153 +[3166]: https://github.com/bevyengine/bevy/pull/3166 +[3168]: https://github.com/bevyengine/bevy/pull/3168 +[3171]: https://github.com/bevyengine/bevy/pull/3171 +[3175]: https://github.com/bevyengine/bevy/pull/3175 +[3178]: https://github.com/bevyengine/bevy/pull/3178 +[3182]: https://github.com/bevyengine/bevy/pull/3182 +[3186]: https://github.com/bevyengine/bevy/pull/3186 +[3189]: https://github.com/bevyengine/bevy/pull/3189 +[3192]: https://github.com/bevyengine/bevy/pull/3192 +[3193]: https://github.com/bevyengine/bevy/pull/3193 +[3200]: https://github.com/bevyengine/bevy/pull/3200 +[3201]: https://github.com/bevyengine/bevy/pull/3201 +[3202]: https://github.com/bevyengine/bevy/pull/3202 +[3206]: https://github.com/bevyengine/bevy/pull/3206 +[3207]: https://github.com/bevyengine/bevy/pull/3207 +[3209]: https://github.com/bevyengine/bevy/pull/3209 +[3236]: https://github.com/bevyengine/bevy/pull/3236 +[3244]: https://github.com/bevyengine/bevy/pull/3244 +[3257]: https://github.com/bevyengine/bevy/pull/3257 +[3258]: https://github.com/bevyengine/bevy/pull/3258 +[3260]: https://github.com/bevyengine/bevy/pull/3260 +[3264]: https://github.com/bevyengine/bevy/pull/3264 +[3268]: https://github.com/bevyengine/bevy/pull/3268 +[3270]: https://github.com/bevyengine/bevy/pull/3270 +[3271]: https://github.com/bevyengine/bevy/pull/3271 +[3280]: https://github.com/bevyengine/bevy/pull/3280 +[3281]: https://github.com/bevyengine/bevy/pull/3281 +[3282]: https://github.com/bevyengine/bevy/pull/3282 +[3286]: https://github.com/bevyengine/bevy/pull/3286 +[3289]: https://github.com/bevyengine/bevy/pull/3289 +[3290]: https://github.com/bevyengine/bevy/pull/3290 +[3291]: https://github.com/bevyengine/bevy/pull/3291 +[3296]: https://github.com/bevyengine/bevy/pull/3296 +[3297]: https://github.com/bevyengine/bevy/pull/3297 +[3304]: https://github.com/bevyengine/bevy/pull/3304 +[3309]: https://github.com/bevyengine/bevy/pull/3309 +[3316]: https://github.com/bevyengine/bevy/pull/3316 +[3318]: https://github.com/bevyengine/bevy/pull/3318 +[3320]: https://github.com/bevyengine/bevy/pull/3320 +[3325]: https://github.com/bevyengine/bevy/pull/3325 +[3330]: https://github.com/bevyengine/bevy/pull/3330 +[3335]: https://github.com/bevyengine/bevy/pull/3335 +[3336]: https://github.com/bevyengine/bevy/pull/3336 +[3337]: https://github.com/bevyengine/bevy/pull/3337 +[3343]: https://github.com/bevyengine/bevy/pull/3343 +[3349]: https://github.com/bevyengine/bevy/pull/3349 +[3364]: https://github.com/bevyengine/bevy/pull/3364 +[3367]: https://github.com/bevyengine/bevy/pull/3367 +[3369]: https://github.com/bevyengine/bevy/pull/3369 +[3371]: https://github.com/bevyengine/bevy/pull/3371 +[3375]: https://github.com/bevyengine/bevy/pull/3375 +[3378]: https://github.com/bevyengine/bevy/pull/3378 +[3381]: https://github.com/bevyengine/bevy/pull/3381 +[3393]: https://github.com/bevyengine/bevy/pull/3393 +[3395]: https://github.com/bevyengine/bevy/pull/3395 +[3401]: https://github.com/bevyengine/bevy/pull/3401 +[3411]: https://github.com/bevyengine/bevy/pull/3411 +[3413]: https://github.com/bevyengine/bevy/pull/3413 +[3415]: https://github.com/bevyengine/bevy/pull/3415 +[3416]: https://github.com/bevyengine/bevy/pull/3416 +[3420]: https://github.com/bevyengine/bevy/pull/3420 +[3421]: https://github.com/bevyengine/bevy/pull/3421 +[3426]: https://github.com/bevyengine/bevy/pull/3426 +[3428]: https://github.com/bevyengine/bevy/pull/3428 +[3438]: https://github.com/bevyengine/bevy/pull/3438 +[3441]: https://github.com/bevyengine/bevy/pull/3441 +[3443]: https://github.com/bevyengine/bevy/pull/3443 +[3448]: https://github.com/bevyengine/bevy/pull/3448 +[3452]: https://github.com/bevyengine/bevy/pull/3452 +[3460]: https://github.com/bevyengine/bevy/pull/3460 +[3461]: https://github.com/bevyengine/bevy/pull/3461 +[3465]: https://github.com/bevyengine/bevy/pull/3465 +[3466]: https://github.com/bevyengine/bevy/pull/3466 +[3489]: https://github.com/bevyengine/bevy/pull/3489 +[3490]: https://github.com/bevyengine/bevy/pull/3490 +[3495]: https://github.com/bevyengine/bevy/pull/3495 +[3498]: https://github.com/bevyengine/bevy/pull/3498 +[3502]: https://github.com/bevyengine/bevy/pull/3502 +[3506]: https://github.com/bevyengine/bevy/pull/3506 +[3521]: https://github.com/bevyengine/bevy/pull/3521 +[3534]: https://github.com/bevyengine/bevy/pull/3534 +[3544]: https://github.com/bevyengine/bevy/pull/3544 +[3545]: https://github.com/bevyengine/bevy/pull/3545 +[3546]: https://github.com/bevyengine/bevy/pull/3546 +[3549]: https://github.com/bevyengine/bevy/pull/3549 +[3551]: https://github.com/bevyengine/bevy/pull/3551 +[3553]: https://github.com/bevyengine/bevy/pull/3553 +[3577]: https://github.com/bevyengine/bevy/pull/3577 +[3581]: https://github.com/bevyengine/bevy/pull/3581 +[c6]: https://github.com/cart/bevy/pull/6 +[c24]: https://github.com/cart/bevy/pull/24 +[c26]: https://github.com/cart/bevy/pull/26 ## Version 0.5.0 (2021-04-06) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1f4a66e5bbe3..e6c0e91dea1c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,7 +83,7 @@ Check out the next section for details on how this plays out. Some Bevy Org members are also [Triage Team](https://github.com/orgs/bevyengine/teams/triage-team) members. These people can label and close issues and PRs but do not have merge rights or any special authority within the community. Existing Bevy Engine Org members can [automatically request membership](https://github.com/orgs/bevyengine/teams/triage-team/members). Once again, if you are interested don't hesitate to apply. We generally accept membership requests. -We heavily limit who has merge rights within the org because this requires a large amount of trust when it comes to ethics, technical ability, and ability to enforce consistent project direction. Currently, only @cart can merge every class of change. @mockersf is allowed to merge small "uncontroversial" changes, provided they have two approvals. +We heavily limit who has merge rights within the org because this requires a large amount of trust when it comes to ethics, technical ability, and ability to enforce consistent project direction. Currently, only @cart can merge every class of change. @mockersf is allowed to merge small "uncontroversial" changes, provided these changes have at least two approvals. Same goes for @alice-i-cecile and doc related changes. If there is an emergency that needs a quick resolution and @cart is not around, both @mockersf and @alice-i-cecile are allowed to merge changes that resolve the emergency. ## How we work together @@ -252,7 +252,7 @@ To locally lint your files using the same workflow as our CI: 1. Install [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). 2. Run `markdownlint -f -c .github/linters/.markdown-lint.yml .` in the root directory of the Bevy project. 5. Push your changes to your fork on Github and open a Pull Request. -6. If you're account is new on github, one of the Bevy org members [will need to manually trigger CI for your PR](https://github.blog/changelog/2021-04-22-github-actions-maintainers-must-approve-first-time-contributor-workflow-runs/) using the `bors try` command. +6. If your account is new on github, one of the Bevy org members [will need to manually trigger CI for your PR](https://github.blog/changelog/2021-04-22-github-actions-maintainers-must-approve-first-time-contributor-workflow-runs/) using the `bors try` command. 7. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to @cart's final judgement. 8. When your PR is ready to merge, @cart will review it and suggest final changes. If those changes are minimal he may even apply them directly to speed up merging. diff --git a/CREDITS.md b/CREDITS.md index cefd08df93ad4..da6c38d6a8ba1 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -16,3 +16,8 @@ * Generic RPG Pack (CC0 license) by [Bakudas](https://twitter.com/bakudas) and [Gabe Fern](https://twitter.com/_Gabrielfer) * Environment maps (`.hdr` files) from [HDRIHaven](https://hdrihaven.com) (CC0 license) +* Alien from [Kenney's Space Kit](https://www.kenney.nl/assets/space-kit) (CC0 1.0 Universal) +* Cake from [Kenney's Food Kit](https://www.kenney.nl/assets/food-kit) (CC0 1.0 Universal) +* Ground tile from [Kenney's Tower Defense Kit](https://www.kenney.nl/assets/tower-defense-kit) (CC0 1.0 Universal) +* Game icons from [Kenney's Game Icons](https://www.kenney.nl/assets/game-icons) (CC0 1.0 Universal) +* Space ships from [Kenny's Simple Space Kit](https://www.kenney.nl/assets/simple-space) (CC0 1.0 Universal) diff --git a/Cargo.toml b/Cargo.toml index 7a0eec40f6455..ef6cb40a7da7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" categories = ["game-engines", "graphics", "gui", "rendering"] description = "A refreshingly simple data-driven game engine and app framework" exclude = ["assets/**/*", "tools/**/*", ".github/**/*", "crates/**/*"] @@ -12,29 +12,30 @@ readme = "README.md" repository = "https://github.com/bevyengine/bevy" [workspace] -exclude = ["benches"] -members = ["crates/*", "examples/ios", "tools/ci"] +exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"] +members = ["crates/*", "examples/ios", "tools/ci", "errors"] [features] default = [ "bevy_audio", "bevy_gilrs", - "bevy_gltf", - "bevy_wgpu", "bevy_winit", "render", "png", "hdr", - "mp3", + "vorbis", "x11", + "filesystem_watcher", ] # Force dynamic linking, which improves iterative compile times dynamic = ["bevy_dylib"] -# Rendering support (Also needs the bevy_wgpu feature or a third-party rendering backend) +# Rendering support render = [ + "bevy_internal/bevy_core_pipeline", "bevy_internal/bevy_pbr", + "bevy_internal/bevy_gltf", "bevy_internal/bevy_render", "bevy_internal/bevy_sprite", "bevy_internal/bevy_text", @@ -43,14 +44,20 @@ render = [ # Optional bevy crates bevy_audio = ["bevy_internal/bevy_audio"] +bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] bevy_gilrs = ["bevy_internal/bevy_gilrs"] bevy_gltf = ["bevy_internal/bevy_gltf"] -bevy_wgpu = ["bevy_internal/bevy_wgpu"] +bevy_pbr = ["bevy_internal/bevy_pbr"] +bevy_render = ["bevy_internal/bevy_render"] +bevy_sprite = ["bevy_internal/bevy_sprite"] +bevy_text = ["bevy_internal/bevy_text"] +bevy_ui = ["bevy_internal/bevy_ui"] bevy_winit = ["bevy_internal/bevy_winit"] -trace_chrome = ["bevy_internal/trace_chrome"] -trace_tracy = ["bevy_internal/trace_tracy"] +# Tracing features +trace_chrome = ["trace", "bevy_internal/trace_chrome"] +trace_tracy = ["trace", "bevy_internal/trace_tracy"] trace = ["bevy_internal/trace"] wgpu_trace = ["bevy_internal/wgpu_trace"] @@ -68,8 +75,8 @@ mp3 = ["bevy_internal/mp3"] vorbis = ["bevy_internal/vorbis"] wav = ["bevy_internal/wav"] -# WASM support for audio (Currently only works with flac, wav and vorbis. Not with mp3) -wasm_audio = ["bevy_internal/wasm_audio"] +# Enable watching file system for asset hot reload +filesystem_watcher = ["bevy_internal/filesystem_watcher"] serialize = ["bevy_internal/serialize"] @@ -77,23 +84,28 @@ serialize = ["bevy_internal/serialize"] wayland = ["bevy_internal/wayland"] x11 = ["bevy_internal/x11"] -# enable rendering of font glyphs using subpixel accuracy +# Enable rendering of font glyphs using subpixel accuracy subpixel_glyph_atlas = ["bevy_internal/subpixel_glyph_atlas"] -# enable systems that allow for automated testing on CI +# Enable systems that allow for automated testing on CI bevy_ci_testing = ["bevy_internal/bevy_ci_testing"] [dependencies] -bevy_dylib = { path = "crates/bevy_dylib", version = "0.5.0", default-features = false, optional = true } -bevy_internal = { path = "crates/bevy_internal", version = "0.5.0", default-features = false } +bevy_dylib = { path = "crates/bevy_dylib", version = "0.6.0", default-features = false, optional = true } +bevy_internal = { path = "crates/bevy_internal", version = "0.6.0", default-features = false } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +bevy_internal = { path = "crates/bevy_internal", version = "0.6.0", default-features = false, features = ["webgl"] } [dev-dependencies] anyhow = "1.0.4" rand = "0.8.0" -ron = "0.6.2" +ron = "0.7.0" serde = { version = "1", features = ["derive"] } +bytemuck = "1.7" # Needed to poll Task examples futures-lite = "1.11.3" +crossbeam-channel = "0.5.0" [[example]] name = "hello_world" @@ -104,14 +116,30 @@ path = "examples/hello_world.rs" name = "contributors" path = "examples/2d/contributors.rs" -[[example]] -name = "mesh" -path = "examples/2d/mesh.rs" - [[example]] name = "many_sprites" path = "examples/2d/many_sprites.rs" +[[example]] +name = "move_sprite" +path = "examples/2d/move_sprite.rs" + +[[example]] +name = "2d_rotation" +path = "examples/2d/rotation.rs" + +[[example]] +name = "mesh2d" +path = "examples/2d/mesh2d.rs" + +[[example]] +name = "mesh2d_manual" +path = "examples/2d/mesh2d_manual.rs" + +[[example]] +name = "rect" +path = "examples/2d/rect.rs" + [[example]] name = "sprite" path = "examples/2d/sprite.rs" @@ -137,10 +165,18 @@ path = "examples/2d/texture_atlas.rs" name = "3d_scene" path = "examples/3d/3d_scene.rs" +[[example]] +name = "lighting" +path = "examples/3d/lighting.rs" + [[example]] name = "load_gltf" path = "examples/3d/load_gltf.rs" +[[example]] +name = "many_cubes" +path = "examples/3d/many_cubes.rs" + [[example]] name = "msaa" path = "examples/3d/msaa.rs" @@ -158,12 +194,16 @@ name = "pbr" path = "examples/3d/pbr.rs" [[example]] -name = "render_to_texture" -path = "examples/3d/render_to_texture.rs" +name = "shadow_biases" +path = "examples/3d/shadow_biases.rs" + +[[example]] +name = "shadow_caster_receiver" +path = "examples/3d/shadow_caster_receiver.rs" [[example]] -name = "spawner" -path = "examples/3d/spawner.rs" +name = "spherical_area_lights" +path = "examples/3d/spherical_area_lights.rs" [[example]] name = "texture" @@ -177,10 +217,6 @@ path = "examples/3d/update_gltf_scene.rs" name = "wireframe" path = "examples/3d/wireframe.rs" -[[example]] -name = "z_sort_debug" -path = "examples/3d/z_sort_debug.rs" - # Application [[example]] name = "custom_loop" @@ -222,6 +258,14 @@ path = "examples/app/return_after_run.rs" name = "thread_pool_resources" path = "examples/app/thread_pool_resources.rs" +[[example]] +name = "headless_defaults" +path = "examples/app/headless_defaults.rs" + +[[example]] +name = "without_winit" +path = "examples/app/without_winit.rs" + # Assets [[example]] name = "asset_loading" @@ -244,6 +288,10 @@ path = "examples/asset/hot_asset_reloading.rs" name = "async_compute" path = "examples/async_tasks/async_compute.rs" +[[example]] +name = "external_source_external_thread" +path = "examples/async_tasks/external_source_external_thread.rs" + # Audio [[example]] name = "audio" @@ -275,6 +323,10 @@ path = "examples/ecs/event.rs" name = "fixed_timestep" path = "examples/ecs/fixed_timestep.rs" +[[example]] +name = "generic_system" +path = "examples/ecs/generic_system.rs" + [[example]] name = "hierarchy" path = "examples/ecs/hierarchy.rs" @@ -324,6 +376,10 @@ path = "examples/game/alien_cake_addict.rs" name = "breakout" path = "examples/game/breakout.rs" +[[example]] +name = "game_menu" +path = "examples/game/game_menu.rs" + # Input [[example]] name = "char_input_events" @@ -389,28 +445,28 @@ path = "examples/scene/scene.rs" # Shaders [[example]] -name = "animate_shader" -path = "examples/shader/animate_shader.rs" +name = "shader_defs" +path = "examples/shader/shader_defs.rs" [[example]] -name = "array_texture" -path = "examples/shader/array_texture.rs" +name = "shader_material" +path = "examples/shader/shader_material.rs" [[example]] -name = "hot_shader_reloading" -path = "examples/shader/hot_shader_reloading.rs" +name = "shader_material_glsl" +path = "examples/shader/shader_material_glsl.rs" [[example]] -name = "mesh_custom_attribute" -path = "examples/shader/mesh_custom_attribute.rs" +name = "shader_instancing" +path = "examples/shader/shader_instancing.rs" [[example]] -name = "shader_custom_material" -path = "examples/shader/shader_custom_material.rs" +name = "animate_shader" +path = "examples/shader/animate_shader.rs" [[example]] -name = "shader_defs" -path = "examples/shader/shader_defs.rs" +name = "compute_shader_game_of_life" +path = "examples/shader/compute_shader_game_of_life.rs" # Tools [[example]] @@ -452,29 +508,12 @@ name = "scale_factor_override" path = "examples/window/scale_factor_override.rs" [[example]] -name = "window_settings" -path = "examples/window/window_settings.rs" - -# WASM -[[example]] -name = "hello_wasm" -path = "examples/wasm/hello_wasm.rs" -required-features = [] +name = "transparent_window" +path = "examples/window/transparent_window.rs" [[example]] -name = "assets_wasm" -path = "examples/wasm/assets_wasm.rs" -required-features = ["bevy_winit"] - -[[example]] -name = "headless_wasm" -path = "examples/wasm/headless_wasm.rs" -required-features = [] - -[[example]] -name = "winit_wasm" -path = "examples/wasm/winit_wasm.rs" -required-features = ["bevy_winit"] +name = "window_settings" +path = "examples/window/window_settings.rs" # Android [[example]] diff --git a/README.md b/README.md index 734584d4eb147..4a9495b92be6b 100644 --- a/README.md +++ b/README.md @@ -27,24 +27,28 @@ Bevy is still in the _very_ early stages of development. APIs can and will chang ## About * **[Features](https://bevyengine.org):** A quick overview of Bevy's features. -* **[Roadmap](https://github.com/bevyengine/bevy/projects/1):** The Bevy team's development plan. -* **[Introducing Bevy](https://bevyengine.org/news/introducing-bevy/)**: A blog post covering some of Bevy's features +* **[News](https://bevyengine.org/news/)**: A development blog that covers our progress, plans and shiny new features. ## Docs * **[The Bevy Book](https://bevyengine.org/learn/book/introduction):** Bevy's official documentation. The best place to start learning Bevy. * **[Bevy Rust API Docs](https://docs.rs/bevy):** Bevy's Rust API docs, which are automatically generated from the doc comments in this repo. -* **[Community-Made Learning Resources](https://github.com/bevyengine/awesome-bevy#learning)**: Tutorials, documentation, and examples made by the Bevy community. +* **[Official Examples](https://github.com/bevyengine/bevy/tree/latest/examples):** Bevy's dedicated, runnable examples, which are great for digging into specific concepts. +* **[Community-Made Learning Resources](https://bevyengine.org/assets/#learning)**: More tutorials, documentation, and examples made by the Bevy community. ## Community -Before contributing or participating in discussions with the community, you should familiarize yourself with our **[Code of Conduct](./CODE_OF_CONDUCT.md)** and -**[How to Contribute](https://bevyengine.org/learn/book/contributing/code/)** +Before contributing or participating in discussions with the community, you should familiarize yourself with our [**Code of Conduct**](./CODE_OF_CONDUCT.md). * **[Discord](https://discord.gg/bevy):** Bevy's official discord server. * **[Reddit](https://reddit.com/r/bevy):** Bevy's official subreddit. -* **[Stack Overflow](https://stackoverflow.com/questions/tagged/bevy):** Questions tagged Bevy on Stack Overflow. -* **[Awesome Bevy](https://github.com/bevyengine/awesome-bevy):** A collection of awesome Bevy projects. +* **[GitHub Discussions](https://github.com/bevyengine/bevy/discussions):** The best place for questions about Bevy, answered right here! +* **[Bevy Assets](https://bevyengine.org/assets/):** A collection of awesome Bevy projects, tools, plugins and learning materials. + +If you'd like to help build Bevy, check out the **[Contributor's Guide](https://github.com/bevyengine/bevy/blob/main/CONTRIBUTING.md)**. +For simple problems, feel free to open an issue or PR and tackle it yourself! + +For more complex architecture decisions and experimental mad science, please open an [RFC](https://github.com/bevyengine/rfcs) (Request For Comments) so we can brainstorm together effectively! ## Getting Started @@ -64,16 +68,6 @@ cargo run --example breakout Bevy can be built just fine using default configuration on stable Rust. However for really fast iterative compiles, you should enable the "fast compiles" setup by [following the instructions here](http://bevyengine.org/learn/book/getting-started/setup/). -## Focus Areas - -Bevy has the following [Focus Areas](https://github.com/bevyengine/bevy/labels/focus-area). We are currently focusing our development efforts in these areas, and they will receive priority for Bevy developers' time. If you would like to contribute to Bevy, you are heavily encouraged to join in on these efforts: - -### [Editor-Ready UI](https://github.com/bevyengine/bevy/issues/254) - -### [PBR / Clustered Forward Rendering](https://github.com/bevyengine/bevy/issues/179) - -### [Scenes](https://github.com/bevyengine/bevy/issues/255) - ## Libraries Used Bevy is only possible because of the hard work put into these foundational technologies: diff --git a/assets/branding/bevy_bird_simpleicons.svg b/assets/branding/bevy_bird_simpleicons.svg new file mode 100644 index 0000000000000..25597d418ac69 --- /dev/null +++ b/assets/branding/bevy_bird_simpleicons.svg @@ -0,0 +1 @@ +Bevy Engine diff --git a/assets/models/AlienCake/README.md b/assets/models/AlienCake/README.md deleted file mode 100644 index 2cf86d3d8bd0d..0000000000000 --- a/assets/models/AlienCake/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Assets are from [Kenney](https://www.kenney.nl) - -- alien from [Space Kit](https://www.kenney.nl/assets/space-kit) -- cake from [Food Kit](https://www.kenney.nl/assets/food-kit) -- ground tile from [Tower Defense Kit](https://www.kenney.nl/assets/tower-defense-kit) diff --git a/assets/shaders/animate_shader.wgsl b/assets/shaders/animate_shader.wgsl new file mode 100644 index 0000000000000..fdc60da00b539 --- /dev/null +++ b/assets/shaders/animate_shader.wgsl @@ -0,0 +1,72 @@ +#import bevy_pbr::mesh_view_bind_group +#import bevy_pbr::mesh_struct + +[[group(1), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] uv: vec2; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let world_position = mesh.model * vec4(vertex.position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + out.uv = vertex.uv; + return out; +} + + +struct Time { + time_since_startup: f32; +}; +[[group(2), binding(0)]] +var time: Time; + + +fn oklab_to_linear_srgb(c: vec3) -> vec3 { + let L = c.x; + let a = c.y; + let b = c.z; + + let l_ = L + 0.3963377774 * a + 0.2158037573 * b; + let m_ = L - 0.1055613458 * a - 0.0638541728 * b; + let s_ = L - 0.0894841775 * a - 1.2914855480 * b; + + let l = l_*l_*l_; + let m = m_*m_*m_; + let s = s_*s_*s_; + + return vec3( + 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, + -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, + -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s, + ); +} + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { + let speed = 2.0; + let t_1 = sin(time.time_since_startup * speed) * 0.5 + 0.5; + let t_2 = cos(time.time_since_startup * speed); + + let distance_to_center = distance(in.uv, vec2(0.5)) * 1.4; + + // blending is done in a perceptual color space: https://bottosson.github.io/posts/oklab/ + let red = vec3(0.627955, 0.224863, 0.125846); + let green = vec3(0.86644, -0.233887, 0.179498); + let blue = vec3(0.701674, 0.274566, -0.169156); + let white = vec3(1.0, 0.0, 0.0); + let mixed = mix(mix(red, blue, t_1), mix(green, white, t_2), distance_to_center); + + return vec4(oklab_to_linear_srgb(mixed), 1.0); +} diff --git a/assets/shaders/custom_material.frag b/assets/shaders/custom_material.frag new file mode 100644 index 0000000000000..c18a67b4e3d1b --- /dev/null +++ b/assets/shaders/custom_material.frag @@ -0,0 +1,11 @@ +#version 450 + +layout(location = 0) out vec4 o_Target; + +layout(set = 1, binding = 0) uniform CustomMaterial { + vec4 Color; +}; + +void main() { + o_Target = Color; +} diff --git a/assets/shaders/custom_material.vert b/assets/shaders/custom_material.vert new file mode 100644 index 0000000000000..1dbf4a8829d89 --- /dev/null +++ b/assets/shaders/custom_material.vert @@ -0,0 +1,26 @@ +#version 450 + +layout(location = 0) in vec3 Vertex_Position; +layout(location = 1) in vec3 Vertex_Normal; +layout(location = 2) in vec2 Vertex_Uv; + +layout(set = 0, binding = 0) uniform CameraViewProj { + mat4 ViewProj; + mat4 InverseView; + mat4 Projection; + vec3 WorldPosition; + float near; + float far; + float width; + float height; +}; + +layout(set = 2, binding = 0) uniform Mesh { + mat4 Model; + mat4 InverseTransposeModel; + uint flags; +}; + +void main() { + gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0); +} diff --git a/assets/shaders/custom_material.wgsl b/assets/shaders/custom_material.wgsl new file mode 100644 index 0000000000000..4f40d3cff6936 --- /dev/null +++ b/assets/shaders/custom_material.wgsl @@ -0,0 +1,10 @@ +struct CustomMaterial { + color: vec4; +}; +[[group(1), binding(0)]] +var material: CustomMaterial; + +[[stage(fragment)]] +fn fragment() -> [[location(0)]] vec4 { + return material.color; +} diff --git a/assets/shaders/game_of_life.wgsl b/assets/shaders/game_of_life.wgsl new file mode 100644 index 0000000000000..6acb095fe78bc --- /dev/null +++ b/assets/shaders/game_of_life.wgsl @@ -0,0 +1,67 @@ +[[group(0), binding(0)]] +var texture: texture_storage_2d; + +fn hash(value: u32) -> u32 { + var state = value; + state = state ^ 2747636419u; + state = state * 2654435769u; + state = state ^ state >> 16u; + state = state * 2654435769u; + state = state ^ state >> 16u; + state = state * 2654435769u; + return state; +} +fn randomFloat(value: u32) -> f32 { + return f32(hash(value)) / 4294967295.0; +} + +[[stage(compute), workgroup_size(8, 8, 1)]] +fn init([[builtin(global_invocation_id)]] invocation_id: vec3, [[builtin(num_workgroups)]] num_workgroups: vec3) { + let location = vec2(i32(invocation_id.x), i32(invocation_id.y)); + let location_f32 = vec2(f32(invocation_id.x), f32(invocation_id.y)); + + let randomNumber = randomFloat(invocation_id.y * num_workgroups.x + invocation_id.x); + let alive = randomNumber > 0.9; + let color = vec4(f32(alive)); + + textureStore(texture, location, color); +} + + +fn get(location: vec2, offset_x: i32, offset_y: i32) -> i32 { + let value: vec4 = textureLoad(texture, location + vec2(offset_x, offset_y)); + return i32(value.x); +} + +fn count_alive(location: vec2) -> i32 { + return get(location, -1, -1) + + get(location, -1, 0) + + get(location, -1, 1) + + get(location, 0, -1) + + get(location, 0, 1) + + get(location, 1, -1) + + get(location, 1, 0) + + get(location, 1, 1); +} + +[[stage(compute), workgroup_size(8, 8, 1)]] +fn update([[builtin(global_invocation_id)]] invocation_id: vec3) { + let location = vec2(i32(invocation_id.x), i32(invocation_id.y)); + + let n_alive = count_alive(location); + let color = vec4(f32(n_alive) / 8.0); + + var alive: bool; + if (n_alive == 3) { + alive = true; + } else if (n_alive == 2) { + let currently_alive = get(location, 0, 0); + alive = bool(currently_alive); + } else { + alive = false; + } + + storageBarrier(); + + textureStore(texture, location, vec4(f32(alive))); +} \ No newline at end of file diff --git a/assets/shaders/hot.frag b/assets/shaders/hot.frag deleted file mode 100644 index 18c41c8cd5c04..0000000000000 --- a/assets/shaders/hot.frag +++ /dev/null @@ -1,11 +0,0 @@ -#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 deleted file mode 100644 index 1809b220dfd90..0000000000000 --- a/assets/shaders/hot.vert +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; - -layout(set = 0, binding = 0) uniform CameraViewProj { - 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/shaders/instancing.wgsl b/assets/shaders/instancing.wgsl new file mode 100644 index 0000000000000..262df7224b887 --- /dev/null +++ b/assets/shaders/instancing.wgsl @@ -0,0 +1,35 @@ +#import bevy_pbr::mesh_view_bind_group +#import bevy_pbr::mesh_struct + +[[group(1), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; + + [[location(3)]] i_pos_scale: vec4; + [[location(4)]] i_color: vec4; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; + [[location(0)]] color: vec4; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz; + let world_position = mesh.model * vec4(position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + out.color = vertex.i_color; + return out; +} + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { + return in.color; +} diff --git a/assets/shaders/shader_defs.wgsl b/assets/shaders/shader_defs.wgsl new file mode 100644 index 0000000000000..0d1c93d37e5ea --- /dev/null +++ b/assets/shaders/shader_defs.wgsl @@ -0,0 +1,33 @@ +#import bevy_pbr::mesh_view_bind_group +#import bevy_pbr::mesh_struct + +[[group(1), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; +}; + +struct VertexOutput { + [[builtin(position)]] clip_position: vec4; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let world_position = mesh.model * vec4(vertex.position, 1.0); + + var out: VertexOutput; + out.clip_position = view.view_proj * world_position; + return out; +} + +[[stage(fragment)]] +fn fragment() -> [[location(0)]] vec4 { + var color = vec4(0.0, 0.0, 1.0, 1.0); +# ifdef IS_RED + color = vec4(1.0, 0.0, 0.0, 1.0); +# endif + return color; +} diff --git a/assets/sounds/Windless Slopes.mp3 b/assets/sounds/Windless Slopes.mp3 deleted file mode 100644 index 8b8b76d7ab7e1..0000000000000 Binary files a/assets/sounds/Windless Slopes.mp3 and /dev/null differ diff --git a/assets/sounds/Windless Slopes.ogg b/assets/sounds/Windless Slopes.ogg new file mode 100644 index 0000000000000..87910863670e3 Binary files /dev/null and b/assets/sounds/Windless Slopes.ogg differ diff --git a/assets/textures/Game Icons/exitRight.png b/assets/textures/Game Icons/exitRight.png new file mode 100644 index 0000000000000..de78ab6f0f025 Binary files /dev/null and b/assets/textures/Game Icons/exitRight.png differ diff --git a/assets/textures/Game Icons/right.png b/assets/textures/Game Icons/right.png new file mode 100644 index 0000000000000..3f2480f9a5465 Binary files /dev/null and b/assets/textures/Game Icons/right.png differ diff --git a/assets/textures/Game Icons/wrench.png b/assets/textures/Game Icons/wrench.png new file mode 100644 index 0000000000000..440e7edc41af9 Binary files /dev/null and b/assets/textures/Game Icons/wrench.png differ diff --git a/assets/textures/simplespace/License.txt b/assets/textures/simplespace/License.txt new file mode 100644 index 0000000000000..bdba93fb4f136 --- /dev/null +++ b/assets/textures/simplespace/License.txt @@ -0,0 +1,22 @@ + + + Simple Space + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 03-03-2021 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/assets/textures/simplespace/enemy_A.png b/assets/textures/simplespace/enemy_A.png new file mode 100644 index 0000000000000..7db03b81a3320 Binary files /dev/null and b/assets/textures/simplespace/enemy_A.png differ diff --git a/assets/textures/simplespace/enemy_B.png b/assets/textures/simplespace/enemy_B.png new file mode 100644 index 0000000000000..fbc35f06bf6a6 Binary files /dev/null and b/assets/textures/simplespace/enemy_B.png differ diff --git a/assets/textures/simplespace/ship_C.png b/assets/textures/simplespace/ship_C.png new file mode 100644 index 0000000000000..4f5e493701a1a Binary files /dev/null and b/assets/textures/simplespace/ship_C.png differ diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 500667f668bde..bb3aab4c1c3e3 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -1,11 +1,14 @@ [package] name = "benches" version = "0.1.0" -edition = "2018" +edition = "2021" +description = "Benchmarks for Bevy engine" +publish = false +license = "MIT OR Apache-2.0" [dev-dependencies] criterion = "0.3" -bevy = { path = "../" } +bevy = { path = ".." } [[bench]] name = "system_stage" diff --git a/benches/benches/bevy_ecs/commands.rs b/benches/benches/bevy_ecs/commands.rs index 620b1915f9aa1..3d78a16decdf2 100644 --- a/benches/benches/bevy_ecs/commands.rs +++ b/benches/benches/bevy_ecs/commands.rs @@ -1,4 +1,5 @@ use bevy::ecs::{ + component::Component, entity::Entity, system::{Command, CommandQueue, Commands}, world::World, @@ -18,8 +19,11 @@ criterion_group!( ); criterion_main!(benches); +#[derive(Component)] struct A; +#[derive(Component)] struct B; +#[derive(Component)] struct C; fn empty_commands(criterion: &mut Criterion) { @@ -79,10 +83,10 @@ fn spawn_commands(criterion: &mut Criterion) { group.finish(); } -#[derive(Default)] +#[derive(Default, Component)] struct Matrix([[f32; 4]; 4]); -#[derive(Default)] +#[derive(Default, Component)] struct Vec3([f32; 3]); fn insert_commands(criterion: &mut Criterion) { @@ -95,14 +99,16 @@ fn insert_commands(criterion: &mut Criterion) { let mut world = World::default(); let mut command_queue = CommandQueue::default(); let mut entities = Vec::new(); - for i in 0..entity_count { + for _ in 0..entity_count { entities.push(world.spawn().id()); } bencher.iter(|| { let mut commands = Commands::new(&mut command_queue, &world); for entity in entities.iter() { - commands.entity(*entity).insert_bundle((Matrix::default(), Vec3::default())); + commands + .entity(*entity) + .insert_bundle((Matrix::default(), Vec3::default())); } drop(commands); command_queue.apply(&mut world); @@ -112,7 +118,7 @@ fn insert_commands(criterion: &mut Criterion) { let mut world = World::default(); let mut command_queue = CommandQueue::default(); let mut entities = Vec::new(); - for i in 0..entity_count { + for _ in 0..entity_count { entities.push(world.spawn().id()); } @@ -244,7 +250,7 @@ fn get_or_spawn(criterion: &mut Criterion) { let mut commands = Commands::new(&mut command_queue, &world); for i in 0..10_000 { commands - .get_or_spawn(Entity::new(i)) + .get_or_spawn(Entity::from_raw(i)) .insert_bundle((Matrix::default(), Vec3::default())); } command_queue.apply(&mut world); @@ -259,7 +265,7 @@ fn get_or_spawn(criterion: &mut Criterion) { let mut commands = Commands::new(&mut command_queue, &world); let mut values = Vec::with_capacity(10_000); for i in 0..10_000 { - values.push((Entity::new(i), (Matrix::default(), Vec3::default()))); + values.push((Entity::from_raw(i), (Matrix::default(), Vec3::default()))); } commands.insert_or_spawn_batch(values); command_queue.apply(&mut world); diff --git a/benches/benches/bevy_ecs/stages.rs b/benches/benches/bevy_ecs/stages.rs index e4fce66f0fea3..8e572acff131c 100644 --- a/benches/benches/bevy_ecs/stages.rs +++ b/benches/benches/bevy_ecs/stages.rs @@ -1,6 +1,7 @@ use bevy::ecs::{ + component::Component, schedule::{Stage, SystemStage}, - system::{IntoSystem, Query}, + system::Query, world::World, }; use criterion::{criterion_group, criterion_main, Criterion}; @@ -12,10 +13,15 @@ fn run_stage(stage: &mut SystemStage, world: &mut World) { stage.run(world); } +#[derive(Component)] struct A(f32); +#[derive(Component)] struct B(f32); +#[derive(Component)] struct C(f32); +#[derive(Component)] struct D(f32); +#[derive(Component)] struct E(f32); const ENTITY_BUNCH: usize = 5000; diff --git a/benches/benches/bevy_ecs/world_get.rs b/benches/benches/bevy_ecs/world_get.rs index 21aae5a8f40c2..7c43789e3716f 100644 --- a/benches/benches/bevy_ecs/world_get.rs +++ b/benches/benches/bevy_ecs/world_get.rs @@ -1,8 +1,4 @@ -use bevy::ecs::{ - component::{ComponentDescriptor, StorageType}, - entity::Entity, - world::World, -}; +use bevy::ecs::{component::Component, entity::Entity, world::World}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; criterion_group!( @@ -15,17 +11,19 @@ criterion_group!( ); criterion_main!(benches); -struct A(f32); +#[derive(Component, Default)] +#[component(storage = "Table")] +struct Table(f32); +#[derive(Component, Default)] +#[component(storage = "SparseSet")] +struct Sparse(f32); const RANGE: std::ops::Range = 5..6; -fn setup(entity_count: u32, storage: StorageType) -> World { +fn setup(entity_count: u32) -> World { let mut world = World::default(); - world - .register_component(ComponentDescriptor::new::(storage)) - .unwrap(); - world.spawn_batch((0..entity_count).map(|_| (A(0.0),))); - world + world.spawn_batch((0..entity_count).map(|_| (T::default(),))); + black_box(world) } fn world_entity(criterion: &mut Criterion) { @@ -35,11 +33,11 @@ fn world_entity(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{}_entities", entity_count), |bencher| { - let world = setup(entity_count, StorageType::Table); + let world = setup::(entity_count); bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::new(i); + let entity = Entity::from_raw(i); black_box(world.entity(entity)); } }); @@ -55,21 +53,26 @@ fn world_get(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let world = setup(entity_count, storage); - - bencher.iter(|| { - for i in 0..entity_count { - let entity = Entity::new(i); - assert!(world.get::(entity).is_some()); - } - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let world = setup::
(entity_count); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::from_raw(i); + assert!(world.get::
(entity).is_some()); + } + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let world = setup::(entity_count); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::from_raw(i); + assert!(world.get::(entity).is_some()); + } + }); + }); } group.finish(); @@ -81,22 +84,28 @@ fn world_query_get(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let mut world = setup(entity_count, storage); - let mut query = world.query::<&A>(); - - bencher.iter(|| { - for i in 0..entity_count { - let entity = Entity::new(i); - assert!(query.get(&world, entity).is_ok()); - } - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let mut world = setup::
(entity_count); + let mut query = world.query::<&Table>(); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::from_raw(i); + assert!(query.get(&world, entity).is_ok()); + } + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let mut world = setup::(entity_count); + let mut query = world.query::<&Sparse>(); + + bencher.iter(|| { + for i in 0..entity_count { + let entity = Entity::from_raw(i); + assert!(query.get(&world, entity).is_ok()); + } + }); + }); } group.finish(); @@ -108,24 +117,34 @@ fn world_query_iter(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let mut world = setup(entity_count, storage); - let mut query = world.query::<&A>(); - - bencher.iter(|| { - let mut count = 0; - for comp in query.iter(&world) { - black_box(comp); - count += 1; - } - assert_eq!(black_box(count), entity_count); - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let mut world = setup::
(entity_count); + let mut query = world.query::<&Table>(); + + bencher.iter(|| { + let mut count = 0; + for comp in query.iter(&world) { + black_box(comp); + count += 1; + black_box(count); + } + assert_eq!(black_box(count), entity_count); + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let mut world = setup::(entity_count); + let mut query = world.query::<&Sparse>(); + + bencher.iter(|| { + let mut count = 0; + for comp in query.iter(&world) { + black_box(comp); + count += 1; + black_box(count); + } + assert_eq!(black_box(count), entity_count); + }); + }); } group.finish(); @@ -137,24 +156,34 @@ fn world_query_for_each(criterion: &mut Criterion) { group.measurement_time(std::time::Duration::from_secs(4)); for entity_count in RANGE.map(|i| i * 10_000) { - for storage in [StorageType::Table, StorageType::SparseSet] { - group.bench_function( - format!("{}_entities_{:?}", entity_count, storage), - |bencher| { - let mut world = setup(entity_count, storage); - let mut query = world.query::<&A>(); - - bencher.iter(|| { - let mut count = 0; - query.for_each(&world, |comp| { - black_box(comp); - count += 1; - }); - assert_eq!(black_box(count), entity_count); - }); - }, - ); - } + group.bench_function(format!("{}_entities_table", entity_count), |bencher| { + let mut world = setup::
(entity_count); + let mut query = world.query::<&Table>(); + + bencher.iter(|| { + let mut count = 0; + query.for_each(&world, |comp| { + black_box(comp); + count += 1; + black_box(count); + }); + assert_eq!(black_box(count), entity_count); + }); + }); + group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| { + let mut world = setup::(entity_count); + let mut query = world.query::<&Sparse>(); + + bencher.iter(|| { + let mut count = 0; + query.for_each(&world, |comp| { + black_box(comp); + count += 1; + black_box(count); + }); + assert_eq!(black_box(count), entity_count); + }); + }); } group.finish(); diff --git a/benches/benches/bevy_tasks/iter.rs b/benches/benches/bevy_tasks/iter.rs index aafa4b6f5b3db..5f267dbc4b4e0 100644 --- a/benches/benches/bevy_tasks/iter.rs +++ b/benches/benches/bevy_tasks/iter.rs @@ -6,8 +6,6 @@ impl<'a, T> ParallelIterator> for ParChunks<'a, T> where T: 'a + Send + Sync, { - type Item = &'a T; - fn next_batch(&mut self) -> Option> { self.0.next().map(|s| s.iter()) } @@ -18,8 +16,6 @@ impl<'a, T> ParallelIterator> for ParChunksMut<'a, T> where T: 'a + Send + Sync, { - type Item = &'a mut T; - fn next_batch(&mut self) -> Option> { self.0.next().map(|s| s.iter_mut()) } diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000000..2ebc97cf45585 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["sRGB", "NaN", "iOS", "glTF", "GitHub", "WebGPU"] diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index 95a0bd6cf8217..8bd0ae0fda209 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_app" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Provides core App functionality for Bevy Engine" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -15,14 +15,14 @@ default = ["bevy_reflect"] [dependencies] # bevy -bevy_derive = { path = "../bevy_derive", version = "0.5.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.5.0" } +bevy_derive = { path = "../bevy_derive", version = "0.6.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.6.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.6.0", optional = true } +bevy_utils = { path = "../bevy_utils", version = "0.6.0" } # other serde = { version = "1.0", features = ["derive"], optional = true } -ron = { version = "0.6.2", optional = true } +ron = { version = "0.7.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -31,4 +31,4 @@ web-sys = { version = "0.3", features = [ "Window" ] } [dev-dependencies] # bevy -bevy_log = { path = "../bevy_log", version = "0.5.0" } +bevy_log = { path = "../bevy_log", version = "0.6.0" } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 41462689053bf..734ef502abd3a 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1,25 +1,31 @@ -use crate::{CoreStage, Events, Plugin, PluginGroup, PluginGroupBuilder, StartupStage}; +use crate::{ + CoreStage, Events, Plugin, PluginGroup, PluginGroupBuilder, StartupSchedule, StartupStage, +}; +pub use bevy_derive::AppLabel; use bevy_ecs::{ - component::{Component, ComponentDescriptor}, prelude::{FromWorld, IntoExclusiveSystem}, schedule::{ - IntoSystemDescriptor, RunOnce, Schedule, Stage, StageLabel, State, SystemSet, SystemStage, + IntoSystemDescriptor, RunOnce, Schedule, Stage, StageLabel, State, StateData, SystemSet, + SystemStage, }, + system::Resource, world::World, }; -use bevy_utils::tracing::debug; -use std::{fmt::Debug, hash::Hash}; +use bevy_utils::{tracing::debug, HashMap}; +use std::fmt::Debug; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; +bevy_utils::define_label!(AppLabel); #[allow(clippy::needless_doctest_main)] /// Containers of app logic and data /// /// Bundles together the necessary elements, like [`World`] and [`Schedule`], to create /// an ECS-based application. It also stores a pointer to a -/// [runner function](App::set_runner), which by default executes the App schedule -/// once. Apps are constructed with the builder pattern. +/// [runner function](Self::set_runner). The runner is responsible for managing the application's +/// event loop and applying the [`Schedule`] to the [`World`] to drive application logic. +/// Apps are constructed with the builder pattern. /// /// ## Example /// Here is a simple "Hello World" Bevy app: @@ -38,9 +44,25 @@ use bevy_utils::tracing::info_span; /// } /// ``` pub struct App { + /// The main ECS [`World`] of the [`App`]. + /// This stores and provides access to all the main data of the application. + /// The systems of the [`App`] will run using this [`World`]. + /// If additional separate [`World`]-[`Schedule`] pairs are needed, you can use [`sub_app`][App::add_sub_app]s. pub world: World, + /// The [runner function](Self::set_runner) is primarily responsible for managing + /// the application's event loop and advancing the [`Schedule`]. + /// Typically, it is not configured manually, but set by one of Bevy's built-in plugins. + /// See `bevy::winit::WinitPlugin` and [`ScheduleRunnerPlugin`](crate::schedule_runner::ScheduleRunnerPlugin). pub runner: Box, + /// A container of [`Stage`]s set to be run in a linear order. pub schedule: Schedule, + sub_apps: HashMap, SubApp>, +} + +/// Each [`SubApp`] has its own [`Schedule`] and [`World`], enabling a separation of concerns. +struct SubApp { + app: App, + runner: Box, } impl Default for App { @@ -63,20 +85,28 @@ impl Default for App { } impl App { + /// Creates a new [`App`] with some default structure to enable core engine features. + /// This is the preferred constructor for most use cases. pub fn new() -> App { App::default() } + /// Creates a new empty [`App`] with minimal default configuration. + /// + /// This constructor should be used if you wish to provide a custom schedule, exit handling, cleanup, etc. pub fn empty() -> App { Self { world: Default::default(), schedule: Default::default(), runner: Box::new(run_once), + sub_apps: HashMap::default(), } } /// Advances the execution of the [`Schedule`] by one cycle. /// + /// This method also updates sub apps. See [`add_sub_app`](Self::add_sub_app) for more details. + /// /// See [`Schedule::run_once`] for more details. pub fn update(&mut self) { #[cfg(feature = "trace")] @@ -84,6 +114,9 @@ impl App { #[cfg(feature = "trace")] let _bevy_frame_update_guard = bevy_frame_update_span.enter(); self.schedule.run(&mut self.world); + for sub_app in self.sub_apps.values_mut() { + (sub_app.runner)(&mut self.world, &mut sub_app.app); + } } /// Starts the application by calling the app's [runner function](Self::set_runner). @@ -176,7 +209,7 @@ impl App { /// ``` pub fn add_startup_stage(&mut self, label: impl StageLabel, stage: S) -> &mut Self { self.schedule - .stage(CoreStage::Startup, |schedule: &mut Schedule| { + .stage(StartupSchedule, |schedule: &mut Schedule| { schedule.add_stage(label, stage) }); self @@ -207,7 +240,7 @@ impl App { stage: S, ) -> &mut Self { self.schedule - .stage(CoreStage::Startup, |schedule: &mut Schedule| { + .stage(StartupSchedule, |schedule: &mut Schedule| { schedule.add_stage_after(target, label, stage) }); self @@ -238,7 +271,7 @@ impl App { stage: S, ) -> &mut Self { self.schedule - .stage(CoreStage::Startup, |schedule: &mut Schedule| { + .stage(StartupSchedule, |schedule: &mut Schedule| { schedule.add_stage_before(target, label, stage) }); self @@ -339,6 +372,10 @@ impl App { stage_label: impl StageLabel, system: impl IntoSystemDescriptor, ) -> &mut Self { + use std::any::TypeId; + if stage_label.type_id() == TypeId::of::() { + panic!("add systems to a startup stage using App::add_startup_system_to_stage"); + } self.schedule.add_system_to_stage(stage_label, system); self } @@ -369,6 +406,10 @@ impl App { stage_label: impl StageLabel, system_set: SystemSet, ) -> &mut Self { + use std::any::TypeId; + if stage_label.type_id() == TypeId::of::() { + panic!("add system sets to a startup stage using App::add_startup_system_set_to_stage"); + } self.schedule .add_system_set_to_stage(stage_label, system_set); self @@ -389,7 +430,7 @@ impl App { /// } /// /// App::new() - /// .add_startup_system(my_startup_system.system()); + /// .add_startup_system(my_startup_system); /// ``` pub fn add_startup_system( &mut self, @@ -444,7 +485,7 @@ impl App { system: impl IntoSystemDescriptor, ) -> &mut Self { self.schedule - .stage(CoreStage::Startup, |schedule: &mut Schedule| { + .stage(StartupSchedule, |schedule: &mut Schedule| { schedule.add_system_to_stage(stage_label, system) }); self @@ -480,32 +521,32 @@ impl App { system_set: SystemSet, ) -> &mut Self { self.schedule - .stage(CoreStage::Startup, |schedule: &mut Schedule| { + .stage(StartupSchedule, |schedule: &mut Schedule| { schedule.add_system_set_to_stage(stage_label, system_set) }); self } - /// Adds a new [State] with the given `initial` value. - /// This inserts a new `State` resource and adds a new "driver" to [CoreStage::Update]. + /// Adds a new [`State`] with the given `initial` value. + /// This inserts a new `State` resource and adds a new "driver" to [`CoreStage::Update`]. /// Each stage that uses `State` for system run criteria needs a driver. If you need to use - /// your state in a different stage, consider using [Self::add_state_to_stage] or manually - /// adding [State::get_driver] to additional stages you need it in. + /// your state in a different stage, consider using [`Self::add_state_to_stage`] or manually + /// adding [`State::get_driver`] to additional stages you need it in. pub fn add_state(&mut self, initial: T) -> &mut Self where - T: Component + Debug + Clone + Eq + Hash, + T: StateData, { self.add_state_to_stage(CoreStage::Update, initial) } - /// Adds a new [State] with the given `initial` value. + /// Adds a new [`State`] with the given `initial` value. /// This inserts a new `State` resource and adds a new "driver" to the given stage. /// Each stage that uses `State` for system run criteria needs a driver. If you need to use - /// your state in more than one stage, consider manually adding [State::get_driver] to the + /// your state in more than one stage, consider manually adding [`State::get_driver`] to the /// stages you need it in. pub fn add_state_to_stage(&mut self, stage: impl StageLabel, initial: T) -> &mut Self where - T: Component + Debug + Clone + Eq + Hash, + T: StateData, { self.insert_resource(State::new(initial)) .add_system_set_to_stage(stage, State::::get_driver()) @@ -549,7 +590,7 @@ impl App { pub fn add_default_stages(&mut self) -> &mut Self { self.add_stage(CoreStage::First, SystemStage::parallel()) .add_stage( - CoreStage::Startup, + StartupSchedule, Schedule::default() .with_run_criteria(RunOnce::default()) .with_stage(StartupStage::PreStartup, SystemStage::parallel()) @@ -582,7 +623,7 @@ impl App { /// ``` pub fn add_event(&mut self) -> &mut Self where - T: Component, + T: Resource, { self.init_resource::>() .add_system_to_stage(CoreStage::First, Events::::update_system) @@ -606,18 +647,15 @@ impl App { /// App::new() /// .insert_resource(MyCounter { counter: 0 }); /// ``` - pub fn insert_resource(&mut self, resource: T) -> &mut Self - where - T: Component, - { + pub fn insert_resource(&mut self, resource: R) -> &mut Self { self.world.insert_resource(resource); self } /// Inserts a non-send resource to the app /// - /// You usually want to use `insert_resource`, but there are some special cases when a resource must - /// be non-send. + /// You usually want to use `insert_resource`, + /// but there are some special cases when a resource cannot be sent across threads. /// /// ## Example /// ``` @@ -630,19 +668,18 @@ impl App { /// App::new() /// .insert_non_send_resource(MyCounter { counter: 0 }); /// ``` - pub fn insert_non_send_resource(&mut self, resource: T) -> &mut Self - where - T: 'static, - { - self.world.insert_non_send(resource); + pub fn insert_non_send_resource(&mut self, resource: R) -> &mut Self { + self.world.insert_non_send_resource(resource); self } - /// Initialize a resource in the current [`App`], if it does not exist yet + /// Initialize a resource with standard starting values by adding it to the [`World`] /// /// If the resource already exists, nothing happens. /// - /// Adds a resource that implements `Default` or [`FromWorld`] trait. + /// The resource must implement the [`FromWorld`] trait. + /// If the `Default` trait is implemented, the `FromWorld` trait will use + /// the `Default::default` method to initialize the resource. /// /// ## Example /// ``` @@ -663,32 +700,18 @@ impl App { /// App::new() /// .init_resource::(); /// ``` - pub fn init_resource(&mut self) -> &mut Self - where - R: FromWorld + Send + Sync + 'static, - { - // PERF: We could avoid double hashing here, since the `from_resources` call is guaranteed - // not to modify the map. However, we would need to be borrowing resources both - // mutably and immutably, so we would need to be extremely certain this is correct - if !self.world.contains_resource::() { - let resource = R::from_world(&mut self.world); - self.insert_resource(resource); - } + pub fn init_resource(&mut self) -> &mut Self { + self.world.init_resource::(); self } - /// Initialize a non-send resource in the current [`App`], if it does not exist yet. + /// Initialize a non-send resource with standard starting values by adding it to the [`World`] /// - /// Adds a resource that implements `Default` or [`FromWorld`] trait. - pub fn init_non_send_resource(&mut self) -> &mut Self - where - R: FromWorld + 'static, - { - // See perf comment in init_resource - if self.world.get_non_send_resource::().is_none() { - let resource = R::from_world(&mut self.world); - self.world.insert_non_send(resource); - } + /// The resource must implement the [`FromWorld`] trait. + /// If the `Default` trait is implemented, the `FromWorld` trait will use + /// the `Default::default` method to initialize the resource. + pub fn init_non_send_resource(&mut self) -> &mut Self { + self.world.init_non_send_resource::(); self } @@ -745,7 +768,7 @@ impl App { /// Adds a group of plugins /// /// Bevy plugins can be grouped into a set of plugins. Bevy provides - /// built-in PluginGroups that provide core engine functionality. + /// built-in `PluginGroups` that provide core engine functionality. /// /// The plugin groups available by default are `DefaultPlugins` and `MinimalPlugins`. /// @@ -810,18 +833,6 @@ impl App { self } - /// Registers a new component using the given [ComponentDescriptor]. - /// - /// Components do not need to be manually registered. This just provides a way to - /// override default configuration. Attempting to register a component with a type - /// that has already been used by [World] will result in an error. - /// - /// See [World::register_component] - pub fn register_component(&mut self, descriptor: ComponentDescriptor) -> &mut Self { - self.world.register_component(descriptor).unwrap(); - self - } - /// Adds the type `T` to the type registry resource. #[cfg(feature = "bevy_reflect")] pub fn register_type(&mut self) -> &mut Self { @@ -834,6 +845,61 @@ impl App { } self } + + /// Adds an `App` as a child of the current one. + /// + /// The provided function `f` is called by the [`update`](Self::update) method. The `World` + /// parameter represents the main app world, while the `App` parameter is just a mutable + /// reference to the sub app itself. + pub fn add_sub_app( + &mut self, + label: impl AppLabel, + app: App, + sub_app_runner: impl Fn(&mut World, &mut App) + 'static, + ) -> &mut Self { + self.sub_apps.insert( + Box::new(label), + SubApp { + app, + runner: Box::new(sub_app_runner), + }, + ); + self + } + + /// Retrieves a "sub app" stored inside this [App]. This will panic if the sub app does not exist. + pub fn sub_app_mut(&mut self, label: impl AppLabel) -> &mut App { + match self.get_sub_app_mut(label) { + Ok(app) => app, + Err(label) => panic!("Sub-App with label '{:?}' does not exist", label), + } + } + + /// Retrieves a "sub app" inside this [App] with the given label, if it exists. Otherwise returns + /// an [Err] containing the given label. + pub fn get_sub_app_mut(&mut self, label: impl AppLabel) -> Result<&mut App, impl AppLabel> { + self.sub_apps + .get_mut((&label) as &dyn AppLabel) + .map(|sub_app| &mut sub_app.app) + .ok_or(label) + } + + /// Retrieves a "sub app" stored inside this [App]. This will panic if the sub app does not exist. + pub fn sub_app(&self, label: impl AppLabel) -> &App { + match self.get_sub_app(label) { + Ok(app) => app, + Err(label) => panic!("Sub-App with label '{:?}' does not exist", label), + } + } + + /// Retrieves a "sub app" inside this [App] with the given label, if it exists. Otherwise returns + /// an [Err] containing the given label. + pub fn get_sub_app(&self, label: impl AppLabel) -> Result<&App, impl AppLabel> { + self.sub_apps + .get((&label) as &dyn AppLabel) + .map(|sub_app| &sub_app.app) + .ok_or(label) + } } fn run_once(mut app: App) { @@ -841,5 +907,5 @@ fn run_once(mut app: App) { } /// An event that indicates the app should exit. This will fully exit the app process. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct AppExit; diff --git a/crates/bevy_app/src/ci_testing.rs b/crates/bevy_app/src/ci_testing.rs index 5c8bc879604e7..a61ee728dddf7 100644 --- a/crates/bevy_app/src/ci_testing.rs +++ b/crates/bevy_app/src/ci_testing.rs @@ -22,16 +22,15 @@ fn ci_testing_exit_after( *current_frame += 1; } -pub(crate) fn setup_app(app_builder: &mut App) -> &mut App { +pub(crate) fn setup_app(app: &mut App) -> &mut App { let filename = std::env::var("CI_TESTING_CONFIG").unwrap_or_else(|_| "ci_testing_config.ron".to_string()); let config: CiTestingConfig = ron::from_str( &std::fs::read_to_string(filename).expect("error reading CI testing configuration file"), ) .expect("error deserializing CI testing configuration file"); - app_builder - .insert_resource(config) + app.insert_resource(config) .add_system(ci_testing_exit_after); - app_builder + app } diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index c54d79d90f6ec..2a8eb88c94bae 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -1,3 +1,4 @@ +#![warn(missing_docs)] //! This crate is about everything concerning the highest-level, application layer of a Bevy //! app. @@ -16,9 +17,12 @@ pub use plugin::*; pub use plugin_group::*; pub use schedule_runner::*; +#[allow(missing_docs)] pub mod prelude { #[doc(hidden)] - pub use crate::{app::App, CoreStage, DynamicPlugin, Plugin, PluginGroup, StartupStage}; + pub use crate::{ + app::App, CoreStage, DynamicPlugin, Plugin, PluginGroup, StartupSchedule, StartupStage, + }; } use bevy_ecs::schedule::StageLabel; @@ -28,11 +32,6 @@ use bevy_ecs::schedule::StageLabel; /// The relative stages are added by [`App::add_default_stages`]. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] pub enum CoreStage { - /// Runs only once at the beginning of the app. - /// - /// Consists of the sub-stages defined in [`StartupStage`]. Systems added here are - /// referred to as "startup systems". - Startup, /// Name of app stage that runs before all other app stages First, /// Name of app stage responsible for performing setup before an update. Runs before UPDATE. @@ -45,6 +44,15 @@ pub enum CoreStage { /// Name of app stage that runs after all other app stages Last, } + +/// The label for the Startup [`Schedule`](bevy_ecs::schedule::Schedule), +/// which runs once at the beginning of the app. +/// +/// When targeting a [`Stage`](bevy_ecs::schedule::Stage) inside this Schedule, +/// you need to use [`StartupStage`] instead. +#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] +pub struct StartupSchedule; + /// The names of the default App startup stages #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] pub enum StartupStage { diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index 38115af2ced49..c6876db0b3b60 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -3,13 +3,18 @@ use std::any::Any; /// A collection of Bevy App logic and configuration /// -/// Plugins configure an [App](crate::App). When an [App](crate::App) registers -/// a plugin, the plugin's [Plugin::build] function is run. +/// Plugins configure an [`App`](crate::App). When an [`App`](crate::App) registers +/// a plugin, the plugin's [`Plugin::build`] function is run. pub trait Plugin: Any + Send + Sync { + /// Configures the [`App`] to which this plugin is added. fn build(&self, app: &mut App); + /// Configures a name for the [`Plugin`]. Primarily for debugging. fn name(&self) -> &str { std::any::type_name::() } } +/// Type representing an unsafe function that returns a mutable pointer to a [`Plugin`]. +/// Used for dynamically loading plugins. See +/// `bevy_dynamic_plugin/src/loader.rs#dynamically_load_plugin` pub type CreatePlugin = unsafe fn() -> *mut dyn Plugin; diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index 6bb41483bba89..aecb3dd05acda 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -2,7 +2,9 @@ use crate::{App, Plugin}; use bevy_utils::{tracing::debug, HashMap}; use std::any::TypeId; +/// Combines multiple [`Plugin`]s into a single unit. pub trait PluginGroup { + /// Configures the [`Plugin`]s that are to be added. fn build(&mut self, group: &mut PluginGroupBuilder); } @@ -11,6 +13,9 @@ struct PluginEntry { enabled: bool, } +/// Facilitates the creation and configuration of a [`PluginGroup`]. +/// Provides a build ordering to ensure that [`Plugin`]s which produce/require a resource +/// are built before/after dependent/depending [`Plugin`]s. #[derive(Default)] pub struct PluginGroupBuilder { plugins: HashMap, @@ -18,6 +23,7 @@ pub struct PluginGroupBuilder { } impl PluginGroupBuilder { + /// Appends a [`Plugin`] to the [`PluginGroupBuilder`]. pub fn add(&mut self, plugin: T) -> &mut Self { self.order.push(TypeId::of::()); self.plugins.insert( @@ -30,6 +36,7 @@ impl PluginGroupBuilder { self } + /// Configures a [`Plugin`] to be built before another plugin. pub fn add_before(&mut self, plugin: T) -> &mut Self { let target_index = self .order @@ -54,6 +61,7 @@ impl PluginGroupBuilder { self } + /// Configures a [`Plugin`] to be built after another plugin. pub fn add_after(&mut self, plugin: T) -> &mut Self { let target_index = self .order @@ -78,6 +86,10 @@ impl PluginGroupBuilder { self } + /// Enables a [`Plugin`] + /// + /// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to + /// opt back in to a [`Plugin`] after [disabling](Self::disable) it. pub fn enable(&mut self) -> &mut Self { let mut plugin_entry = self .plugins @@ -87,6 +99,7 @@ impl PluginGroupBuilder { self } + /// Disables a [`Plugin`], preventing it from being added to the `App` with the rest of the [`PluginGroup`]. pub fn disable(&mut self) -> &mut Self { let mut plugin_entry = self .plugins @@ -96,6 +109,7 @@ impl PluginGroupBuilder { self } + /// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s. pub fn finish(self, app: &mut App) { for ty in self.order.iter() { if let Some(entry) = self.plugins.get(ty) { diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index c3a897d5eced1..3227826f70d06 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -11,10 +11,16 @@ use std::{cell::RefCell, rc::Rc}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::{prelude::*, JsCast}; -/// Determines the method used to run an [App]'s `Schedule` +/// Determines the method used to run an [App]'s [`Schedule`](bevy_ecs::schedule::Schedule). #[derive(Copy, Clone, Debug)] pub enum RunMode { - Loop { wait: Option }, + /// Indicates that the [`App`]'s schedule should run repeatedly. + Loop { + /// Minimum duration to wait after a schedule has completed before repeating. + /// A value of [`None`] will not wait. + wait: Option, + }, + /// Indicates that the [`App`]'s schedule should run only once. Once, } @@ -24,18 +30,22 @@ impl Default for RunMode { } } +/// Configuration information for [`ScheduleRunnerPlugin`]. #[derive(Copy, Clone, Default)] pub struct ScheduleRunnerSettings { + /// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly. pub run_mode: RunMode, } impl ScheduleRunnerSettings { + /// [`RunMode::Once`] pub fn run_once() -> Self { ScheduleRunnerSettings { run_mode: RunMode::Once, } } + /// [`RunMode::Loop`] pub fn run_loop(wait_duration: Duration) -> Self { ScheduleRunnerSettings { run_mode: RunMode::Loop { @@ -45,8 +55,8 @@ impl ScheduleRunnerSettings { } } -/// Configures an App to run its [Schedule](bevy_ecs::schedule::Schedule) according to a given -/// [RunMode] +/// Configures an `App` to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given +/// [`RunMode`] #[derive(Default)] pub struct ScheduleRunnerPlugin; diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 0c103c7610d6f..7ba0cece7b7ac 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_asset" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Provides asset functionality for Bevy Engine" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -9,18 +9,18 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -default = ["filesystem_watcher"] +default = [] filesystem_watcher = ["notify"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.5.0" } -bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.5.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } -bevy_log = { path = "../bevy_log", version = "0.5.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } -bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" } -bevy_utils = { path = "../bevy_utils", version = "0.5.0" } +bevy_app = { path = "../bevy_app", version = "0.6.0" } +bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.6.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.6.0" } +bevy_log = { path = "../bevy_log", version = "0.6.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.6.0", features = ["bevy"] } +bevy_tasks = { path = "../bevy_tasks", version = "0.6.0" } +bevy_utils = { path = "../bevy_utils", version = "0.6.0" } # other serde = { version = "1", features = ["derive"] } @@ -34,13 +34,14 @@ rand = "0.8.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = { version = "0.2" } -web-sys = { version = "0.3", features = ["Request", "Window", "Response"]} +web-sys = { version = "0.3", features = ["Request", "Window", "Response"] } wasm-bindgen-futures = "0.4" js-sys = "0.3" [target.'cfg(target_os = "android")'.dependencies] -ndk-glue = { version = "0.4" } +ndk-glue = { version = "0.5" } [dev-dependencies] futures-lite = "1.4.0" tempfile = "3.2.0" +bevy_core = { path = "../bevy_core", version = "0.6.0" } diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 4dcfee66afb7e..58e7bbef814c3 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -14,7 +14,7 @@ use parking_lot::{Mutex, RwLock}; use std::{collections::hash_map::Entry, path::Path, sync::Arc}; use thiserror::Error; -/// Errors that occur while loading assets with an AssetServer +/// Errors that occur while loading assets with an `AssetServer` #[derive(Error, Debug)] pub enum AssetServerError { #[error("asset folder path is not a directory: {0}")] @@ -115,6 +115,8 @@ impl AssetServer { loaders.push(Arc::new(loader)); } + /// Enable watching of the filesystem for changes, if support is available, starting from after + /// the point of calling this function. pub fn watch_for_changes(&self) -> Result<(), AssetServerError> { self.server.asset_io.watch_for_changes()?; Ok(()) @@ -213,9 +215,9 @@ impl AssetServer { load_state } - /// Loads an Asset at the provided relative path. + /// Queue an [`Asset`] at the provided relative path for asynchronous loading. /// - /// The absolute Path to the asset is "ROOT/ASSET_FOLDER_NAME/path". + /// The absolute Path to the asset is `"ROOT/ASSET_FOLDER_NAME/path"`. /// /// By default the ROOT is the directory of the Application, but this can be overridden by /// setting the `"CARGO_MANIFEST_DIR"` environment variable @@ -226,6 +228,10 @@ impl AssetServer { /// The name of the asset folder is set inside the /// [`AssetServerSettings`](crate::AssetServerSettings) resource. The default name is /// `"assets"`. + /// + /// The asset is loaded asynchronously, and will generally not be available by the time + /// this calls returns. Use [`AssetServer::get_load_state`] to determine when the asset is + /// effectively loaded and available in the [`Assets`] collection. #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn load<'a, T: Asset, P: Into>>(&self, path: P) -> Handle { self.load_untyped(path).typed() @@ -552,6 +558,7 @@ pub fn free_unused_assets_system(asset_server: Res) { mod test { use super::*; use crate::{loader::LoadedAsset, update_asset_storage_system}; + use bevy_app::App; use bevy_ecs::prelude::*; use bevy_reflect::TypeUuid; use bevy_utils::BoxedFuture; @@ -618,7 +625,7 @@ mod test { handle_to_path: Default::default(), asset_lifecycles: Default::default(), task_pool: Default::default(), - asset_io: Box::new(FileAssetIo::new(asset_path)), + asset_io: Box::new(FileAssetIo::new(asset_path, false)), }), } } @@ -765,21 +772,13 @@ mod test { asset_server.add_loader(FakePngLoader); let assets = asset_server.register_asset_type::(); - let mut world = World::new(); - world.insert_resource(assets); - world.insert_resource(asset_server); - - let mut tick = { - let mut free_unused_assets_system = free_unused_assets_system.system(); - free_unused_assets_system.initialize(&mut world); - let mut update_asset_storage_system = update_asset_storage_system::.system(); - update_asset_storage_system.initialize(&mut world); - - move |world: &mut World| { - free_unused_assets_system.run((), world); - update_asset_storage_system.run((), world); - } - }; + #[derive(SystemLabel, Clone, Hash, Debug, PartialEq, Eq)] + struct FreeUnusedAssets; + let mut app = App::new(); + app.insert_resource(assets); + app.insert_resource(asset_server); + app.add_system(free_unused_assets_system.label(FreeUnusedAssets)); + app.add_system(update_asset_storage_system::.after(FreeUnusedAssets)); fn load_asset(path: AssetPath, world: &World) -> HandleUntyped { let asset_server = world.get_resource::().unwrap(); @@ -807,37 +806,43 @@ mod test { // --- let path: AssetPath = "fake.png".into(); - assert_eq!(LoadState::NotLoaded, get_load_state(path.get_id(), &world)); + assert_eq!( + LoadState::NotLoaded, + get_load_state(path.get_id(), &app.world) + ); // load the asset - let handle = load_asset(path.clone(), &world); + let handle = load_asset(path.clone(), &app.world); let weak_handle = handle.clone_weak(); // asset is loading - assert_eq!(LoadState::Loading, get_load_state(&handle, &world)); + assert_eq!(LoadState::Loading, get_load_state(&handle, &app.world)); - tick(&mut world); + app.update(); // asset should exist and be loaded at this point - assert_eq!(LoadState::Loaded, get_load_state(&handle, &world)); - assert!(get_asset(&handle, &world).is_some()); + assert_eq!(LoadState::Loaded, get_load_state(&handle, &app.world)); + assert!(get_asset(&handle, &app.world).is_some()); // after dropping the handle, next call to `tick` will prepare the assets for removal. drop(handle); - tick(&mut world); - assert_eq!(LoadState::Loaded, get_load_state(&weak_handle, &world)); - assert!(get_asset(&weak_handle, &world).is_some()); + app.update(); + assert_eq!(LoadState::Loaded, get_load_state(&weak_handle, &app.world)); + assert!(get_asset(&weak_handle, &app.world).is_some()); // second call to tick will actually remove the asset. - tick(&mut world); - assert_eq!(LoadState::Unloaded, get_load_state(&weak_handle, &world)); - assert!(get_asset(&weak_handle, &world).is_none()); + app.update(); + assert_eq!( + LoadState::Unloaded, + get_load_state(&weak_handle, &app.world) + ); + assert!(get_asset(&weak_handle, &app.world).is_none()); // finally, reload the asset - let handle = load_asset(path.clone(), &world); - assert_eq!(LoadState::Loading, get_load_state(&handle, &world)); - tick(&mut world); - assert_eq!(LoadState::Loaded, get_load_state(&handle, &world)); - assert!(get_asset(&handle, &world).is_some()); + let handle = load_asset(path.clone(), &app.world); + assert_eq!(LoadState::Loading, get_load_state(&handle, &app.world)); + app.update(); + assert_eq!(LoadState::Loaded, get_load_state(&handle, &app.world)); + assert!(get_asset(&handle, &app.world).is_some()); } #[test] diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index d444cdee2d159..27d013588a067 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -9,6 +9,8 @@ use crossbeam_channel::Sender; use std::fmt::Debug; /// Events that happen on assets of type `T` +/// +/// Events sent via the [Assets] struct will always be sent with a _Weak_ handle pub enum AssetEvent { Created { handle: Handle }, Modified { handle: Handle }, @@ -44,6 +46,18 @@ impl Debug for AssetEvent { } /// Stores Assets of a given type and tracks changes to them. +/// +/// Each asset is mapped by a unique [`HandleId`], allowing any [`Handle`] with the same +/// [`HandleId`] to access it. These assets remain loaded for as long as a Strong handle to that +/// asset exists. +/// +/// To store a reference to an asset without forcing it to stay loaded, you can use a Weak handle. +/// To make a Weak handle a Strong one, use [`Assets::get_handle`] or pass the `Assets` collection +/// into the handle's [`make_strong`](Handle::make_strong) method. +/// +/// Remember, if there are no Strong handles for an asset (i.e. they have all been dropped), the +/// asset will unload. Make sure you always have a Strong handle when you want to keep an asset +/// loaded! #[derive(Debug)] pub struct Assets { assets: HashMap, @@ -60,6 +74,10 @@ impl Assets { } } + /// Adds an asset to the collection, returning a Strong handle to that asset. + /// + /// # Events + /// * [`AssetEvent::Created`] pub fn add(&mut self, asset: T) -> Handle { let id = HandleId::random::(); self.assets.insert(id, asset); @@ -69,6 +87,12 @@ impl Assets { self.get_handle(id) } + /// Add/modify the asset pointed to by the given handle. + /// + /// Unless there exists another Strong handle for this asset, it's advised to use the returned + /// Strong handle. Not doing so may result in the unexpected release of the asset. + /// + /// See [`set_untracked`](Assets::set_untracked) for more info. #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn set>(&mut self, handle: H, asset: T) -> Handle { let id: HandleId = handle.into(); @@ -76,6 +100,14 @@ impl Assets { self.get_handle(id) } + /// Add/modify the asset pointed to by the given handle. + /// + /// If an asset already exists with the given [`HandleId`], it will be modified. Otherwise the + /// new asset will be inserted. + /// + /// # Events + /// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle + /// * [`AssetEvent::Modified`]: Sent if the asset with given handle already existed pub fn set_untracked>(&mut self, handle: H, asset: T) { let id: HandleId = handle.into(); if self.assets.insert(id, asset).is_some() { @@ -89,14 +121,23 @@ impl Assets { } } + /// Get the asset for the given handle. + /// + /// This is the main method for accessing asset data from an [Assets] collection. If you need + /// mutable access to the asset, use [`get_mut`](Assets::get_mut). pub fn get>(&self, handle: H) -> Option<&T> { self.assets.get(&handle.into()) } + /// Checks if an asset exists for the given handle pub fn contains>(&self, handle: H) -> bool { self.assets.contains_key(&handle.into()) } + /// Get mutable access to the asset for the given handle. + /// + /// This is the main method for mutably accessing asset data from an [Assets] collection. If you + /// do not need mutable access to the asset, you may also use [get](Assets::get). pub fn get_mut>(&mut self, handle: H) -> Option<&mut T> { let id: HandleId = handle.into(); self.events.send(AssetEvent::Modified { @@ -105,10 +146,15 @@ impl Assets { self.assets.get_mut(&id) } + /// Gets a _Strong_ handle pointing to the same asset as the given one pub fn get_handle>(&self, handle: H) -> Handle { Handle::strong(handle.into(), self.ref_change_sender.clone()) } + /// Get mutable access to an asset for the given handle, inserting a new value if none exists. + /// + /// # Events + /// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle pub fn get_or_insert_with>( &mut self, handle: H, @@ -129,14 +175,32 @@ impl Assets { borrowed } + /// Get an iterator over all assets in the collection. pub fn iter(&self) -> impl Iterator { self.assets.iter().map(|(k, v)| (*k, v)) } + /// Get a mutable iterator over all assets in the collection. + pub fn iter_mut(&mut self) -> impl Iterator { + for id in self.assets.keys() { + self.events.send(AssetEvent::Modified { + handle: Handle::weak(*id), + }); + } + self.assets.iter_mut().map(|(k, v)| (*k, v)) + } + + /// Get an iterator over all [`HandleId`]'s in the collection. pub fn ids(&self) -> impl Iterator + '_ { self.assets.keys().cloned() } + /// Remove an asset for the given handle. + /// + /// The asset is returned if it existed in the collection, otherwise `None`. + /// + /// # Events + /// * [`AssetEvent::Removed`] pub fn remove>(&mut self, handle: H) -> Option { let id: HandleId = handle.into(); let asset = self.assets.remove(&id); @@ -181,10 +245,12 @@ impl Assets { } } + /// Gets the number of assets in the collection pub fn len(&self) -> usize { self.assets.len() } + /// Returns true if there are no stored assets pub fn is_empty(&self) -> bool { self.assets.is_empty() } @@ -204,10 +270,16 @@ pub trait AddAsset { } impl AddAsset for App { + /// Add an [`Asset`] to the [`App`]. + /// + /// Adding the same [`Asset`] again after it has been added does nothing. fn add_asset(&mut self) -> &mut Self where T: Asset, { + if self.world.contains_resource::>() { + return self; + } let assets = { let asset_server = self.world.get_resource::().unwrap(); asset_server.register_asset_type::() @@ -239,3 +311,26 @@ impl AddAsset for App { self } } + +#[cfg(test)] +mod tests { + use bevy_app::App; + + use crate::{AddAsset, Assets}; + + #[test] + fn asset_overwriting() { + #[derive(bevy_reflect::TypeUuid)] + #[uuid = "44115972-f31b-46e5-be5c-2b9aece6a52f"] + struct MyAsset; + let mut app = App::new(); + app.add_plugin(bevy_core::CorePlugin) + .add_plugin(crate::AssetPlugin); + app.add_asset::(); + let mut assets_before = app.world.get_resource_mut::>().unwrap(); + let handle = assets_before.add(MyAsset); + app.add_asset::(); // Ensure this doesn't overwrite the Asset + let assets_after = app.world.get_resource_mut::>().unwrap(); + assert!(assets_after.get(handle).is_some()) + } +} diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index ae2bb45c0837c..d5287a2f174d9 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -9,15 +9,26 @@ use crate::{ path::{AssetPath, AssetPathId}, Asset, Assets, }; -use bevy_ecs::reflect::ReflectComponent; -use bevy_reflect::{Reflect, ReflectDeserialize}; +use bevy_ecs::{component::Component, reflect::ReflectComponent}; +use bevy_reflect::{FromReflect, Reflect, 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, Reflect, + Debug, + Clone, + Copy, + Eq, + PartialEq, + Hash, + Ord, + PartialOrd, + Serialize, + Deserialize, + Reflect, + FromReflect, )] #[reflect_value(Serialize, Deserialize, PartialEq, Hash)] pub enum HandleId { @@ -58,12 +69,38 @@ impl HandleId { /// /// Handles contain a unique id that corresponds to a specific asset in the [Assets](crate::Assets) /// collection. -#[derive(Reflect)] +/// +/// # Accessing the Asset +/// +/// A handle is _not_ the asset itself, but should be seen as a pointer to the asset. Modifying a +/// handle's `id` only modifies which asset is being pointed to. To get the actual asset, try using +/// [`Assets::get`](crate::Assets::get) or [`Assets::get_mut`](crate::Assets::get_mut). +/// +/// # Strong and Weak +/// +/// A handle can be either "Strong" or "Weak". Simply put: Strong handles keep the asset loaded, +/// while Weak handles do not affect the loaded status of assets. This is due to a type of +/// _reference counting_. When the number of Strong handles that exist for any given asset reach +/// zero, the asset is dropped and becomes unloaded. In some cases, you might want a reference to an +/// asset but don't want to take the responsibility of keeping it loaded that comes with a Strong handle. +/// This is where a Weak handle can be very useful. +/// +/// For example, imagine you have a `Sprite` component and a `Collider` component. The `Collider` uses +/// the `Sprite`'s image size to check for collisions. It does so by keeping a Weak copy of the +/// `Sprite`'s Strong handle to the image asset. +/// +/// If the `Sprite` is removed, its Strong handle to the image is dropped with it. And since it was the +/// only Strong handle for that asset, the asset is unloaded. Our `Collider` component still has a Weak +/// handle to the unloaded asset, but it will not be able to retrieve the image data, resulting in +/// collisions no longer being detected for that entity. +/// +#[derive(Component, Reflect, FromReflect)] #[reflect(Component)] pub struct Handle where T: Asset, { + /// The ID of the asset as contained within its respective [Assets](crate::Assets) collection pub id: HandleId, #[reflect(ignore)] handle_type: HandleType, @@ -77,6 +114,13 @@ enum HandleType { Strong(Sender), } +// FIXME: This only is needed because `Handle`'s field `handle_type` is currently ignored for reflection +impl Default for HandleType { + fn default() -> Self { + Self::Weak + } +} + impl Debug for HandleType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -96,6 +140,7 @@ impl Handle { } } + #[inline] pub fn weak(id: HandleId) -> Self { Self { id, @@ -104,6 +149,7 @@ impl Handle { } } + /// Get a copy of this handle as a Weak handle pub fn as_weak(&self) -> Handle { Handle { id: self.id, @@ -120,6 +166,9 @@ impl Handle { matches!(self.handle_type, HandleType::Strong(_)) } + /// Makes this handle Strong if it wasn't already. + /// + /// This method requires the corresponding [Assets](crate::Assets) collection pub fn make_strong(&mut self, assets: &mut Assets) { if self.is_strong() { return; @@ -129,6 +178,7 @@ impl Handle { self.handle_type = HandleType::Strong(sender); } + #[inline] pub fn clone_weak(&self) -> Self { Handle::weak(self.id) } @@ -176,6 +226,18 @@ impl From<&str> for HandleId { } } +impl From<&String> for HandleId { + fn from(value: &String) -> Self { + AssetPathId::from(value).into() + } +} + +impl From for HandleId { + fn from(value: String) -> Self { + AssetPathId::from(&value).into() + } +} + impl From<&Handle> for HandleId { fn from(value: &Handle) -> Self { value.id @@ -234,6 +296,8 @@ impl Clone for Handle { /// /// This allows handles to be mingled in a cross asset context. For example, storing `Handle` and /// `Handle` in the same `HashSet`. +/// +/// To convert back to a typed handle, use the [typed](HandleUntyped::typed) method. #[derive(Debug)] pub struct HandleUntyped { pub id: HandleId, @@ -275,6 +339,9 @@ impl HandleUntyped { matches!(self.handle_type, HandleType::Strong(_)) } + /// Convert this handle into a typed [Handle]. + /// + /// The new handle will maintain the Strong or Weak status of the current handle. pub fn typed(mut self) -> Handle { if let HandleId::Id(type_uuid, _) = self.id { if T::TYPE_UUID != type_uuid { diff --git a/crates/bevy_asset/src/io/android_asset_io.rs b/crates/bevy_asset/src/io/android_asset_io.rs index 91aa3770e1d5a..4397dcae63cfd 100644 --- a/crates/bevy_asset/src/io/android_asset_io.rs +++ b/crates/bevy_asset/src/io/android_asset_io.rs @@ -42,6 +42,7 @@ impl AssetIo for AndroidAssetIo { } fn watch_for_changes(&self) -> Result<(), AssetIoError> { + bevy_log::warn!("Watching for changes is not supported on Android"); Ok(()) } diff --git a/crates/bevy_asset/src/io/file_asset_io.rs b/crates/bevy_asset/src/io/file_asset_io.rs index ee23a6c730c43..efe762b542b41 100644 --- a/crates/bevy_asset/src/io/file_asset_io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -1,15 +1,23 @@ -use crate::{filesystem_watcher::FilesystemWatcher, AssetIo, AssetIoError, AssetServer}; +#[cfg(feature = "filesystem_watcher")] +use crate::{filesystem_watcher::FilesystemWatcher, AssetServer}; +use crate::{AssetIo, AssetIoError}; use anyhow::Result; +#[cfg(feature = "filesystem_watcher")] use bevy_ecs::system::Res; -use bevy_utils::{BoxedFuture, HashSet}; +use bevy_utils::BoxedFuture; +#[cfg(feature = "filesystem_watcher")] +use bevy_utils::HashSet; +#[cfg(feature = "filesystem_watcher")] use crossbeam_channel::TryRecvError; use fs::File; -use io::Read; +#[cfg(feature = "filesystem_watcher")] use parking_lot::RwLock; +#[cfg(feature = "filesystem_watcher")] +use std::sync::Arc; use std::{ - env, fs, io, + env, fs, + io::Read, path::{Path, PathBuf}, - sync::Arc, }; pub struct FileAssetIo { @@ -19,11 +27,26 @@ pub struct FileAssetIo { } impl FileAssetIo { - pub fn new>(path: P) -> Self { - FileAssetIo { + pub fn new>(path: P, watch_for_changes: bool) -> Self { + let file_asset_io = FileAssetIo { + #[cfg(feature = "filesystem_watcher")] filesystem_watcher: Default::default(), root_path: Self::get_root_path().join(path.as_ref()), + }; + if watch_for_changes { + #[cfg(any( + not(feature = "filesystem_watcher"), + target_arch = "wasm32", + target_os = "android" + ))] + panic!( + "Watch for changes requires the filesystem_watcher feature and cannot be used on \ + wasm32 / android targets" + ); + #[cfg(feature = "filesystem_watcher")] + file_asset_io.watch_for_changes().unwrap(); } + file_asset_io } pub fn get_root_path() -> PathBuf { @@ -75,10 +98,10 @@ impl AssetIo for FileAssetIo { ))) } - fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> { + fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> { #[cfg(feature = "filesystem_watcher")] { - let path = self.root_path.join(path); + let path = self.root_path.join(_path); let mut watcher = self.filesystem_watcher.write(); if let Some(ref mut watcher) = *watcher { watcher @@ -95,6 +118,8 @@ impl AssetIo for FileAssetIo { { *self.filesystem_watcher.write() = Some(FilesystemWatcher::default()); } + #[cfg(not(feature = "filesystem_watcher"))] + bevy_log::warn!("Watching for changes is not supported when the `filesystem_watcher` feature is disabled"); Ok(()) } diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index a40b7b2d013b6..acff0ec013600 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -32,7 +32,7 @@ pub enum AssetIoError { PathWatchError(PathBuf), } -/// Handles load requests from an AssetServer +/// Handles load requests from an `AssetServer` pub trait AssetIo: Downcast + Send + Sync + 'static { fn load_path<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result, AssetIoError>>; fn read_directory( diff --git a/crates/bevy_asset/src/io/wasm_asset_io.rs b/crates/bevy_asset/src/io/wasm_asset_io.rs index 3e66981edcc1b..a4e260d6fee43 100644 --- a/crates/bevy_asset/src/io/wasm_asset_io.rs +++ b/crates/bevy_asset/src/io/wasm_asset_io.rs @@ -46,6 +46,7 @@ impl AssetIo for WasmAssetIo { } fn watch_for_changes(&self) -> Result<(), AssetIoError> { + bevy_log::warn!("Watching for changes is not supported in WASM"); Ok(()) } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 0067c266ce981..b62d7537f2c7f 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -44,12 +44,16 @@ pub struct AssetPlugin; pub struct AssetServerSettings { pub asset_folder: String, + /// Whether to watch for changes in asset files. Requires the `filesystem_watcher` feature, + /// and cannot be supported on the wasm32 arch nor android os. + pub watch_for_changes: bool, } impl Default for AssetServerSettings { fn default() -> Self { Self { asset_folder: "assets".to_string(), + watch_for_changes: false, } } } @@ -64,7 +68,7 @@ pub fn create_platform_default_asset_io(app: &mut App) -> Box { .get_resource_or_insert_with(AssetServerSettings::default); #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] - let source = FileAssetIo::new(&settings.asset_folder); + let source = FileAssetIo::new(&settings.asset_folder, settings.watch_for_changes); #[cfg(target_arch = "wasm32")] let source = WasmAssetIo::new(&settings.asset_folder); #[cfg(target_os = "android")] diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 0ca85fa74738e..7b665bf8befe1 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -3,10 +3,7 @@ use crate::{ RefChangeChannel, }; use anyhow::Result; -use bevy_ecs::{ - component::Component, - system::{Res, ResMut}, -}; +use bevy_ecs::system::{Res, ResMut}; use bevy_reflect::{TypeUuid, TypeUuidDynamic}; use bevy_tasks::TaskPool; use bevy_utils::{BoxedFuture, HashMap}; @@ -46,13 +43,19 @@ impl LoadedAsset { } } - pub fn with_dependency(mut self, asset_path: AssetPath) -> Self { + pub fn add_dependency(&mut self, asset_path: AssetPath) { self.dependencies.push(asset_path.to_owned()); + } + + pub fn with_dependency(mut self, asset_path: AssetPath) -> Self { + self.add_dependency(asset_path); self } - pub fn with_dependencies(mut self, asset_paths: Vec>) -> Self { - self.dependencies.extend(asset_paths); + pub fn with_dependencies(mut self, mut asset_paths: Vec>) -> Self { + for asset_path in asset_paths.drain(..) { + self.add_dependency(asset_path); + } self } } @@ -146,20 +149,20 @@ impl<'a> LoadContext<'a> { /// The result of loading an asset of type `T` #[derive(Debug)] -pub struct AssetResult { +pub struct AssetResult { pub asset: Box, pub id: HandleId, pub version: usize, } -/// A channel to send and receive [AssetResult]s +/// A channel to send and receive [`AssetResult`]s #[derive(Debug)] -pub struct AssetLifecycleChannel { +pub struct AssetLifecycleChannel { pub sender: Sender>, pub receiver: Receiver>, } -pub enum AssetLifecycleEvent { +pub enum AssetLifecycleEvent { Create(AssetResult), Free(HandleId), } @@ -193,14 +196,14 @@ impl AssetLifecycle for AssetLifecycleChannel { } } -impl Default for AssetLifecycleChannel { +impl Default for AssetLifecycleChannel { fn default() -> Self { let (sender, receiver) = crossbeam_channel::unbounded(); AssetLifecycleChannel { sender, receiver } } } -/// Updates the [Assets] collection according to the changes queued up by [AssetServer]. +/// Updates the [`Assets`] collection according to the changes queued up by [`AssetServer`]. pub fn update_asset_storage_system( asset_server: Res, assets: ResMut>, diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index 22f4c2f79cfdf..df99eaee19da1 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -18,7 +18,7 @@ impl<'a> AssetPath<'a> { pub fn new_ref(path: &'a Path, label: Option<&'a str>) -> AssetPath<'a> { AssetPath { path: Cow::Borrowed(path), - label: label.map(|val| Cow::Borrowed(val)), + label: label.map(Cow::Borrowed), } } @@ -147,11 +147,17 @@ impl<'a> From<&'a str> for AssetPath<'a> { let label = parts.next(); AssetPath { path: Cow::Borrowed(path), - label: label.map(|label| Cow::Borrowed(label)), + label: label.map(Cow::Borrowed), } } } +impl<'a> From<&'a String> for AssetPath<'a> { + fn from(asset_path: &'a String) -> Self { + asset_path.as_str().into() + } +} + impl<'a> From<&'a Path> for AssetPath<'a> { fn from(path: &'a Path) -> Self { AssetPath { diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index fbad59c25a501..13aa5a785866b 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_audio" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Provides audio functionality for Bevy Engine" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -10,20 +10,26 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.5.0" } -bevy_asset = { path = "../bevy_asset", version = "0.5.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } -bevy_utils = { path = "../bevy_utils", version = "0.5.0" } +bevy_app = { path = "../bevy_app", version = "0.6.0" } +bevy_asset = { path = "../bevy_asset", version = "0.6.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.6.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.6.0", features = ["bevy"] } +bevy_utils = { path = "../bevy_utils", version = "0.6.0" } # other anyhow = "1.0.4" -rodio = { version = "0.14", default-features = false } +rodio = { version = "0.15", default-features = false } parking_lot = "0.11.0" +[target.'cfg(target_arch = "wasm32")'.dependencies] +rodio = { version = "0.15", default-features = false, features = ["wasm-bindgen"] } + +[dev-dependencies] +# bevy +bevy_internal = { path = "../bevy_internal", version = "0.6.0" } + [features] mp3 = ["rodio/mp3"] flac = ["rodio/flac"] wav = ["rodio/wav"] vorbis = ["rodio/vorbis"] -wasm_audio = ["rodio/wasm-bindgen"] diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 7a7fe12b3f99f..1a8ae32b88d9c 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -3,26 +3,36 @@ use bevy_asset::{Asset, Handle}; use parking_lot::RwLock; use std::{collections::VecDeque, fmt}; -/// The external struct used to play audio -pub struct Audio

+/// Use this resource to play audio +/// +/// ``` +/// # use bevy_ecs::system::Res; +/// # use bevy_asset::AssetServer; +/// # use bevy_audio::Audio; +/// fn play_audio_system(asset_server: Res, audio: Res

+impl fmt::Debug for Audio where - P: Decodable, + Source: Decodable, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Audio").field("queue", &self.queue).finish() } } -impl

Default for Audio

+impl Default for Audio where - P: Asset + Decodable, + Source: Asset + Decodable, { fn default() -> Self { Self { @@ -31,13 +41,21 @@ where } } -impl

Audio

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

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

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, + Source: Asset + Decodable, { - pub fn play(&self, audio_source: Handle

) { + /// Play audio from a [`Handle`] to the audio source + /// + /// ``` + /// # use bevy_ecs::system::Res; + /// # use bevy_asset::AssetServer; + /// # use bevy_audio::Audio; + /// fn play_audio_system(asset_server: Res, audio: Res

+pub struct AudioOutput where - P: Decodable, + Source: Decodable, { _stream: Option, stream_handle: Option, - phantom: PhantomData

, + phantom: PhantomData, } -impl

Default for AudioOutput

+impl Default for AudioOutput where - P: Decodable, + Source: Decodable, { fn default() -> Self { if let Ok((stream, stream_handle)) = OutputStream::try_default() { @@ -37,13 +37,11 @@ where } } -impl

AudioOutput

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

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

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, + Source: Asset + Decodable, { - fn play_source(&self, audio_source: &P) { + fn play_source(&self, audio_source: &Source) { if let Some(stream_handle) = &self.stream_handle { let sink = Sink::try_new(stream_handle).unwrap(); sink.append(audio_source.decoder()); @@ -51,7 +49,7 @@ where } } - fn try_play_queued(&self, audio_sources: &Assets

, audio: &mut Audio

) { + fn try_play_queued(&self, audio_sources: &Assets, audio: &mut Audio) { let mut queue = audio.queue.write(); let len = queue.len(); let mut i = 0; @@ -68,18 +66,16 @@ where } } -/// Plays audio currently queued in the [Audio] resource through the [AudioOutput] resource -pub fn play_queued_audio_system(world: &mut World) +/// Plays audio currently queued in the [`Audio`] resource through the [`AudioOutput`] resource +pub fn play_queued_audio_system(world: &mut World) where - P: Decodable, -

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

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, + Source: Decodable, { let world = world.cell(); - let audio_output = world.get_non_send::>().unwrap(); - let mut audio = world.get_resource_mut::>().unwrap(); + let audio_output = world.get_non_send::>().unwrap(); + let mut audio = world.get_resource_mut::>().unwrap(); - if let Some(audio_sources) = world.get_resource::>() { + if let Some(audio_sources) = world.get_resource::>() { audio_output.try_play_queued(&*audio_sources, &mut *audio); }; } diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 8b982cdfb50a4..823526046c7c3 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -8,6 +8,7 @@ use std::{io::Cursor, sync::Arc}; #[derive(Debug, Clone, TypeUuid)] #[uuid = "7a14806a-672b-443b-8d16-4f18afefa463"] pub struct AudioSource { + /// Raw data of the audio source pub bytes: Arc<[u8]>, } @@ -17,11 +18,18 @@ impl AsRef<[u8]> for AudioSource { } } -/// Loads mp3 files as [AudioSource] [Assets](bevy_asset::Assets) +/// Loads files as [`AudioSource`] [`Assets`](bevy_asset::Assets) +/// +/// This asset loader supports different audio formats based on the enable Bevy features. +/// The feature `bevy/vorbis` enables loading from `.ogg` files and is enabled by default. +/// Other file endings can be loaded from with additional features: +/// `.mp3` with `bevy/mp3` +/// `.flac` with `bevy/flac` +/// `.wav` with `bevy/wav` #[derive(Default)] -pub struct Mp3Loader; +pub struct AudioLoader; -impl AssetLoader for Mp3Loader { +impl AssetLoader for AudioLoader { fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> BoxedFuture> { load_context.set_default_asset(LoadedAsset::new(AudioSource { bytes: bytes.into(), @@ -43,14 +51,20 @@ impl AssetLoader for Mp3Loader { } } +/// A type implementing this trait can be decoded as a rodio source pub trait Decodable: Send + Sync + 'static { - type Decoder; + /// The decoder that can decode the implemeting type + type Decoder: rodio::Source + Send + Sync + Iterator; + /// A single value given by the decoder + type DecoderItem: rodio::Sample + Send + Sync; + /// Build and return a [`Self::Decoder`] for the implementing type fn decoder(&self) -> Self::Decoder; } impl Decodable for AudioSource { type Decoder = rodio::Decoder>; + type DecoderItem = > as Iterator>::Item; fn decoder(&self) -> Self::Decoder { rodio::Decoder::new(Cursor::new(self.clone())).unwrap() diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 5f44d68f9f40b..0bf910cbba9be 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -1,7 +1,38 @@ +//! Audio support for the game engine Bevy +//! +//! ``` +//! # use bevy_ecs::{system::Res, event::EventWriter}; +//! # use bevy_audio::{Audio, AudioPlugin}; +//! # use bevy_asset::{AssetPlugin, AssetServer}; +//! # use bevy_app::{App, AppExit}; +//! # use bevy_internal::MinimalPlugins; +//! fn main() { +//! App::new() +//! .add_plugins(MinimalPlugins) +//! .add_plugin(AssetPlugin) +//! .add_plugin(AudioPlugin) +//! # .add_system(stop) +//! .add_startup_system(play_background_audio) +//! .run(); +//! } +//! +//! fn play_background_audio(asset_server: Res, audio: Res, +} + +/// Type-level linked list terminator for array dimensions. +pub struct DimensionNil; + +/// Trait for type-level array dimensions. Probably shouldn't be implemented outside this crate. +pub unsafe trait DimensionList { + /// Write dimensions in square brackets to a string, list tail to list head. + fn push_to_string(s: &mut String); +} + +unsafe impl DimensionList for DimensionNil { + fn push_to_string(_: &mut String) {} +} + +unsafe impl DimensionList for Dimension { + fn push_to_string(s: &mut String) { + use std::fmt::Write; + A::push_to_string(s); + write!(s, "[{}]", N).unwrap(); + } +} + +/// Trait for types that have a GLSL equivalent. Useful for generating GLSL code +/// from Rust structs. +pub unsafe trait Glsl { + /// The name of this type in GLSL, like `vec2` or `mat4`. + const NAME: &'static str; +} + +/// Trait for types that can be represented as a struct in GLSL. +/// +/// This trait should not generally be implemented by hand, but can be derived. +pub unsafe trait GlslStruct: Glsl { + /// The fields contained in this struct. + fn enumerate_fields(s: &mut String); + + /// Generates GLSL code that represents this struct and its fields. + fn glsl_definition() -> String { + let mut output = String::new(); + output.push_str("struct "); + output.push_str(Self::NAME); + output.push_str(" {\n"); + + Self::enumerate_fields(&mut output); + + output.push_str("};"); + output + } +} + +/// Trait for types that are expressible as a GLSL type with (possibly zero) array dimensions. +pub unsafe trait GlslArray { + /// Base type name. + const NAME: &'static str; + /// Type-level linked list of array dimensions, ordered outer to inner. + type ArraySize: DimensionList; +} + +unsafe impl GlslArray for T { + const NAME: &'static str = ::NAME; + type ArraySize = DimensionNil; +} + +unsafe impl Glsl for f32 { + const NAME: &'static str = "float"; +} + +unsafe impl Glsl for f64 { + const NAME: &'static str = "double"; +} + +unsafe impl Glsl for i32 { + const NAME: &'static str = "int"; +} + +unsafe impl Glsl for u32 { + const NAME: &'static str = "uint"; +} + +unsafe impl GlslArray for [T; N] { + const NAME: &'static str = T::NAME; + + type ArraySize = Dimension; +} diff --git a/crates/bevy_crevice/src/imp.rs b/crates/bevy_crevice/src/imp.rs new file mode 100644 index 0000000000000..af49bd8915bbf --- /dev/null +++ b/crates/bevy_crevice/src/imp.rs @@ -0,0 +1,10 @@ +mod imp_mint; + +#[cfg(feature = "cgmath")] +mod imp_cgmath; + +#[cfg(feature = "glam")] +mod imp_glam; + +#[cfg(feature = "nalgebra")] +mod imp_nalgebra; diff --git a/crates/bevy_crevice/src/imp/imp_cgmath.rs b/crates/bevy_crevice/src/imp/imp_cgmath.rs new file mode 100644 index 0000000000000..79ee7e071cec2 --- /dev/null +++ b/crates/bevy_crevice/src/imp/imp_cgmath.rs @@ -0,0 +1,30 @@ +easy_impl! { + Vec2 cgmath::Vector2 { x, y }, + Vec3 cgmath::Vector3 { x, y, z }, + Vec4 cgmath::Vector4 { x, y, z, w }, + + IVec2 cgmath::Vector2 { x, y }, + IVec3 cgmath::Vector3 { x, y, z }, + IVec4 cgmath::Vector4 { x, y, z, w }, + + UVec2 cgmath::Vector2 { x, y }, + UVec3 cgmath::Vector3 { x, y, z }, + UVec4 cgmath::Vector4 { x, y, z, w }, + + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // BVec2 cgmath::Vector2 { x, y }, + // BVec3 cgmath::Vector3 { x, y, z }, + // BVec4 cgmath::Vector4 { x, y, z, w }, + + DVec2 cgmath::Vector2 { x, y }, + DVec3 cgmath::Vector3 { x, y, z }, + DVec4 cgmath::Vector4 { x, y, z, w }, + + Mat2 cgmath::Matrix2 { x, y }, + Mat3 cgmath::Matrix3 { x, y, z }, + Mat4 cgmath::Matrix4 { x, y, z, w }, + + DMat2 cgmath::Matrix2 { x, y }, + DMat3 cgmath::Matrix3 { x, y, z }, + DMat4 cgmath::Matrix4 { x, y, z, w }, +} diff --git a/crates/bevy_crevice/src/imp/imp_glam.rs b/crates/bevy_crevice/src/imp/imp_glam.rs new file mode 100644 index 0000000000000..58ef711c27855 --- /dev/null +++ b/crates/bevy_crevice/src/imp/imp_glam.rs @@ -0,0 +1,24 @@ +minty_impl! { + mint::Vector2 => glam::Vec2, + mint::Vector3 => glam::Vec3, + mint::Vector4 => glam::Vec4, + mint::Vector2 => glam::IVec2, + mint::Vector3 => glam::IVec3, + mint::Vector4 => glam::IVec4, + mint::Vector2 => glam::UVec2, + mint::Vector3 => glam::UVec3, + mint::Vector4 => glam::UVec4, + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // mint::Vector2 => glam::BVec2, + // mint::Vector3 => glam::BVec3, + // mint::Vector4 => glam::BVec4, + mint::Vector2 => glam::DVec2, + mint::Vector3 => glam::DVec3, + mint::Vector4 => glam::DVec4, + mint::ColumnMatrix2 => glam::Mat2, + mint::ColumnMatrix3 => glam::Mat3, + mint::ColumnMatrix4 => glam::Mat4, + mint::ColumnMatrix2 => glam::DMat2, + mint::ColumnMatrix3 => glam::DMat3, + mint::ColumnMatrix4 => glam::DMat4, +} diff --git a/crates/bevy_crevice/src/imp/imp_mint.rs b/crates/bevy_crevice/src/imp/imp_mint.rs new file mode 100644 index 0000000000000..056a181c2ca70 --- /dev/null +++ b/crates/bevy_crevice/src/imp/imp_mint.rs @@ -0,0 +1,30 @@ +easy_impl! { + Vec2 mint::Vector2 { x, y }, + Vec3 mint::Vector3 { x, y, z }, + Vec4 mint::Vector4 { x, y, z, w }, + + IVec2 mint::Vector2 { x, y }, + IVec3 mint::Vector3 { x, y, z }, + IVec4 mint::Vector4 { x, y, z, w }, + + UVec2 mint::Vector2 { x, y }, + UVec3 mint::Vector3 { x, y, z }, + UVec4 mint::Vector4 { x, y, z, w }, + + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // BVec2 mint::Vector2 { x, y }, + // BVec3 mint::Vector3 { x, y, z }, + // BVec4 mint::Vector4 { x, y, z, w }, + + DVec2 mint::Vector2 { x, y }, + DVec3 mint::Vector3 { x, y, z }, + DVec4 mint::Vector4 { x, y, z, w }, + + Mat2 mint::ColumnMatrix2 { x, y }, + Mat3 mint::ColumnMatrix3 { x, y, z }, + Mat4 mint::ColumnMatrix4 { x, y, z, w }, + + DMat2 mint::ColumnMatrix2 { x, y }, + DMat3 mint::ColumnMatrix3 { x, y, z }, + DMat4 mint::ColumnMatrix4 { x, y, z, w }, +} diff --git a/crates/bevy_crevice/src/imp/imp_nalgebra.rs b/crates/bevy_crevice/src/imp/imp_nalgebra.rs new file mode 100644 index 0000000000000..3d1b89c0d315b --- /dev/null +++ b/crates/bevy_crevice/src/imp/imp_nalgebra.rs @@ -0,0 +1,24 @@ +minty_impl! { + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // mint::Vector2 => nalgebra::Vector2, + // mint::Vector3 => nalgebra::Vector3, + // mint::Vector4 => nalgebra::Vector4, + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + mint::ColumnMatrix2 => nalgebra::Matrix2, + mint::ColumnMatrix3 => nalgebra::Matrix3, + mint::ColumnMatrix4 => nalgebra::Matrix4, + mint::ColumnMatrix2 => nalgebra::Matrix2, + mint::ColumnMatrix3 => nalgebra::Matrix3, + mint::ColumnMatrix4 => nalgebra::Matrix4, +} diff --git a/crates/bevy_crevice/src/internal.rs b/crates/bevy_crevice/src/internal.rs new file mode 100644 index 0000000000000..cd22972fb30aa --- /dev/null +++ b/crates/bevy_crevice/src/internal.rs @@ -0,0 +1,40 @@ +//! This module is internal to crevice but used by its derive macro. No +//! guarantees are made about its contents. + +pub use bytemuck; + +/// Gives the number of bytes needed to make `offset` be aligned to `alignment`. +pub const fn align_offset(offset: usize, alignment: usize) -> usize { + if alignment == 0 || offset % alignment == 0 { + 0 + } else { + alignment - offset % alignment + } +} + +/// Max of two `usize`. Implemented because the `max` method from `Ord` cannot +/// be used in const fns. +pub const fn max(a: usize, b: usize) -> usize { + if a > b { + a + } else { + b + } +} + +/// Max of an array of `usize`. This function's implementation is funky because +/// we have no for loops! +pub const fn max_arr(input: [usize; N]) -> usize { + let mut max = 0; + let mut i = 0; + + while i < N { + if input[i] > max { + max = input[i]; + } + + i += 1; + } + + max +} diff --git a/crates/bevy_crevice/src/lib.rs b/crates/bevy_crevice/src/lib.rs new file mode 100644 index 0000000000000..48451966be4a5 --- /dev/null +++ b/crates/bevy_crevice/src/lib.rs @@ -0,0 +1,174 @@ +#![allow( + clippy::new_without_default, + clippy::needless_update, + clippy::len_without_is_empty, + clippy::needless_range_loop, + clippy::all, + clippy::doc_markdown +)] +/*! +[![GitHub CI Status](https://github.com/LPGhatguy/crevice/workflows/CI/badge.svg)](https://github.com/LPGhatguy/crevice/actions) +[![crevice on crates.io](https://img.shields.io/crates/v/crevice.svg)](https://crates.io/crates/crevice) +[![crevice docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/crevice) + +Crevice creates GLSL-compatible versions of types through the power of derive +macros. Generated structures provide an [`as_bytes`][std140::Std140::as_bytes] +method to allow safely packing data into buffers for uploading. + +Generated structs also implement [`bytemuck::Zeroable`] and +[`bytemuck::Pod`] for use with other libraries. + +Crevice is similar to [`glsl-layout`][glsl-layout], but supports types from many +math crates, can generate GLSL source from structs, and explicitly initializes +padding to remove one source of undefined behavior. + +Crevice has support for many Rust math libraries via feature flags, and most +other math libraries by use of the mint crate. Crevice currently supports: + +* mint 0.5, enabled by default +* cgmath 0.18, using the `cgmath` feature +* nalgebra 0.29, using the `nalgebra` feature +* glam 0.19, using the `glam` feature + +PRs are welcome to add or update math libraries to Crevice. + +If your math library is not supported, it's possible to define structs using the +types from mint and convert your math library's types into mint types. This is +supported by most Rust math libraries. + +Your math library may require you to turn on a feature flag to get mint support. +For example, cgmath requires the "mint" feature to be enabled to allow +conversions to and from mint types. + +## Examples + +### Single Value + +Uploading many types can be done by deriving [`AsStd140`][std140::AsStd140] and +using [`as_std140`][std140::AsStd140::as_std140] and +[`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes. + +```glsl +uniform MAIN { + mat3 orientation; + vec3 position; + float scale; +} main; +``` + +```rust +use bevy_crevice::std140::{AsStd140, Std140}; + +#[derive(AsStd140)] +struct MainUniform { + orientation: mint::ColumnMatrix3, + position: mint::Vector3, + scale: f32, +} + +let value = MainUniform { + orientation: [ + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ].into(), + position: [1.0, 2.0, 3.0].into(), + scale: 4.0, +}; + +let value_std140 = value.as_std140(); + +# fn upload_data_to_gpu(_value: &[u8]) {} +upload_data_to_gpu(value_std140.as_bytes()); +``` + +### Sequential Types + +More complicated data can be uploaded using the std140 +[`Writer`][std140::Writer] type. + +```glsl +struct PointLight { + vec3 position; + vec3 color; + float brightness; +}; + +buffer POINT_LIGHTS { + uint len; + PointLight[] lights; +} point_lights; +``` + +```rust +use bevy_crevice::std140::{self, AsStd140}; + +#[derive(AsStd140)] +struct PointLight { + position: mint::Vector3, + color: mint::Vector3, + brightness: f32, +} + +let lights = vec![ + PointLight { + position: [0.0, 1.0, 0.0].into(), + color: [1.0, 0.0, 0.0].into(), + brightness: 0.6, + }, + PointLight { + position: [0.0, 4.0, 3.0].into(), + color: [1.0, 1.0, 1.0].into(), + brightness: 1.0, + }, +]; + +# fn map_gpu_buffer_for_write() -> &'static mut [u8] { +# Box::leak(vec![0; 1024].into_boxed_slice()) +# } +let target_buffer = map_gpu_buffer_for_write(); +let mut writer = std140::Writer::new(target_buffer); + +let light_count = lights.len() as u32; +writer.write(&light_count)?; + +// Crevice will automatically insert the required padding to align the +// PointLight structure correctly. In this case, there will be 12 bytes of +// padding between the length field and the light list. + +writer.write(lights.as_slice())?; + +# fn unmap_gpu_buffer() {} +unmap_gpu_buffer(); + +# Ok::<(), std::io::Error>(()) +``` + +## Features + +* `std` (default): Enables [`std::io::Write`]-based structs. +* `cgmath`: Enables support for types from cgmath. +* `nalgebra`: Enables support for types from nalgebra. +* `glam`: Enables support for types from glam. + +## Minimum Supported Rust Version (MSRV) + +Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features. + +[glsl-layout]: https://github.com/rustgd/glsl-layout +*/ + +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[macro_use] +mod util; + +pub mod glsl; +pub mod std140; +pub mod std430; + +#[doc(hidden)] +pub mod internal; + +mod imp; diff --git a/crates/bevy_crevice/src/std140.rs b/crates/bevy_crevice/src/std140.rs new file mode 100644 index 0000000000000..dd7cde1cabf40 --- /dev/null +++ b/crates/bevy_crevice/src/std140.rs @@ -0,0 +1,18 @@ +//! Defines traits and types for working with data adhering to GLSL's `std140` +//! layout specification. + +mod dynamic_uniform; +mod primitives; +mod sizer; +mod traits; +#[cfg(feature = "std")] +mod writer; + +pub use self::dynamic_uniform::*; +pub use self::primitives::*; +pub use self::sizer::*; +pub use self::traits::*; +#[cfg(feature = "std")] +pub use self::writer::*; + +pub use bevy_crevice_derive::AsStd140; diff --git a/crates/bevy_crevice/src/std140/dynamic_uniform.rs b/crates/bevy_crevice/src/std140/dynamic_uniform.rs new file mode 100644 index 0000000000000..262f8ea449842 --- /dev/null +++ b/crates/bevy_crevice/src/std140/dynamic_uniform.rs @@ -0,0 +1,68 @@ +use bytemuck::{Pod, Zeroable}; + +#[allow(unused_imports)] +use crate::internal::{align_offset, max}; +use crate::std140::{AsStd140, Std140}; + +/// Wrapper type that aligns the inner type to at least 256 bytes. +/// +/// This type is useful for ensuring correct alignment when creating dynamic +/// uniform buffers in APIs like WebGPU. +pub struct DynamicUniform(pub T); + +impl AsStd140 for DynamicUniform { + type Output = DynamicUniformStd140<::Output>; + + fn as_std140(&self) -> Self::Output { + DynamicUniformStd140(self.0.as_std140()) + } + + fn from_std140(value: Self::Output) -> Self { + DynamicUniform(::from_std140(value.0)) + } +} + +/// std140 variant of [`DynamicUniform`]. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct DynamicUniformStd140(T); + +unsafe impl Std140 for DynamicUniformStd140 { + const ALIGNMENT: usize = max(256, T::ALIGNMENT); + #[cfg(const_evaluatable_checked)] + type Padded = crate::std140::Std140Padded< + Self, + { align_offset(core::mem::size_of::(), max(256, T::ALIGNMENT)) }, + >; + #[cfg(not(const_evaluatable_checked))] + type Padded = crate::std140::InvalidPadded; +} + +unsafe impl Zeroable for DynamicUniformStd140 {} +unsafe impl Pod for DynamicUniformStd140 {} + +#[cfg(test)] +mod test { + use super::*; + + use crate::std140::{self, WriteStd140}; + + #[test] + fn size_is_unchanged() { + let dynamic_f32 = DynamicUniform(0.0f32); + + assert_eq!(dynamic_f32.std140_size(), 0.0f32.std140_size()); + } + + #[test] + fn alignment_applies() { + let mut output = Vec::new(); + let mut writer = std140::Writer::new(&mut output); + + writer.write(&DynamicUniform(0.0f32)).unwrap(); + assert_eq!(writer.len(), 4); + + writer.write(&DynamicUniform(1.0f32)).unwrap(); + assert_eq!(writer.len(), 260); + } +} diff --git a/crates/bevy_crevice/src/std140/primitives.rs b/crates/bevy_crevice/src/std140/primitives.rs new file mode 100644 index 0000000000000..34e161e3b7818 --- /dev/null +++ b/crates/bevy_crevice/src/std140/primitives.rs @@ -0,0 +1,175 @@ +use bytemuck::{Pod, Zeroable}; + +use crate::glsl::Glsl; +use crate::std140::{Std140, Std140Padded}; + +use crate::internal::{align_offset, max}; +use core::mem::size_of; + +unsafe impl Std140 for f32 { + const ALIGNMENT: usize = 4; + type Padded = Std140Padded; +} + +unsafe impl Std140 for f64 { + const ALIGNMENT: usize = 8; + type Padded = Std140Padded; +} + +unsafe impl Std140 for i32 { + const ALIGNMENT: usize = 4; + type Padded = Std140Padded; +} + +unsafe impl Std140 for u32 { + const ALIGNMENT: usize = 4; + type Padded = Std140Padded; +} + +macro_rules! vectors { + ( + $( + #[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+) + )+ + ) => { + $( + #[$doc] + #[allow(missing_docs)] + #[derive(Debug, Clone, Copy, PartialEq)] + #[repr(C)] + pub struct $name { + $(pub $field: $prim,)+ + } + + unsafe impl Zeroable for $name {} + unsafe impl Pod for $name {} + + unsafe impl Std140 for $name { + const ALIGNMENT: usize = $align; + type Padded = Std140Padded(), max(16, $align))}>; + } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } + )+ + }; +} + +vectors! { + #[doc = "Corresponds to a GLSL `vec2` in std140 layout."] align(8) vec2 Vec2(x, y) + #[doc = "Corresponds to a GLSL `vec3` in std140 layout."] align(16) vec3 Vec3(x, y, z) + #[doc = "Corresponds to a GLSL `vec4` in std140 layout."] align(16) vec4 Vec4(x, y, z, w) + + #[doc = "Corresponds to a GLSL `ivec2` in std140 layout."] align(8) ivec2 IVec2(x, y) + #[doc = "Corresponds to a GLSL `ivec3` in std140 layout."] align(16) ivec3 IVec3(x, y, z) + #[doc = "Corresponds to a GLSL `ivec4` in std140 layout."] align(16) ivec4 IVec4(x, y, z, w) + + #[doc = "Corresponds to a GLSL `uvec2` in std140 layout."] align(8) uvec2 UVec2(x, y) + #[doc = "Corresponds to a GLSL `uvec3` in std140 layout."] align(16) uvec3 UVec3(x, y, z) + #[doc = "Corresponds to a GLSL `uvec4` in std140 layout."] align(16) uvec4 UVec4(x, y, z, w) + + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + + // #[doc = "Corresponds to a GLSL `bvec2` in std140 layout."] align(8) bvec2 BVec2(x, y) + // #[doc = "Corresponds to a GLSL `bvec3` in std140 layout."] align(16) bvec3 BVec3(x, y, z) + // #[doc = "Corresponds to a GLSL `bvec4` in std140 layout."] align(16) bvec4 BVec4(x, y, z, w) + + #[doc = "Corresponds to a GLSL `dvec2` in std140 layout."] align(16) dvec2 DVec2(x, y) + #[doc = "Corresponds to a GLSL `dvec3` in std140 layout."] align(32) dvec3 DVec3(x, y, z) + #[doc = "Corresponds to a GLSL `dvec4` in std140 layout."] align(32) dvec4 DVec4(x, y, z, w) +} + +macro_rules! matrices { + ( + $( + #[$doc:meta] + align($align:literal) + $glsl_name:ident $name:ident { + $($field:ident: $field_ty:ty,)+ + } + )+ + ) => { + $( + #[$doc] + #[allow(missing_docs)] + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub struct $name { + $(pub $field: $field_ty,)+ + } + + unsafe impl Zeroable for $name {} + unsafe impl Pod for $name {} + + unsafe impl Std140 for $name { + const ALIGNMENT: usize = $align; + /// Matrices are technically arrays of primitives, and as such require pad at end. + const PAD_AT_END: bool = true; + type Padded = Std140Padded(), max(16, $align))}>; + } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } + )+ + }; +} + +matrices! { + #[doc = "Corresponds to a GLSL `mat2` in std140 layout."] + align(16) + mat2 Mat2 { + x: Vec2, + _pad_x: [f32; 2], + y: Vec2, + _pad_y: [f32; 2], + } + + #[doc = "Corresponds to a GLSL `mat3` in std140 layout."] + align(16) + mat3 Mat3 { + x: Vec3, + _pad_x: f32, + y: Vec3, + _pad_y: f32, + z: Vec3, + _pad_z: f32, + } + + #[doc = "Corresponds to a GLSL `mat4` in std140 layout."] + align(16) + mat4 Mat4 { + x: Vec4, + y: Vec4, + z: Vec4, + w: Vec4, + } + + #[doc = "Corresponds to a GLSL `dmat2` in std140 layout."] + align(16) + dmat2 DMat2 { + x: DVec2, + y: DVec2, + } + + #[doc = "Corresponds to a GLSL `dmat3` in std140 layout."] + align(32) + dmat3 DMat3 { + x: DVec3, + _pad_x: f64, + y: DVec3, + _pad_y: f64, + z: DVec3, + _pad_z: f64, + } + + #[doc = "Corresponds to a GLSL `dmat3` in std140 layout."] + align(32) + dmat4 DMat4 { + x: DVec4, + y: DVec4, + z: DVec4, + w: DVec4, + } +} diff --git a/crates/bevy_crevice/src/std140/sizer.rs b/crates/bevy_crevice/src/std140/sizer.rs new file mode 100644 index 0000000000000..87c27cb63b3d9 --- /dev/null +++ b/crates/bevy_crevice/src/std140/sizer.rs @@ -0,0 +1,81 @@ +use core::mem::size_of; + +use crate::internal::align_offset; +use crate::std140::{AsStd140, Std140}; + +/** +Type that computes the buffer size needed by a series of `std140` types laid +out. + +This type works well well when paired with `Writer`, precomputing a buffer's +size to alleviate the need to dynamically re-allocate buffers. + +## Example + +```glsl +struct Frob { + vec3 size; + float frobiness; +} + +buffer FROBS { + uint len; + Frob[] frobs; +} frobs; +``` + +``` +use bevy_crevice::std140::{self, AsStd140}; + +#[derive(AsStd140)] +struct Frob { + size: mint::Vector3, + frobiness: f32, +} + +// Many APIs require that buffers contain at least enough space for all +// fixed-size bindiongs to a buffer as well as one element of any arrays, if +// there are any. +let mut sizer = std140::Sizer::new(); +sizer.add::(); +sizer.add::(); + +# fn create_buffer_with_size(size: usize) {} +let buffer = create_buffer_with_size(sizer.len()); +# assert_eq!(sizer.len(), 32); +``` +*/ +pub struct Sizer { + offset: usize, +} + +impl Sizer { + /// Create a new `Sizer`. + pub fn new() -> Self { + Self { offset: 0 } + } + + /// Add a type's necessary padding and size to the `Sizer`. Returns the + /// offset into the buffer where that type would be written. + pub fn add(&mut self) -> usize + where + T: AsStd140, + { + let size = size_of::<::Output>(); + let alignment = ::Output::ALIGNMENT; + let padding = align_offset(self.offset, alignment); + + self.offset += padding; + let write_here = self.offset; + + self.offset += size; + + write_here + } + + /// Returns the number of bytes required to contain all the types added to + /// the `Sizer`. + pub fn len(&self) -> usize { + self.offset + } +} diff --git a/crates/bevy_crevice/src/std140/traits.rs b/crates/bevy_crevice/src/std140/traits.rs new file mode 100644 index 0000000000000..392251c3f80ec --- /dev/null +++ b/crates/bevy_crevice/src/std140/traits.rs @@ -0,0 +1,284 @@ +use core::mem::{size_of, MaybeUninit}; +#[cfg(feature = "std")] +use std::io::{self, Write}; + +use bytemuck::{bytes_of, Pod, Zeroable}; + +#[cfg(feature = "std")] +use crate::std140::Writer; + +/// Trait implemented for all `std140` primitives. Generally should not be +/// implemented outside this crate. +pub unsafe trait Std140: Copy + Zeroable + Pod { + /// The required alignment of the type. Must be a power of two. + /// + /// This is distinct from the value returned by `std::mem::align_of` because + /// `AsStd140` structs do not use Rust's alignment. This enables them to + /// control and zero their padding bytes, making converting them to and from + /// slices safe. + const ALIGNMENT: usize; + + /// Whether this type requires a padding at the end (ie, is a struct or an array + /// of primitives). + /// See + /// (rule 4 and 9) + const PAD_AT_END: bool = false; + /// Padded type (Std140Padded specialization) + /// The usual implementation is + /// type Padded = Std140Padded(), max(16, ALIGNMENT))}>; + type Padded: Std140Convertible; + + /// Casts the type to a byte array. Implementors should not override this + /// method. + /// + /// # Safety + /// This is always safe due to the requirements of [`bytemuck::Pod`] being a + /// prerequisite for this trait. + fn as_bytes(&self) -> &[u8] { + bytes_of(self) + } +} + +/// Trait specifically for Std140::Padded, implements conversions between padded type and base type. +pub trait Std140Convertible: Copy { + /// Convert from self to Std140 + fn into_std140(self) -> T; + /// Convert from Std140 to self + fn from_std140(_: T) -> Self; +} + +impl Std140Convertible for T { + fn into_std140(self) -> T { + self + } + fn from_std140(also_self: T) -> Self { + also_self + } +} + +/// Unfortunately, we cannot easily derive padded representation for generic Std140 types. +/// For now, we'll just use this empty enum with no values. +#[derive(Copy, Clone)] +pub enum InvalidPadded {} +impl Std140Convertible for InvalidPadded { + fn into_std140(self) -> T { + unimplemented!() + } + fn from_std140(_: T) -> Self { + unimplemented!() + } +} +/** +Trait implemented for all types that can be turned into `std140` values. +* +This trait can often be `#[derive]`'d instead of manually implementing it. Any +struct which contains only fields that also implement `AsStd140` can derive +`AsStd140`. + +Types from the mint crate implement `AsStd140`, making them convenient for use +in uniform types. Most Rust math crates, like cgmath, nalgebra, and +ultraviolet support mint. + +## Example + +```glsl +uniform CAMERA { + mat4 view; + mat4 projection; +} camera; +``` + +```no_run +use bevy_crevice::std140::{AsStd140, Std140}; + +#[derive(AsStd140)] +struct CameraUniform { + view: mint::ColumnMatrix4, + projection: mint::ColumnMatrix4, +} + +let view: mint::ColumnMatrix4 = todo!("your math code here"); +let projection: mint::ColumnMatrix4 = todo!("your math code here"); + +let camera = CameraUniform { + view, + projection, +}; + +# fn write_to_gpu_buffer(bytes: &[u8]) {} +let camera_std140 = camera.as_std140(); +write_to_gpu_buffer(camera_std140.as_bytes()); +``` +*/ +pub trait AsStd140 { + /// The `std140` version of this value. + type Output: Std140; + + /// Convert this value into the `std140` version of itself. + fn as_std140(&self) -> Self::Output; + + /// Returns the size of the `std140` version of this type. Useful for + /// pre-sizing buffers. + fn std140_size_static() -> usize { + size_of::() + } + + /// Converts from `std140` version of self to self. + fn from_std140(val: Self::Output) -> Self; +} + +impl AsStd140 for T +where + T: Std140, +{ + type Output = Self; + + fn as_std140(&self) -> Self { + *self + } + + fn from_std140(x: Self) -> Self { + x + } +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug)] +pub struct Std140Padded { + inner: T, + _padding: [u8; PAD], +} + +unsafe impl Zeroable for Std140Padded {} +unsafe impl Pod for Std140Padded {} + +impl Std140Convertible for Std140Padded { + fn into_std140(self) -> T { + self.inner + } + + fn from_std140(inner: T) -> Self { + Self { + inner, + _padding: [0u8; PAD], + } + } +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct Std140Array([T::Padded; N]); + +unsafe impl Zeroable for Std140Array where T::Padded: Zeroable {} +unsafe impl Pod for Std140Array where T::Padded: Pod {} +unsafe impl Std140 for Std140Array +where + T::Padded: Pod, +{ + const ALIGNMENT: usize = crate::internal::max(T::ALIGNMENT, 16); + type Padded = Self; +} + +impl Std140Array { + fn uninit_array() -> [MaybeUninit; N] { + unsafe { MaybeUninit::uninit().assume_init() } + } + + fn from_uninit_array(a: [MaybeUninit; N]) -> Self { + unsafe { core::mem::transmute_copy(&a) } + } +} + +impl AsStd140 for [T; N] +where + ::Padded: Pod, +{ + type Output = Std140Array; + fn as_std140(&self) -> Self::Output { + let mut res = Self::Output::uninit_array(); + + for i in 0..N { + res[i] = MaybeUninit::new(Std140Convertible::from_std140(self[i].as_std140())); + } + + Self::Output::from_uninit_array(res) + } + + fn from_std140(val: Self::Output) -> Self { + let mut res: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + for i in 0..N { + res[i] = MaybeUninit::new(T::from_std140(Std140Convertible::into_std140(val.0[i]))); + } + unsafe { core::mem::transmute_copy(&res) } + } +} + +/// Trait implemented for all types that can be written into a buffer as +/// `std140` bytes. This type is more general than [`AsStd140`]: all `AsStd140` +/// types implement `WriteStd140`, but not the other way around. +/// +/// While `AsStd140` requires implementers to return a type that implements the +/// `Std140` trait, `WriteStd140` directly writes bytes using a [`Writer`]. This +/// makes `WriteStd140` usable for writing slices or other DSTs that could not +/// implement `AsStd140` without allocating new memory on the heap. +#[cfg(feature = "std")] +pub trait WriteStd140 { + /// Writes this value into the given [`Writer`] using `std140` layout rules. + /// + /// Should return the offset of the first byte of this type, as returned by + /// the first call to [`Writer::write`]. + fn write_std140(&self, writer: &mut Writer) -> io::Result; + + /// The space required to write this value using `std140` layout rules. This + /// does not include alignment padding that may be needed before or after + /// this type when written as part of a larger buffer. + fn std140_size(&self) -> usize { + let mut writer = Writer::new(io::sink()); + self.write_std140(&mut writer).unwrap(); + writer.len() + } +} + +#[cfg(feature = "std")] +impl WriteStd140 for T +where + T: AsStd140, +{ + fn write_std140(&self, writer: &mut Writer) -> io::Result { + writer.write_std140(&self.as_std140()) + } + + fn std140_size(&self) -> usize { + size_of::<::Output>() + } +} + +#[cfg(feature = "std")] +impl WriteStd140 for [T] +where + T: WriteStd140, +{ + fn write_std140(&self, writer: &mut Writer) -> io::Result { + // if no items are written, offset is current position of the writer + let mut offset = writer.len(); + + let mut iter = self.iter(); + + if let Some(item) = iter.next() { + offset = item.write_std140(writer)?; + } + + for item in iter { + item.write_std140(writer)?; + } + + Ok(offset) + } + + fn std140_size(&self) -> usize { + let mut writer = Writer::new(io::sink()); + self.write_std140(&mut writer).unwrap(); + writer.len() + } +} diff --git a/crates/bevy_crevice/src/std140/writer.rs b/crates/bevy_crevice/src/std140/writer.rs new file mode 100644 index 0000000000000..aeed06ff78e9b --- /dev/null +++ b/crates/bevy_crevice/src/std140/writer.rs @@ -0,0 +1,162 @@ +use std::io::{self, Write}; +use std::mem::size_of; + +use bytemuck::bytes_of; + +use crate::internal::align_offset; +use crate::std140::{AsStd140, Std140, WriteStd140}; + +/** +Type that enables writing correctly aligned `std140` values to a buffer. + +`Writer` is useful when many values need to be laid out in a row that cannot be +represented by a struct alone, like dynamically sized arrays or dynamically +laid-out values. + +## Example +In this example, we'll write a length-prefixed list of lights to a buffer. +`std140::Writer` helps align correctly, even across multiple structs, which can +be tricky and error-prone otherwise. + +```glsl +struct PointLight { + vec3 position; + vec3 color; + float brightness; +}; + +buffer POINT_LIGHTS { + uint len; + PointLight[] lights; +} point_lights; +``` + +``` +use bevy_crevice::std140::{self, AsStd140}; + +#[derive(AsStd140)] +struct PointLight { + position: mint::Vector3, + color: mint::Vector3, + brightness: f32, +} + +let lights = vec![ + PointLight { + position: [0.0, 1.0, 0.0].into(), + color: [1.0, 0.0, 0.0].into(), + brightness: 0.6, + }, + PointLight { + position: [0.0, 4.0, 3.0].into(), + color: [1.0, 1.0, 1.0].into(), + brightness: 1.0, + }, +]; + +# fn map_gpu_buffer_for_write() -> &'static mut [u8] { +# Box::leak(vec![0; 1024].into_boxed_slice()) +# } +let target_buffer = map_gpu_buffer_for_write(); +let mut writer = std140::Writer::new(target_buffer); + +let light_count = lights.len() as u32; +writer.write(&light_count)?; + +// Crevice will automatically insert the required padding to align the +// PointLight structure correctly. In this case, there will be 12 bytes of +// padding between the length field and the light list. + +writer.write(lights.as_slice())?; + +# fn unmap_gpu_buffer() {} +unmap_gpu_buffer(); + +# Ok::<(), std::io::Error>(()) +``` +*/ +pub struct Writer { + writer: W, + offset: usize, +} + +impl Writer { + /// Create a new `Writer`, wrapping a buffer, file, or other type that + /// implements [`std::io::Write`]. + pub fn new(writer: W) -> Self { + Self { writer, offset: 0 } + } + + /// Write a new value to the underlying buffer, writing zeroed padding where + /// necessary. + /// + /// Returns the offset into the buffer that the value was written to. + pub fn write(&mut self, value: &T) -> io::Result + where + T: WriteStd140 + ?Sized, + { + value.write_std140(self) + } + + /// Write an iterator of values to the underlying buffer. + /// + /// Returns the offset into the buffer that the first value was written to. + /// If no values were written, returns the `len()`. + pub fn write_iter(&mut self, iter: I) -> io::Result + where + I: IntoIterator, + T: WriteStd140, + { + let mut offset = self.offset; + + let mut iter = iter.into_iter(); + + if let Some(item) = iter.next() { + offset = item.write_std140(self)?; + } + + for item in iter { + item.write_std140(self)?; + } + + Ok(offset) + } + + /// Write an `Std140` type to the underlying buffer. + pub fn write_std140(&mut self, value: &T) -> io::Result + where + T: Std140, + { + let padding = align_offset(self.offset, T::ALIGNMENT); + + for _ in 0..padding { + self.writer.write_all(&[0])?; + } + self.offset += padding; + + let value = value.as_std140(); + self.writer.write_all(bytes_of(&value))?; + + let write_here = self.offset; + self.offset += size_of::(); + + Ok(write_here) + } + + /// Write a slice of values to the underlying buffer. + #[deprecated( + since = "0.6.0", + note = "Use `write` instead -- it now works on slices." + )] + pub fn write_slice(&mut self, slice: &[T]) -> io::Result + where + T: AsStd140, + { + self.write(slice) + } + + /// Returns the amount of data written by this `Writer`. + pub fn len(&self) -> usize { + self.offset + } +} diff --git a/crates/bevy_crevice/src/std430.rs b/crates/bevy_crevice/src/std430.rs new file mode 100644 index 0000000000000..676c999556c11 --- /dev/null +++ b/crates/bevy_crevice/src/std430.rs @@ -0,0 +1,16 @@ +//! Defines traits and types for working with data adhering to GLSL's `std140` +//! layout specification. + +mod primitives; +mod sizer; +mod traits; +#[cfg(feature = "std")] +mod writer; + +pub use self::primitives::*; +pub use self::sizer::*; +pub use self::traits::*; +#[cfg(feature = "std")] +pub use self::writer::*; + +pub use bevy_crevice_derive::AsStd430; diff --git a/crates/bevy_crevice/src/std430/primitives.rs b/crates/bevy_crevice/src/std430/primitives.rs new file mode 100644 index 0000000000000..3348e82c7b2c6 --- /dev/null +++ b/crates/bevy_crevice/src/std430/primitives.rs @@ -0,0 +1,173 @@ +use bytemuck::{Pod, Zeroable}; + +use crate::glsl::Glsl; +use crate::std430::{Std430, Std430Padded}; + +use crate::internal::align_offset; +use core::mem::size_of; + +unsafe impl Std430 for f32 { + const ALIGNMENT: usize = 4; + type Padded = Self; +} + +unsafe impl Std430 for f64 { + const ALIGNMENT: usize = 8; + type Padded = Self; +} + +unsafe impl Std430 for i32 { + const ALIGNMENT: usize = 4; + type Padded = Self; +} + +unsafe impl Std430 for u32 { + const ALIGNMENT: usize = 4; + type Padded = Self; +} + +macro_rules! vectors { + ( + $( + #[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+) + )+ + ) => { + $( + #[$doc] + #[allow(missing_docs)] + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub struct $name { + $(pub $field: $prim,)+ + } + + unsafe impl Zeroable for $name {} + unsafe impl Pod for $name {} + + unsafe impl Std430 for $name { + const ALIGNMENT: usize = $align; + type Padded = Std430Padded(), $align)}>; + } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } + )+ + }; +} + +vectors! { + #[doc = "Corresponds to a GLSL `vec2` in std430 layout."] align(8) vec2 Vec2(x, y) + #[doc = "Corresponds to a GLSL `vec3` in std430 layout."] align(16) vec3 Vec3(x, y, z) + #[doc = "Corresponds to a GLSL `vec4` in std430 layout."] align(16) vec4 Vec4(x, y, z, w) + + #[doc = "Corresponds to a GLSL `ivec2` in std430 layout."] align(8) ivec2 IVec2(x, y) + #[doc = "Corresponds to a GLSL `ivec3` in std430 layout."] align(16) ivec3 IVec3(x, y, z) + #[doc = "Corresponds to a GLSL `ivec4` in std430 layout."] align(16) ivec4 IVec4(x, y, z, w) + + #[doc = "Corresponds to a GLSL `uvec2` in std430 layout."] align(8) uvec2 UVec2(x, y) + #[doc = "Corresponds to a GLSL `uvec3` in std430 layout."] align(16) uvec3 UVec3(x, y, z) + #[doc = "Corresponds to a GLSL `uvec4` in std430 layout."] align(16) uvec4 UVec4(x, y, z, w) + + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + + // #[doc = "Corresponds to a GLSL `bvec2` in std430 layout."] align(8) bvec2 BVec2(x, y) + // #[doc = "Corresponds to a GLSL `bvec3` in std430 layout."] align(16) bvec3 BVec3(x, y, z) + // #[doc = "Corresponds to a GLSL `bvec4` in std430 layout."] align(16) bvec4 BVec4(x, y, z, w) + + #[doc = "Corresponds to a GLSL `dvec2` in std430 layout."] align(16) dvec2 DVec2(x, y) + #[doc = "Corresponds to a GLSL `dvec3` in std430 layout."] align(32) dvec3 DVec3(x, y, z) + #[doc = "Corresponds to a GLSL `dvec4` in std430 layout."] align(32) dvec4 DVec4(x, y, z, w) +} + +macro_rules! matrices { + ( + $( + #[$doc:meta] + align($align:literal) + $glsl_name:ident $name:ident { + $($field:ident: $field_ty:ty,)+ + } + )+ + ) => { + $( + #[$doc] + #[allow(missing_docs)] + #[derive(Debug, Clone, Copy)] + #[repr(C)] + pub struct $name { + $(pub $field: $field_ty,)+ + } + + unsafe impl Zeroable for $name {} + unsafe impl Pod for $name {} + + unsafe impl Std430 for $name { + const ALIGNMENT: usize = $align; + /// Matrices are technically arrays of primitives, and as such require pad at end. + const PAD_AT_END: bool = true; + type Padded = Std430Padded(), $align)}>; + } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } + )+ + }; +} + +matrices! { + #[doc = "Corresponds to a GLSL `mat2` in std430 layout."] + align(8) + mat2 Mat2 { + x: Vec2, + y: Vec2, + } + + #[doc = "Corresponds to a GLSL `mat3` in std430 layout."] + align(16) + mat3 Mat3 { + x: Vec3, + _pad_x: f32, + y: Vec3, + _pad_y: f32, + z: Vec3, + _pad_z: f32, + } + + #[doc = "Corresponds to a GLSL `mat4` in std430 layout."] + align(16) + mat4 Mat4 { + x: Vec4, + y: Vec4, + z: Vec4, + w: Vec4, + } + + #[doc = "Corresponds to a GLSL `dmat2` in std430 layout."] + align(16) + dmat2 DMat2 { + x: DVec2, + y: DVec2, + } + + #[doc = "Corresponds to a GLSL `dmat3` in std430 layout."] + align(32) + dmat3 DMat3 { + x: DVec3, + _pad_x: f64, + y: DVec3, + _pad_y: f64, + z: DVec3, + _pad_z: f64, + } + + #[doc = "Corresponds to a GLSL `dmat3` in std430 layout."] + align(32) + dmat4 DMat4 { + x: DVec4, + y: DVec4, + z: DVec4, + w: DVec4, + } +} diff --git a/crates/bevy_crevice/src/std430/sizer.rs b/crates/bevy_crevice/src/std430/sizer.rs new file mode 100644 index 0000000000000..05203d5577d9c --- /dev/null +++ b/crates/bevy_crevice/src/std430/sizer.rs @@ -0,0 +1,81 @@ +use core::mem::size_of; + +use crate::internal::align_offset; +use crate::std430::{AsStd430, Std430}; + +/** +Type that computes the buffer size needed by a series of `std430` types laid +out. + +This type works well well when paired with `Writer`, precomputing a buffer's +size to alleviate the need to dynamically re-allocate buffers. + +## Example + +```glsl +struct Frob { + vec3 size; + float frobiness; +} + +buffer FROBS { + uint len; + Frob[] frobs; +} frobs; +``` + +``` +use bevy_crevice::std430::{self, AsStd430}; + +#[derive(AsStd430)] +struct Frob { + size: mint::Vector3, + frobiness: f32, +} + +// Many APIs require that buffers contain at least enough space for all +// fixed-size bindiongs to a buffer as well as one element of any arrays, if +// there are any. +let mut sizer = std430::Sizer::new(); +sizer.add::(); +sizer.add::(); + +# fn create_buffer_with_size(size: usize) {} +let buffer = create_buffer_with_size(sizer.len()); +# assert_eq!(sizer.len(), 32); +``` +*/ +pub struct Sizer { + offset: usize, +} + +impl Sizer { + /// Create a new `Sizer`. + pub fn new() -> Self { + Self { offset: 0 } + } + + /// Add a type's necessary padding and size to the `Sizer`. Returns the + /// offset into the buffer where that type would be written. + pub fn add(&mut self) -> usize + where + T: AsStd430, + { + let size = size_of::<::Output>(); + let alignment = ::Output::ALIGNMENT; + let padding = align_offset(self.offset, alignment); + + self.offset += padding; + let write_here = self.offset; + + self.offset += size; + + write_here + } + + /// Returns the number of bytes required to contain all the types added to + /// the `Sizer`. + pub fn len(&self) -> usize { + self.offset + } +} diff --git a/crates/bevy_crevice/src/std430/traits.rs b/crates/bevy_crevice/src/std430/traits.rs new file mode 100644 index 0000000000000..7f2967f3b421a --- /dev/null +++ b/crates/bevy_crevice/src/std430/traits.rs @@ -0,0 +1,283 @@ +use core::mem::{size_of, MaybeUninit}; +#[cfg(feature = "std")] +use std::io::{self, Write}; + +use bytemuck::{bytes_of, Pod, Zeroable}; + +#[cfg(feature = "std")] +use crate::std430::Writer; + +/// Trait implemented for all `std430` primitives. Generally should not be +/// implemented outside this crate. +pub unsafe trait Std430: Copy + Zeroable + Pod { + /// The required alignment of the type. Must be a power of two. + /// + /// This is distinct from the value returned by `std::mem::align_of` because + /// `AsStd430` structs do not use Rust's alignment. This enables them to + /// control and zero their padding bytes, making converting them to and from + /// slices safe. + const ALIGNMENT: usize; + + /// Whether this type requires a padding at the end (ie, is a struct or an array + /// of primitives). + /// See + /// (rule 4 and 9) + const PAD_AT_END: bool = false; + /// Padded type (Std430Padded specialization) + /// The usual implementation is + /// type Padded = Std430Padded(), ALIGNMENT)}>; + type Padded: Std430Convertible; + + /// Casts the type to a byte array. Implementors should not override this + /// method. + /// + /// # Safety + /// This is always safe due to the requirements of [`bytemuck::Pod`] being a + /// prerequisite for this trait. + fn as_bytes(&self) -> &[u8] { + bytes_of(self) + } +} + +/// Trait specifically for Std430::Padded, implements conversions between padded type and base type. +pub trait Std430Convertible: Copy { + /// Convert from self to Std430 + fn into_std430(self) -> T; + /// Convert from Std430 to self + fn from_std430(_: T) -> Self; +} + +impl Std430Convertible for T { + fn into_std430(self) -> T { + self + } + fn from_std430(also_self: T) -> Self { + also_self + } +} + +/// Unfortunately, we cannot easily derive padded representation for generic Std140 types. +/// For now, we'll just use this empty enum with no values. +#[derive(Copy, Clone)] +pub enum InvalidPadded {} +impl Std430Convertible for InvalidPadded { + fn into_std430(self) -> T { + unimplemented!() + } + fn from_std430(_: T) -> Self { + unimplemented!() + } +} +/** +Trait implemented for all types that can be turned into `std430` values. + +This trait can often be `#[derive]`'d instead of manually implementing it. Any +struct which contains only fields that also implement `AsStd430` can derive +`AsStd430`. + +Types from the mint crate implement `AsStd430`, making them convenient for use +in uniform types. Most Rust geometry crates, like cgmath, nalgebra, and +ultraviolet support mint. + +## Example + +```glsl +uniform CAMERA { + mat4 view; + mat4 projection; +} camera; +``` + +```no_run +use bevy_crevice::std430::{AsStd430, Std430}; + +#[derive(AsStd430)] +struct CameraUniform { + view: mint::ColumnMatrix4, + projection: mint::ColumnMatrix4, +} + +let view: mint::ColumnMatrix4 = todo!("your math code here"); +let projection: mint::ColumnMatrix4 = todo!("your math code here"); + +let camera = CameraUniform { + view, + projection, +}; + +# fn write_to_gpu_buffer(bytes: &[u8]) {} +let camera_std430 = camera.as_std430(); +write_to_gpu_buffer(camera_std430.as_bytes()); +``` +*/ +pub trait AsStd430 { + /// The `std430` version of this value. + type Output: Std430; + + /// Convert this value into the `std430` version of itself. + fn as_std430(&self) -> Self::Output; + + /// Returns the size of the `std430` version of this type. Useful for + /// pre-sizing buffers. + fn std430_size_static() -> usize { + size_of::() + } + + /// Converts from `std430` version of self to self. + fn from_std430(value: Self::Output) -> Self; +} + +impl AsStd430 for T +where + T: Std430, +{ + type Output = Self; + + fn as_std430(&self) -> Self { + *self + } + + fn from_std430(value: Self) -> Self { + value + } +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug)] +pub struct Std430Padded { + inner: T, + _padding: [u8; PAD], +} + +unsafe impl Zeroable for Std430Padded {} +unsafe impl Pod for Std430Padded {} + +impl Std430Convertible for Std430Padded { + fn into_std430(self) -> T { + self.inner + } + + fn from_std430(inner: T) -> Self { + Self { + inner, + _padding: [0u8; PAD], + } + } +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct Std430Array([T::Padded; N]); + +unsafe impl Zeroable for Std430Array where T::Padded: Zeroable {} +unsafe impl Pod for Std430Array where T::Padded: Pod {} +unsafe impl Std430 for Std430Array +where + T::Padded: Pod, +{ + const ALIGNMENT: usize = T::ALIGNMENT; + type Padded = Self; +} + +impl Std430Array { + fn uninit_array() -> [MaybeUninit; N] { + unsafe { MaybeUninit::uninit().assume_init() } + } + + fn from_uninit_array(a: [MaybeUninit; N]) -> Self { + unsafe { core::mem::transmute_copy(&a) } + } +} + +impl AsStd430 for [T; N] +where + ::Padded: Pod, +{ + type Output = Std430Array; + fn as_std430(&self) -> Self::Output { + let mut res = Self::Output::uninit_array(); + + for i in 0..N { + res[i] = MaybeUninit::new(Std430Convertible::from_std430(self[i].as_std430())); + } + + Self::Output::from_uninit_array(res) + } + + fn from_std430(val: Self::Output) -> Self { + let mut res: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + for i in 0..N { + res[i] = MaybeUninit::new(T::from_std430(val.0[i].into_std430())); + } + unsafe { core::mem::transmute_copy(&res) } + } +} + +/// Trait implemented for all types that can be written into a buffer as +/// `std430` bytes. This type is more general than [`AsStd430`]: all `AsStd430` +/// types implement `WriteStd430`, but not the other way around. +/// +/// While `AsStd430` requires implementers to return a type that implements the +/// `Std430` trait, `WriteStd430` directly writes bytes using a [`Writer`]. This +/// makes `WriteStd430` usable for writing slices or other DSTs that could not +/// implement `AsStd430` without allocating new memory on the heap. +#[cfg(feature = "std")] +pub trait WriteStd430 { + /// Writes this value into the given [`Writer`] using `std430` layout rules. + /// + /// Should return the offset of the first byte of this type, as returned by + /// the first call to [`Writer::write`]. + fn write_std430(&self, writer: &mut Writer) -> io::Result; + + /// The space required to write this value using `std430` layout rules. This + /// does not include alignment padding that may be needed before or after + /// this type when written as part of a larger buffer. + fn std430_size(&self) -> usize { + let mut writer = Writer::new(io::sink()); + self.write_std430(&mut writer).unwrap(); + writer.len() + } +} + +#[cfg(feature = "std")] +impl WriteStd430 for T +where + T: AsStd430, +{ + fn write_std430(&self, writer: &mut Writer) -> io::Result { + writer.write_std430(&self.as_std430()) + } + + fn std430_size(&self) -> usize { + size_of::<::Output>() + } +} + +#[cfg(feature = "std")] +impl WriteStd430 for [T] +where + T: WriteStd430, +{ + fn write_std430(&self, writer: &mut Writer) -> io::Result { + let mut offset = writer.len(); + + let mut iter = self.iter(); + + if let Some(item) = iter.next() { + offset = item.write_std430(writer)?; + } + + for item in iter { + item.write_std430(writer)?; + } + + Ok(offset) + } + + fn std430_size(&self) -> usize { + let mut writer = Writer::new(io::sink()); + self.write_std430(&mut writer).unwrap(); + writer.len() + } +} diff --git a/crates/bevy_crevice/src/std430/writer.rs b/crates/bevy_crevice/src/std430/writer.rs new file mode 100644 index 0000000000000..199ab3ab50abc --- /dev/null +++ b/crates/bevy_crevice/src/std430/writer.rs @@ -0,0 +1,150 @@ +use std::io::{self, Write}; +use std::mem::size_of; + +use bytemuck::bytes_of; + +use crate::internal::align_offset; +use crate::std430::{AsStd430, Std430, WriteStd430}; + +/** +Type that enables writing correctly aligned `std430` values to a buffer. + +`Writer` is useful when many values need to be laid out in a row that cannot be +represented by a struct alone, like dynamically sized arrays or dynamically +laid-out values. + +## Example +In this example, we'll write a length-prefixed list of lights to a buffer. +`std430::Writer` helps align correctly, even across multiple structs, which can +be tricky and error-prone otherwise. + +```glsl +struct PointLight { + vec3 position; + vec3 color; + float brightness; +}; + +buffer POINT_LIGHTS { + uint len; + PointLight[] lights; +} point_lights; +``` + +``` +use bevy_crevice::std430::{self, AsStd430}; + +#[derive(AsStd430)] +struct PointLight { + position: mint::Vector3, + color: mint::Vector3, + brightness: f32, +} + +let lights = vec![ + PointLight { + position: [0.0, 1.0, 0.0].into(), + color: [1.0, 0.0, 0.0].into(), + brightness: 0.6, + }, + PointLight { + position: [0.0, 4.0, 3.0].into(), + color: [1.0, 1.0, 1.0].into(), + brightness: 1.0, + }, +]; + +# fn map_gpu_buffer_for_write() -> &'static mut [u8] { +# Box::leak(vec![0; 1024].into_boxed_slice()) +# } +let target_buffer = map_gpu_buffer_for_write(); +let mut writer = std430::Writer::new(target_buffer); + +let light_count = lights.len() as u32; +writer.write(&light_count)?; + +// Crevice will automatically insert the required padding to align the +// PointLight structure correctly. In this case, there will be 12 bytes of +// padding between the length field and the light list. + +writer.write(lights.as_slice())?; + +# fn unmap_gpu_buffer() {} +unmap_gpu_buffer(); + +# Ok::<(), std::io::Error>(()) +``` +*/ +pub struct Writer { + writer: W, + offset: usize, +} + +impl Writer { + /// Create a new `Writer`, wrapping a buffer, file, or other type that + /// implements [`std::io::Write`]. + pub fn new(writer: W) -> Self { + Self { writer, offset: 0 } + } + + /// Write a new value to the underlying buffer, writing zeroed padding where + /// necessary. + /// + /// Returns the offset into the buffer that the value was written to. + pub fn write(&mut self, value: &T) -> io::Result + where + T: WriteStd430 + ?Sized, + { + value.write_std430(self) + } + + /// Write an iterator of values to the underlying buffer. + /// + /// Returns the offset into the buffer that the first value was written to. + /// If no values were written, returns the `len()`. + pub fn write_iter(&mut self, iter: I) -> io::Result + where + I: IntoIterator, + T: WriteStd430, + { + let mut offset = self.offset; + + let mut iter = iter.into_iter(); + + if let Some(item) = iter.next() { + offset = item.write_std430(self)?; + } + + for item in iter { + item.write_std430(self)?; + } + + Ok(offset) + } + + /// Write an `Std430` type to the underlying buffer. + pub fn write_std430(&mut self, value: &T) -> io::Result + where + T: Std430, + { + let padding = align_offset(self.offset, T::ALIGNMENT); + + for _ in 0..padding { + self.writer.write_all(&[0])?; + } + self.offset += padding; + + let value = value.as_std430(); + self.writer.write_all(bytes_of(&value))?; + + let write_here = self.offset; + self.offset += size_of::(); + + Ok(write_here) + } + + /// Returns the amount of data written by this `Writer`. + pub fn len(&self) -> usize { + self.offset + } +} diff --git a/crates/bevy_crevice/src/util.rs b/crates/bevy_crevice/src/util.rs new file mode 100644 index 0000000000000..9c6c2a396450d --- /dev/null +++ b/crates/bevy_crevice/src/util.rs @@ -0,0 +1,97 @@ +#![allow(unused_macros)] + +macro_rules! easy_impl { + ( $( $std_name:ident $imp_ty:ty { $($field:ident),* }, )* ) => { + $( + impl crate::std140::AsStd140 for $imp_ty { + type Output = crate::std140::$std_name; + + #[inline] + fn as_std140(&self) -> Self::Output { + crate::std140::$std_name { + $( + $field: self.$field.as_std140(), + )* + ..bytemuck::Zeroable::zeroed() + } + } + + #[inline] + fn from_std140(value: Self::Output) -> Self { + Self { + $( + $field: <_ as crate::std140::AsStd140>::from_std140(value.$field), + )* + } + } + } + + impl crate::std430::AsStd430 for $imp_ty { + type Output = crate::std430::$std_name; + + #[inline] + fn as_std430(&self) -> Self::Output { + crate::std430::$std_name { + $( + $field: self.$field.as_std430(), + )* + ..bytemuck::Zeroable::zeroed() + } + } + + #[inline] + fn from_std430(value: Self::Output) -> Self { + Self { + $( + $field: <_ as crate::std430::AsStd430>::from_std430(value.$field), + )* + } + } + } + + unsafe impl crate::glsl::Glsl for $imp_ty { + const NAME: &'static str = crate::std140::$std_name::NAME; + } + )* + }; +} + +macro_rules! minty_impl { + ( $( $mint_ty:ty => $imp_ty:ty, )* ) => { + $( + impl crate::std140::AsStd140 for $imp_ty { + type Output = <$mint_ty as crate::std140::AsStd140>::Output; + + #[inline] + fn as_std140(&self) -> Self::Output { + let mint: $mint_ty = (*self).into(); + mint.as_std140() + } + + #[inline] + fn from_std140(value: Self::Output) -> Self { + <$mint_ty>::from_std140(value).into() + } + } + + impl crate::std430::AsStd430 for $imp_ty { + type Output = <$mint_ty as crate::std430::AsStd430>::Output; + + #[inline] + fn as_std430(&self) -> Self::Output { + let mint: $mint_ty = (*self).into(); + mint.as_std430() + } + + #[inline] + fn from_std430(value: Self::Output) -> Self { + <$mint_ty>::from_std430(value).into() + } + } + + unsafe impl crate::glsl::Glsl for $imp_ty { + const NAME: &'static str = <$mint_ty>::NAME; + } + )* + }; +} diff --git a/crates/bevy_crevice/tests/snapshots/test__generate_struct_array_glsl.snap b/crates/bevy_crevice/tests/snapshots/test__generate_struct_array_glsl.snap new file mode 100644 index 0000000000000..7829bd64ca141 --- /dev/null +++ b/crates/bevy_crevice/tests/snapshots/test__generate_struct_array_glsl.snap @@ -0,0 +1,8 @@ +--- +source: tests/test.rs +expression: "TestGlsl::glsl_definition()" + +--- +struct TestGlsl { + vec3 foo[8][4]; +}; diff --git a/crates/bevy_crevice/tests/snapshots/test__generate_struct_glsl.snap b/crates/bevy_crevice/tests/snapshots/test__generate_struct_glsl.snap new file mode 100644 index 0000000000000..42fc1f4cd770e --- /dev/null +++ b/crates/bevy_crevice/tests/snapshots/test__generate_struct_glsl.snap @@ -0,0 +1,9 @@ +--- +source: tests/test.rs +expression: "TestGlsl::glsl_definition()" + +--- +struct TestGlsl { + vec3 foo; + mat2 bar; +}; diff --git a/crates/bevy_crevice/tests/test.rs b/crates/bevy_crevice/tests/test.rs new file mode 100644 index 0000000000000..693ce080c7f12 --- /dev/null +++ b/crates/bevy_crevice/tests/test.rs @@ -0,0 +1,61 @@ +use bevy_crevice::glsl::GlslStruct; +use bevy_crevice::std140::AsStd140; + +#[test] +fn there_and_back_again() { + #[derive(AsStd140, Debug, PartialEq)] + struct ThereAndBackAgain { + view: mint::ColumnMatrix3, + origin: mint::Vector3, + } + + let x = ThereAndBackAgain { + view: mint::ColumnMatrix3 { + x: mint::Vector3 { + x: 1.0, + y: 0.0, + z: 0.0, + }, + y: mint::Vector3 { + x: 0.0, + y: 1.0, + z: 0.0, + }, + z: mint::Vector3 { + x: 0.0, + y: 0.0, + z: 1.0, + }, + }, + origin: mint::Vector3 { + x: 0.0, + y: 1.0, + z: 2.0, + }, + }; + let x_as = x.as_std140(); + assert_eq!(::from_std140(x_as), x); +} + +#[test] +fn generate_struct_glsl() { + #[allow(dead_code)] + #[derive(GlslStruct)] + struct TestGlsl { + foo: mint::Vector3, + bar: mint::ColumnMatrix2, + } + + insta::assert_display_snapshot!(TestGlsl::glsl_definition()); +} + +#[test] +fn generate_struct_array_glsl() { + #[allow(dead_code)] + #[derive(GlslStruct)] + struct TestGlsl { + foo: [[mint::Vector3; 8]; 4], + } + + insta::assert_display_snapshot!(TestGlsl::glsl_definition()); +} diff --git a/crates/bevy_derive/Cargo.toml b/crates/bevy_derive/Cargo.toml index af16371669dd2..678dafa5ad197 100644 --- a/crates/bevy_derive/Cargo.toml +++ b/crates/bevy_derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_derive" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Provides derive implementations for Bevy Engine" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -12,8 +12,7 @@ keywords = ["bevy"] proc-macro = true [dependencies] -bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.5.0" } +bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.6.0" } -Inflector = { version = "0.11.4", default-features = false } quote = "1.0" syn = "1.0" diff --git a/crates/bevy_derive/src/app_plugin.rs b/crates/bevy_derive/src/app_plugin.rs index 84b5bdf168a7b..56b5d1f0450eb 100644 --- a/crates/bevy_derive/src/app_plugin.rs +++ b/crates/bevy_derive/src/app_plugin.rs @@ -8,7 +8,7 @@ pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream { TokenStream::from(quote! { #[no_mangle] - pub extern "C" fn _bevy_create_plugin() -> *mut bevy::app::Plugin { + pub extern "C" fn _bevy_create_plugin() -> *mut dyn bevy::app::Plugin { // make sure the constructor is the correct type. let object = #struct_name {}; let boxed = Box::new(object); diff --git a/crates/bevy_derive/src/bytes.rs b/crates/bevy_derive/src/bytes.rs deleted file mode 100644 index 453ade9ed3ec1..0000000000000 --- a/crates/bevy_derive/src/bytes.rs +++ /dev/null @@ -1,41 +0,0 @@ -use bevy_macro_utils::BevyManifest; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields}; - -pub fn derive_bytes(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let fields = match &ast.data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => &fields.named, - _ => panic!("Expected a struct with named fields."), - }; - - let bevy_core_path = BevyManifest::default().get_path(crate::modules::BEVY_CORE); - - let fields = fields - .iter() - .map(|field| field.ident.as_ref().unwrap()) - .collect::>(); - - let struct_name = &ast.ident; - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - - TokenStream::from(quote! { - impl #impl_generics #bevy_core_path::Bytes for #struct_name #ty_generics #where_clause { - fn write_bytes(&self, buffer: &mut [u8]) { - let mut offset: usize = 0; - #(let byte_len = self.#fields.byte_len(); - self.#fields.write_bytes(&mut buffer[offset..(offset + byte_len)]); - offset += byte_len;)* - } - fn byte_len(&self) -> usize { - let mut byte_len: usize = 0; - #(byte_len += self.#fields.byte_len();)* - byte_len - } - } - }) -} diff --git a/crates/bevy_derive/src/enum_variant_meta.rs b/crates/bevy_derive/src/enum_variant_meta.rs index 66ed1f0ff7d2a..415c6f291c354 100644 --- a/crates/bevy_derive/src/enum_variant_meta.rs +++ b/crates/bevy_derive/src/enum_variant_meta.rs @@ -1,5 +1,5 @@ use bevy_macro_utils::BevyManifest; -use proc_macro::TokenStream; +use proc_macro::{Span, TokenStream}; use quote::quote; use syn::{parse_macro_input, Data, DeriveInput}; @@ -7,7 +7,11 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let variants = match &ast.data { Data::Enum(v) => &v.variants, - _ => panic!("Expected an enum."), + _ => { + return syn::Error::new(Span::call_site().into(), "Only enums are supported") + .into_compile_error() + .into() + } }; let bevy_util_path = BevyManifest::default().get_path(crate::modules::BEVY_UTILS); @@ -21,7 +25,7 @@ pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream { let indices = 0..names.len(); TokenStream::from(quote! { - impl #impl_generics #bevy_util_path::EnumVariantMeta for #struct_name#ty_generics #where_clause { + impl #impl_generics #bevy_util_path::EnumVariantMeta for #struct_name #ty_generics #where_clause { fn enum_variant_index(&self) -> usize { match self { #(#struct_name::#idents {..} => #indices,)* diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index edbbf6f1ab2a4..98990c11c85ef 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -2,40 +2,12 @@ extern crate proc_macro; mod app_plugin; mod bevy_main; -mod bytes; mod enum_variant_meta; mod modules; -mod render_resource; -mod render_resources; -mod shader_defs; +use bevy_macro_utils::{derive_label, BevyManifest}; use proc_macro::TokenStream; - -/// Derives the Bytes trait. Each field must also implements Bytes or this will fail. -#[proc_macro_derive(Bytes)] -pub fn derive_bytes(input: TokenStream) -> TokenStream { - bytes::derive_bytes(input) -} - -/// Derives the RenderResources trait. Each field must implement RenderResource or this will fail. -/// You can ignore fields using `#[render_resources(ignore)]`. -#[proc_macro_derive(RenderResources, attributes(render_resources))] -pub fn derive_render_resources(input: TokenStream) -> TokenStream { - render_resources::derive_render_resources(input) -} - -/// Derives the RenderResource trait. The type must also implement `Bytes` or this will fail. -#[proc_macro_derive(RenderResource)] -pub fn derive_render_resource(input: TokenStream) -> TokenStream { - render_resource::derive_render_resource(input) -} - -/// Derives the ShaderDefs trait. Each field must implement ShaderDef or this will fail. -/// You can ignore fields using `#[shader_defs(ignore)]`. -#[proc_macro_derive(ShaderDefs, attributes(shader_def))] -pub fn derive_shader_defs(input: TokenStream) -> TokenStream { - shader_defs::derive_shader_defs(input) -} +use quote::format_ident; /// Generates a dynamic plugin entry point function for the given `Plugin` type. #[proc_macro_derive(DynamicPlugin)] @@ -52,3 +24,11 @@ pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream { enum_variant_meta::derive_enum_variant_meta(input) } + +#[proc_macro_derive(AppLabel)] +pub fn derive_app_label(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + let mut trait_path = BevyManifest::default().get_path("bevy_app"); + trait_path.segments.push(format_ident!("AppLabel").into()); + derive_label(input, trait_path) +} diff --git a/crates/bevy_derive/src/modules.rs b/crates/bevy_derive/src/modules.rs index 0191ff907bf9e..14d68e7051088 100644 --- a/crates/bevy_derive/src/modules.rs +++ b/crates/bevy_derive/src/modules.rs @@ -1,4 +1 @@ -pub const BEVY_ASSET: &str = "bevy_asset"; -pub const BEVY_CORE: &str = "bevy_core"; -pub const BEVY_RENDER: &str = "bevy_render"; pub const BEVY_UTILS: &str = "bevy_utils"; diff --git a/crates/bevy_derive/src/render_resource.rs b/crates/bevy_derive/src/render_resource.rs deleted file mode 100644 index e39ef3db37e27..0000000000000 --- a/crates/bevy_derive/src/render_resource.rs +++ /dev/null @@ -1,35 +0,0 @@ -use bevy_macro_utils::BevyManifest; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput, Path}; - -pub fn derive_render_resource(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let manifest = BevyManifest::default(); - - let bevy_render_path: Path = manifest.get_path(crate::modules::BEVY_RENDER); - let bevy_asset_path: Path = manifest.get_path(crate::modules::BEVY_ASSET); - let bevy_core_path: Path = manifest.get_path(crate::modules::BEVY_CORE); - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - - TokenStream::from(quote! { - impl #impl_generics #bevy_render_path::renderer::RenderResource for #struct_name #type_generics #where_clause { - fn resource_type(&self) -> Option<#bevy_render_path::renderer::RenderResourceType> { - Some(#bevy_render_path::renderer::RenderResourceType::Buffer) - } - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - use #bevy_core_path::Bytes; - self.write_bytes(buffer); - } - fn buffer_byte_len(&self) -> Option { - use #bevy_core_path::Bytes; - Some(self.byte_len()) - } - fn texture(&self) -> Option<&#bevy_asset_path::Handle<#bevy_render_path::texture::Texture>> { - None - } - - } - }) -} diff --git a/crates/bevy_derive/src/render_resources.rs b/crates/bevy_derive/src/render_resources.rs deleted file mode 100644 index 7977e2ae1cf34..0000000000000 --- a/crates/bevy_derive/src/render_resources.rs +++ /dev/null @@ -1,183 +0,0 @@ -use bevy_macro_utils::BevyManifest; -use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::{ - parse::ParseStream, parse_macro_input, punctuated::Punctuated, Data, DataStruct, DeriveInput, - Field, Fields, Path, -}; - -#[derive(Default)] -struct RenderResourceFieldAttributes { - pub ignore: bool, - pub buffer: bool, -} - -#[derive(Default)] -struct RenderResourceAttributes { - pub from_self: bool, -} - -static RENDER_RESOURCE_ATTRIBUTE_NAME: &str = "render_resources"; - -pub fn derive_render_resources(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - - let bevy_render_path: Path = BevyManifest::default().get_path(crate::modules::BEVY_RENDER); - let attributes = ast - .attrs - .iter() - .find(|a| *a.path.get_ident().as_ref().unwrap() == RENDER_RESOURCE_ATTRIBUTE_NAME) - .map_or_else(RenderResourceAttributes::default, |a| { - syn::custom_keyword!(from_self); - let mut attributes = RenderResourceAttributes::default(); - a.parse_args_with(|input: ParseStream| { - if input.parse::>()?.is_some() { - attributes.from_self = true; - } - Ok(()) - }) - .expect("Invalid 'render_resources' attribute format."); - - attributes - }); - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let struct_name_string = struct_name.to_string(); - - if attributes.from_self { - TokenStream::from(quote! { - impl #impl_generics #bevy_render_path::renderer::RenderResources for #struct_name #type_generics #where_clause { - fn render_resources_len(&self) -> usize { - 1 - } - - fn get_render_resource(&self, index: usize) -> Option<&dyn #bevy_render_path::renderer::RenderResource> { - if index == 0 { - Some(self) - } else { - None - } - } - - fn get_render_resource_name(&self, index: usize) -> Option<&str> { - if index == 0 { - Some(#struct_name_string) - } else { - None - } - } - - fn iter(&self) -> #bevy_render_path::renderer::RenderResourceIterator { - #bevy_render_path::renderer::RenderResourceIterator::new(self) - } - } - }) - } else { - let empty = Punctuated::new(); - - let fields = match &ast.data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => &fields.named, - Data::Struct(DataStruct { - fields: Fields::Unit, - .. - }) => &empty, - _ => panic!("Expected a struct with named fields."), - }; - let field_attributes = fields - .iter() - .map(|field| { - ( - field, - field - .attrs - .iter() - .find(|a| { - *a.path.get_ident().as_ref().unwrap() == RENDER_RESOURCE_ATTRIBUTE_NAME - }) - .map_or_else(RenderResourceFieldAttributes::default, |a| { - syn::custom_keyword!(ignore); - syn::custom_keyword!(buffer); - let mut attributes = RenderResourceFieldAttributes::default(); - a.parse_args_with(|input: ParseStream| { - if input.parse::>()?.is_some() { - attributes.ignore = true; - } else if input.parse::>()?.is_some() { - attributes.buffer = true; - } - Ok(()) - }) - .expect("Invalid 'render_resources' attribute format."); - - attributes - }), - ) - }) - .collect::>(); - let mut render_resource_names = Vec::new(); - let mut render_resource_fields = Vec::new(); - let mut render_resource_hints = Vec::new(); - for (field, attrs) in field_attributes.iter() { - if attrs.ignore { - continue; - } - - let field_ident = field.ident.as_ref().unwrap(); - let field_name = field_ident.to_string(); - render_resource_fields.push(field_ident); - render_resource_names.push(format!("{}_{}", struct_name, field_name)); - if attrs.buffer { - render_resource_hints - .push(quote! {Some(#bevy_render_path::renderer::RenderResourceHints::BUFFER)}) - } else { - render_resource_hints.push(quote! {None}) - } - } - - let render_resource_count = render_resource_names.len(); - let render_resource_indices = 0..render_resource_count; - - let struct_name_uppercase = struct_name_string.to_uppercase(); - let render_resource_names_ident = - format_ident!("{}_RENDER_RESOURCE_NAMES", struct_name_uppercase); - let render_resource_hints_ident = - format_ident!("{}_RENDER_RESOURCE_HINTS", struct_name_uppercase); - - TokenStream::from(quote! { - static #render_resource_names_ident: &[&str] = &[ - #(#render_resource_names,)* - ]; - - static #render_resource_hints_ident: &[Option<#bevy_render_path::renderer::RenderResourceHints>] = &[ - #(#render_resource_hints,)* - ]; - - impl #impl_generics #bevy_render_path::renderer::RenderResources for #struct_name #type_generics #where_clause { - fn render_resources_len(&self) -> usize { - #render_resource_count - } - - fn get_render_resource(&self, index: usize) -> Option<&dyn #bevy_render_path::renderer::RenderResource> { - match index { - #(#render_resource_indices => Some(&self.#render_resource_fields),)* - _ => None, - } - } - - fn get_render_resource_name(&self, index: usize) -> Option<&str> { - #render_resource_names_ident.get(index).copied() - } - - fn get_render_resource_hints(&self, index: usize) -> Option<#bevy_render_path::renderer::RenderResourceHints> { - #render_resource_hints_ident.get(index).and_then(|o| *o) - } - - fn iter(&self) -> #bevy_render_path::renderer::RenderResourceIterator { - #bevy_render_path::renderer::RenderResourceIterator::new(self) - } - } - }) - } -} diff --git a/crates/bevy_derive/src/shader_defs.rs b/crates/bevy_derive/src/shader_defs.rs deleted file mode 100644 index 4596667b6a1a4..0000000000000 --- a/crates/bevy_derive/src/shader_defs.rs +++ /dev/null @@ -1,65 +0,0 @@ -use bevy_macro_utils::BevyManifest; -use inflector::Inflector; -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Path}; - -static SHADER_DEF_ATTRIBUTE_NAME: &str = "shader_def"; - -pub fn derive_shader_defs(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let bevy_render_path: Path = BevyManifest::default().get_path(crate::modules::BEVY_RENDER); - - let fields = match &ast.data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => &fields.named, - _ => panic!("Expected a struct with named fields."), - }; - - let shader_def_idents = fields - .iter() - .filter(|f| { - f.attrs - .iter() - .any(|a| *a.path.get_ident().as_ref().unwrap() == SHADER_DEF_ATTRIBUTE_NAME) - }) - .map(|f| f.ident.as_ref().unwrap()) - .collect::>(); - let struct_name = &ast.ident; - let struct_name_pascal_case = ast.ident.to_string().to_pascal_case(); - let shader_defs = shader_def_idents - .iter() - .map(|i| format!("{}_{}", struct_name_pascal_case, i.to_string()).to_uppercase()); - - let shader_defs_len = shader_defs.len(); - let shader_def_indices = 0..shader_defs_len; - - let generics = ast.generics; - let (impl_generics, ty_generics, _where_clause) = generics.split_for_impl(); - - TokenStream::from(quote! { - impl #impl_generics #bevy_render_path::shader::ShaderDefs for #struct_name#ty_generics { - fn shader_defs_len(&self) -> usize { - #shader_defs_len - } - - fn get_shader_def(&self, index: usize) -> Option<&str> { - use #bevy_render_path::shader::ShaderDef; - match index { - #(#shader_def_indices => if self.#shader_def_idents.is_defined() { - Some(#shader_defs) - } else { - None - },)* - _ => None, - } - } - - fn iter_shader_defs(&self) -> #bevy_render_path::shader::ShaderDefIterator { - #bevy_render_path::shader::ShaderDefIterator::new(self) - } - } - }) -} diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index b2e26c79f5187..1c3ac4e79369a 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_diagnostic" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Provides diagnostic functionality for Bevy Engine" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -11,8 +11,8 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.5.0" } -bevy_core = { path = "../bevy_core", version = "0.5.0" } -bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } -bevy_log = { path = "../bevy_log", version = "0.5.0" } -bevy_utils = { path = "../bevy_utils", version = "0.5.0" } +bevy_app = { path = "../bevy_app", version = "0.6.0" } +bevy_core = { path = "../bevy_core", version = "0.6.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.6.0" } +bevy_log = { path = "../bevy_log", version = "0.6.0" } +bevy_utils = { path = "../bevy_utils", version = "0.6.0" } diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index 7d8cfaebcacd7..05b08045d104e 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -27,7 +27,7 @@ pub struct DiagnosticMeasurement { pub value: f64, } -/// A timeline of [DiagnosticMeasurement]s of a specific type. +/// A timeline of [`DiagnosticMeasurement`]s of a specific type. /// Diagnostic examples: frames per second, CPU usage, network latency #[derive(Debug)] pub struct Diagnostic { @@ -43,14 +43,14 @@ impl Diagnostic { pub fn add_measurement(&mut self, value: f64) { let time = Instant::now(); if self.history.len() == self.max_history_length { - if let Some(removed_diagnostic) = self.history.pop_back() { + if let Some(removed_diagnostic) = self.history.pop_front() { self.sum -= removed_diagnostic.value; } } self.sum += value; self.history - .push_front(DiagnosticMeasurement { time, value }); + .push_back(DiagnosticMeasurement { time, value }); } pub fn new( @@ -82,8 +82,13 @@ impl Diagnostic { self } + #[inline] + pub fn measurement(&self) -> Option<&DiagnosticMeasurement> { + self.history.back() + } + pub fn value(&self) -> Option { - self.history.back().map(|measurement| measurement.value) + self.measurement().map(|measurement| measurement.value) } pub fn sum(&self) -> f64 { @@ -107,8 +112,8 @@ impl Diagnostic { return None; } - if let Some(oldest) = self.history.back() { - if let Some(newest) = self.history.front() { + if let Some(newest) = self.history.back() { + if let Some(oldest) = self.history.front() { return Some(newest.time.duration_since(oldest.time)); } } @@ -153,7 +158,7 @@ impl Diagnostics { pub fn get_measurement(&self, id: DiagnosticId) -> Option<&DiagnosticMeasurement> { self.diagnostics .get(&id) - .and_then(|diagnostic| diagnostic.history.front()) + .and_then(|diagnostic| diagnostic.measurement()) } pub fn add_measurement(&mut self, id: DiagnosticId, value: f64) { diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index e4054b20b9884..571b050a1d877 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -12,7 +12,7 @@ pub struct LogDiagnosticsPlugin { pub filter: Option>, } -/// State used by the [LogDiagnosticsPlugin] +/// State used by the [`LogDiagnosticsPlugin`] struct LogDiagnosticsState { timer: Timer, filter: Option>, @@ -56,22 +56,24 @@ impl LogDiagnosticsPlugin { if let Some(average) = diagnostic.average() { info!( target: "bevy diagnostic", - "{:12} (avg {:>})", - diagnostic.name, // Suffix is only used for 's' as in seconds currently, - // so we reserve one column for it - format!("{:.6}{:1}", value, diagnostic.suffix), + // so we reserve one column for it; however, // Do not reserve one column for the suffix in the average // The ) hugging the value is more aesthetically pleasing - format!("{:.6}{:}", average, diagnostic.suffix), + "{name:11.6}{suffix:1} (avg {average:>.6}{suffix:})", + name = diagnostic.name, + value = value, + suffix = diagnostic.suffix, + average = average, name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH, ); } else { info!( target: "bevy diagnostic", - "{:}", - diagnostic.name, - format!("{:.6}{:}", value, diagnostic.suffix), + "{name:.6}{suffix:}", + name = diagnostic.name, + value = value, + suffix = diagnostic.suffix, name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH, ); } diff --git a/crates/bevy_dylib/Cargo.toml b/crates/bevy_dylib/Cargo.toml index c1aa74ecd8910..36c49929cef89 100644 --- a/crates/bevy_dylib/Cargo.toml +++ b/crates/bevy_dylib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_dylib" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Force the Bevy Engine to be dynamically linked for faster linking" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -12,4 +12,4 @@ keywords = ["bevy"] crate-type = ["dylib"] [dependencies] -bevy_internal = { path = "../bevy_internal", version = "0.5.0", default-features = false } +bevy_internal = { path = "../bevy_internal", version = "0.6.0", default-features = false } diff --git a/crates/bevy_dylib/src/lib.rs b/crates/bevy_dylib/src/lib.rs index b39929bea6c71..c8a860d62baa1 100644 --- a/crates/bevy_dylib/src/lib.rs +++ b/crates/bevy_dylib/src/lib.rs @@ -1,12 +1,54 @@ +#![warn(missing_docs)] #![allow(clippy::single_component_path_imports)] //! Forces dynamic linking of Bevy. //! -//! Dynamically linking Bevy makes the "link" step much faster. This can be achieved by adding -//! `bevy_dylib` as dependency and `#[allow(unused_imports)] use bevy_dylib` to `main.rs`. It is -//! recommended to disable the `bevy_dylib` dependency in release mode by adding -//! `#[cfg(debug_assertions)]` to the `use` statement. Otherwise you will have to ship `libstd.so` -//! and `libbevy_dylib.so` with your game. +//! Dynamic linking causes Bevy to be built and linked as a dynamic library. This will make +//! incremental builds compile much faster. +//! +//! # Warning +//! +//! Do not enable this feature for release builds because this would require you to ship +//! `libstd.so` and `libbevy_dylib.so` with your game. +//! +//! # Enabling dynamic linking +//! +//! ## The recommended way +//! +//! The easiest way to enable dynamic linking is to use the `--features bevy/dynamic` flag when +//! using the `cargo run` command: +//! +//! `cargo run --features bevy/dynamic` +//! +//! ## The unrecommended way +//! +//! It is also possible to enable the `dynamic` feature inside of the `Cargo.toml` file. This is +//! unrecommended because it requires you to remove this feature every time you want to create a +//! release build to avoid having to ship additional files with your game. +//! +//! To enable dynamic linking inside of the `Cargo.toml` file add the `dynamic` feature to the +//! bevy dependency: +//! +//! `features = ["dynamic"]` +//! +//! ## The manual way +//! +//! Manually enabling dynamic linking is achieved by adding `bevy_dylib` as a dependency and +//! adding the following code to the `main.rs` file: +//! +//! ``` +//! #[allow(unused_imports)] +//! use bevy_dylib; +//! ``` +//! +//! It is recommended to disable the `bevy_dylib` dependency in release mode by adding the +//! following code to the `use` statement to avoid having to ship additional files with your game: +//! +//! ``` +//! #[allow(unused_imports)] +//! #[cfg(debug_assertions)] // new +//! use bevy_dylib; +//! ``` // Force linking of the main bevy crate #[allow(unused_imports)] diff --git a/crates/bevy_dynamic_plugin/Cargo.toml b/crates/bevy_dynamic_plugin/Cargo.toml index e1b17258d3f44..05e462e2c41f7 100644 --- a/crates/bevy_dynamic_plugin/Cargo.toml +++ b/crates/bevy_dynamic_plugin/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_dynamic_plugin" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Provides dynamic plugin loading capabilities for non-wasm platforms" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -12,7 +12,7 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../bevy_app", version = "0.5.0" } +bevy_app = { path = "../bevy_app", version = "0.6.0" } # other libloading = { version = "0.7" } diff --git a/crates/bevy_dynamic_plugin/src/loader.rs b/crates/bevy_dynamic_plugin/src/loader.rs index 20c543944ccc2..6cc33e1bd996b 100644 --- a/crates/bevy_dynamic_plugin/src/loader.rs +++ b/crates/bevy_dynamic_plugin/src/loader.rs @@ -2,7 +2,7 @@ use libloading::{Library, Symbol}; use bevy_app::{App, CreatePlugin, Plugin}; -/// Dynamically links a plugin a the given path. The plugin must export a function with the +/// Dynamically links a plugin at the given path. The plugin must export a function with the /// [`CreatePlugin`] signature named `_bevy_create_plugin`. /// /// # Safety diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 00010a15252e0..86e379dfb2216 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_ecs" -version = "0.5.0" -edition = "2018" +version = "0.6.0" +edition = "2021" description = "Bevy Engine's entity component system" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" @@ -14,30 +14,26 @@ trace = [] default = ["bevy_reflect"] [dependencies] -bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", optional = true } -bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" } -bevy_utils = { path = "../bevy_utils", version = "0.5.0" } -bevy_ecs_macros = { path = "macros", version = "0.5.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.6.0", optional = true } +bevy_tasks = { path = "../bevy_tasks", version = "0.6.0" } +bevy_utils = { path = "../bevy_utils", version = "0.6.0" } +bevy_ecs_macros = { path = "macros", version = "0.6.0" } async-channel = "1.4" fixedbitset = "0.4" fxhash = "0.2" thiserror = "1.0" downcast-rs = "1.2" -rand = "0.8" serde = "1" [dev-dependencies] parking_lot = "0.11" +rand = "0.8" [[example]] name = "events" path = "examples/events.rs" -[[example]] -name = "component_storage" -path = "examples/component_storage.rs" - [[example]] name = "resources" path = "examples/resources.rs" diff --git a/crates/bevy_ecs/README.md b/crates/bevy_ecs/README.md index 478b23b915a1f..699b56624fbaa 100644 --- a/crates/bevy_ecs/README.md +++ b/crates/bevy_ecs/README.md @@ -25,6 +25,9 @@ Bevy ECS is Bevy's implementation of the ECS pattern. Unlike other Rust ECS impl Components are normal Rust structs. They are data stored in a `World` and specific instances of Components correlate to Entities. ```rust +use bevy_ecs::prelude::*; + +#[derive(Component)] struct Position { x: f32, y: f32 } ``` @@ -33,6 +36,8 @@ struct Position { x: f32, y: f32 } Entities, Components, and Resources are stored in a `World`. Worlds, much like Rust std collections like HashSet and Vec, expose operations to insert, read, write, and remove the data they store. ```rust +use bevy_ecs::world::World; + let world = World::default(); ``` @@ -41,6 +46,15 @@ let world = World::default(); Entities are unique identifiers that correlate to zero or more Components. ```rust +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct Position { x: f32, y: f32 } +#[derive(Component)] +struct Velocity { x: f32, y: f32 } + +let mut world = World::new(); + let entity = world.spawn() .insert(Position { x: 0.0, y: 0.0 }) .insert(Velocity { x: 1.0, y: 0.0 }) @@ -56,6 +70,11 @@ let velocity = entity_ref.get::().unwrap(); Systems are normal Rust functions. Thanks to the Rust type system, Bevy ECS can use function parameter types to determine what data needs to be sent to the system. It also uses this "data access" information to determine what Systems can run in parallel with each other. ```rust +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct Position { x: f32, y: f32 } + fn print_position(query: Query<(Entity, &Position)>) { for (entity, position) in query.iter() { println!("Entity {:?} is at position: x {}, y {}", entity, position.x, position.y); @@ -68,11 +87,15 @@ fn print_position(query: Query<(Entity, &Position)>) { Apps often require unique resources, such as asset collections, renderers, audio servers, time, etc. Bevy ECS makes this pattern a first class citizen. `Resource` is a special kind of component that does not belong to any entity. Instead, it is identified uniquely by its type: ```rust +use bevy_ecs::prelude::*; + #[derive(Default)] struct Time { seconds: f32, } +let mut world = World::new(); + world.insert_resource(Time::default()); let time = world.get_resource::(StorageType::SparseSet)); /// ``` #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum StorageType { @@ -128,6 +165,8 @@ impl SparseSetIndex for ComponentId { #[derive(Debug)] pub struct ComponentDescriptor { name: String, + // SAFETY: This must remain private. It must match the statically known StorageType of the + // associated rust component type if one exists. storage_type: StorageType, // SAFETY: This must remain private. It must only be set to "true" if this component is // actually Send + Sync @@ -143,10 +182,26 @@ impl ComponentDescriptor { x.cast::().drop_in_place() } - pub fn new(storage_type: StorageType) -> Self { + pub fn new() -> Self { Self { name: std::any::type_name::().to_string(), - storage_type, + storage_type: T::Storage::STORAGE_TYPE, + is_send_and_sync: true, + type_id: Some(TypeId::of::()), + layout: Layout::new::(), + drop: Self::drop_ptr::, + } + } + + /// Create a new `ComponentDescriptor` for a resource. + /// + /// The [`StorageType`] for resources is always [`TableStorage`]. + pub fn new_resource() -> Self { + Self { + name: std::any::type_name::().to_string(), + // PERF: `SparseStorage` may actually be a more + // reasonable choice as `storage_type` for resources. + storage_type: StorageType::Table, is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), @@ -188,49 +243,22 @@ pub struct Components { resource_indices: std::collections::HashMap, } -#[derive(Debug, Error)] -pub enum ComponentsError { - #[error("A component of type {name:?} ({type_id:?}) already exists")] - ComponentAlreadyExists { type_id: TypeId, name: String }, -} - impl Components { - pub(crate) fn add( - &mut self, - descriptor: ComponentDescriptor, - ) -> Result { - let index = self.components.len(); - if let Some(type_id) = descriptor.type_id { - let index_entry = self.indices.entry(type_id); - if let Entry::Occupied(_) = index_entry { - return Err(ComponentsError::ComponentAlreadyExists { - type_id, - name: descriptor.name, - }); - } - self.indices.insert(type_id, index); - } - self.components - .push(ComponentInfo::new(ComponentId(index), descriptor)); - - Ok(ComponentId(index)) - } - #[inline] - pub fn get_or_insert_id(&mut self) -> ComponentId { - // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] - unsafe { - self.get_or_insert_with(TypeId::of::(), || { - ComponentDescriptor::new::(StorageType::default()) - }) - } - } - - #[inline] - pub fn get_or_insert_info(&mut self) -> &ComponentInfo { - let id = self.get_or_insert_id::(); - // SAFE: component_info with the given `id` initialized above - unsafe { self.get_info_unchecked(id) } + pub fn init_component(&mut self, storages: &mut Storages) -> ComponentId { + let type_id = TypeId::of::(); + let components = &mut self.components; + let index = self.indices.entry(type_id).or_insert_with(|| { + let index = components.len(); + let descriptor = ComponentDescriptor::new::(); + let info = ComponentInfo::new(ComponentId(index), descriptor); + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { + storages.sparse_sets.get_or_insert(&info); + } + components.push(info); + index + }); + ComponentId(*index) } #[inline] @@ -250,7 +278,7 @@ impl Components { /// # Safety /// - /// `id` must be a valid [ComponentId] + /// `id` must be a valid [`ComponentId`] #[inline] pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo { debug_assert!(id.index() < self.components.len()); @@ -270,17 +298,17 @@ impl Components { } #[inline] - pub fn get_or_insert_resource_id(&mut self) -> ComponentId { + pub fn init_resource(&mut self) -> ComponentId { // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] unsafe { self.get_or_insert_resource_with(TypeId::of::(), || { - ComponentDescriptor::new::(StorageType::default()) + ComponentDescriptor::new_resource::() }) } } #[inline] - pub fn get_or_insert_non_send_resource_id(&mut self) -> ComponentId { + pub fn init_non_send(&mut self) -> ComponentId { // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] unsafe { self.get_or_insert_resource_with(TypeId::of::(), || { @@ -308,26 +336,6 @@ impl Components { ComponentId(*index) } - - /// # Safety - /// - /// The [`ComponentDescriptor`] must match the [`TypeId`] - #[inline] - pub(crate) unsafe fn get_or_insert_with( - &mut self, - type_id: TypeId, - func: impl FnOnce() -> ComponentDescriptor, - ) -> ComponentId { - let components = &mut self.components; - let index = self.indices.entry(type_id).or_insert_with(|| { - let descriptor = func(); - let index = components.len(); - components.push(ComponentInfo::new(ComponentId(index), descriptor)); - index - }); - - ComponentId(*index) - } } #[derive(Clone, Debug)] diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index c74974d7a4457..75ee18ee49dfb 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -57,15 +57,54 @@ pub enum AllocAtWithoutReplacement { } impl Entity { - /// Creates a new entity reference with a generation of 0. + /// Creates a new entity reference with the specified `id` and a generation of 0. /// /// # Note /// - /// Spawning a specific `entity` value is rarely the right choice. Most apps should favor + /// Spawning a specific `entity` value is __rarely the right choice__. Most apps should favor /// [`Commands::spawn`](crate::system::Commands::spawn). This method should generally /// only be used for sharing entities across apps, and only when they have a scheme /// worked out to share an ID space (which doesn't happen by default). - pub fn new(id: u32) -> Entity { + /// + /// In general, one should not try to synchronize the ECS by attempting to ensure that + /// `Entity` lines up between instances, but instead insert a secondary identifier as + /// a component. + /// + /// There are still some use cases where it might be appropriate to use this function + /// externally. + /// + /// ## Examples + /// + /// Initializing a collection (e.g. `array` or `Vec`) with a known size: + /// + /// ```no_run + /// # use bevy_ecs::prelude::*; + /// // Create a new array of size 10 and initialize it with (invalid) entities. + /// let mut entities: [Entity; 10] = [Entity::from_raw(0); 10]; + /// + /// // ... replace the entities with valid ones. + /// ``` + /// + /// Deriving `Reflect` for a component that has an `Entity` field: + /// + /// ```no_run + /// # use bevy_ecs::{prelude::*, component::*}; + /// # use bevy_reflect::Reflect; + /// #[derive(Reflect, Component)] + /// #[reflect(Component)] + /// pub struct MyStruct { + /// pub entity: Entity, + /// } + /// + /// impl FromWorld for MyStruct { + /// fn from_world(_world: &mut World) -> Self { + /// Self { + /// entity: Entity::from_raw(u32::MAX), + /// } + /// } + /// } + /// ``` + pub fn from_raw(id: u32) -> Entity { Entity { id, generation: 0 } } @@ -120,7 +159,7 @@ impl SparseSetIndex for Entity { } fn get_sparse_set_index(value: usize) -> Self { - Entity::new(value as u32) + Entity::from_raw(value as u32) } } @@ -454,9 +493,9 @@ impl Entities { /// `reserve_entities`, then initializes each one using the supplied function. /// /// # Safety - /// Flush _must_ set the entity location to the correct ArchetypeId for the given Entity - /// each time init is called. This _can_ be ArchetypeId::INVALID, provided the Entity has - /// not been assigned to an Archetype. + /// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`] + /// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`] + /// has not been assigned to an [`Archetype`][crate::archetype::Archetype]. pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) { let free_cursor = self.free_cursor.get_mut(); let current_free_cursor = *free_cursor; diff --git a/crates/bevy_ecs/src/entity/serde.rs b/crates/bevy_ecs/src/entity/serde.rs index 5c589bda4d200..e9dbf03d1b2d5 100644 --- a/crates/bevy_ecs/src/entity/serde.rs +++ b/crates/bevy_ecs/src/entity/serde.rs @@ -25,13 +25,20 @@ impl<'de> Visitor<'de> for EntityVisitor { type Value = Entity; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("expected Entity") + formatter.write_str("Entity") } fn visit_u32(self, v: u32) -> Result where E: serde::de::Error, { - Ok(Entity::new(v)) + Ok(Entity::from_raw(v)) + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + Ok(Entity::from_raw(v as u32)) } } diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 6b5cc1af48dd7..1b78f2229f9bc 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -1,10 +1,7 @@ //! Event handling types. -use crate as bevy_ecs; -use crate::{ - component::Component, - system::{Local, Res, ResMut, SystemParam}, -}; +use crate::system::{Local, Res, ResMut, SystemParam}; +use crate::{self as bevy_ecs, system::Resource}; use bevy_utils::tracing::trace; use std::{ fmt::{self}, @@ -66,12 +63,16 @@ enum State { /// Each event can be consumed by multiple systems, in parallel, /// with consumption tracked by the [`EventReader`] on a per-system basis. /// +/// If no [ordering](https://github.com/bevyengine/bevy/blob/main/examples/ecs/ecs_guide.rs) +/// is applied between writing and reading systems, there is a risk of a race condition. +/// This means that whether the events arrive before or after the next [`Events::update`] is unpredictable. +/// /// This collection is meant to be paired with a system that calls /// [`Events::update`] exactly once per update/frame. /// /// [`Events::update_system`] is a system that does this, typically intialized automatically using -/// [`App::add_event`]. [EventReader]s are expected to read events from this collection at -/// least once per loop/frame. +/// [`add_event`](https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event). +/// [`EventReader`]s are expected to read events from this collection at least once per loop/frame. /// Events will persist across a single frame boundary and so ordering of event producers and /// consumers is not critical (although poorly-planned ordering may cause accumulating lag). /// If events are not handled by the end of the frame after they are updated, they will be @@ -106,20 +107,25 @@ enum State { /// /// # Details /// -/// [Events] is implemented using a double buffer. Each call to [Events::update] swaps buffers and -/// clears out the oldest buffer. [EventReader]s that read at least once per update will never drop -/// events. [EventReader]s that read once within two updates might still receive some events. -/// [EventReader]s that read after two updates are guaranteed to drop all events that occurred +/// [`Events`] is implemented using a variation of a double buffer strategy. +/// Each call to [`update`](Events::update) swaps buffers and clears out the oldest one. +/// - [`EventReader`]s will read events from both buffers. +/// - [`EventReader`]s that read at least once per update will never drop events. +/// - [`EventReader`]s that read once within two updates might still receive some events +/// - [`EventReader`]s that read after two updates are guaranteed to drop all events that occurred /// before those updates. /// -/// The buffers in [Events] will grow indefinitely if [Events::update] is never called. +/// The buffers in [`Events`] will grow indefinitely if [`update`](Events::update) is never called. /// -/// An alternative call pattern would be to call [Events::update] manually across frames to control -/// when events are cleared. +/// An alternative call pattern would be to call [`update`](Events::update) +/// manually across frames to control when events are cleared. /// This complicates consumption and risks ever-expanding memory usage if not cleaned up, -/// but can be done by adding your event as a resource instead of using [`App::add_event`]. +/// but can be done by adding your event as a resource instead of using +/// [`add_event`](https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event). +/// +/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/event.rs) +/// [Example usage standalone.](https://github.com/bevyengine/bevy/blob/latest/bevy_ecs/examples/events.rs) /// -/// [`App::add_event`]: https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event #[derive(Debug)] pub struct Events { events_a: Vec>, @@ -153,20 +159,22 @@ fn map_instance_event(event_instance: &EventInstance) -> &T { /// Reads events of type `T` in order and tracks which events have already been read. #[derive(SystemParam)] -pub struct EventReader<'w, 's, T: Component> { +pub struct EventReader<'w, 's, T: Resource> { last_event_count: Local<'s, (usize, PhantomData)>, events: Res<'w, Events>, } /// Sends events of type `T`. #[derive(SystemParam)] -pub struct EventWriter<'w, 's, T: Component> { +pub struct EventWriter<'w, 's, T: Resource> { events: ResMut<'w, Events>, #[system_param(ignore)] marker: PhantomData<&'s usize>, } -impl<'w, 's, T: Component> EventWriter<'w, 's, T> { +impl<'w, 's, T: Resource> EventWriter<'w, 's, T> { + /// Sends an `event`. [`EventReader`]s can then read the event. + /// See [`Events`] for details. pub fn send(&mut self, event: T) { self.events.send(event); } @@ -174,6 +182,14 @@ impl<'w, 's, T: Component> EventWriter<'w, 's, T> { pub fn send_batch(&mut self, events: impl Iterator) { self.events.extend(events); } + + /// Sends the default value of the event. Useful when the event is an empty struct. + pub fn send_default(&mut self) + where + T: Default, + { + self.events.send_default(); + } } pub struct ManualEventReader { @@ -190,25 +206,36 @@ impl Default for ManualEventReader { } } -impl ManualEventReader { +#[allow(clippy::len_without_is_empty)] // Check fails since the is_empty implementation has a signature other than `(&self) -> bool` +impl ManualEventReader { /// See [`EventReader::iter`] - pub fn iter<'a>(&mut self, events: &'a Events) -> impl DoubleEndedIterator { + pub fn iter<'a>(&'a mut self, events: &'a Events) -> impl DoubleEndedIterator { internal_event_reader(&mut self.last_event_count, events).map(|(e, _)| e) } /// See [`EventReader::iter_with_id`] pub fn iter_with_id<'a>( - &mut self, + &'a mut self, events: &'a Events, ) -> impl DoubleEndedIterator)> { internal_event_reader(&mut self.last_event_count, events) } + + /// See [`EventReader::len`] + pub fn len(&self, events: &Events) -> usize { + events.event_reader_len(self.last_event_count) + } + + /// See [`EventReader::is_empty`] + pub fn is_empty(&self, events: &Events) -> bool { + self.len(events) == 0 + } } /// Like [`iter_with_id`](EventReader::iter_with_id) except not emitting any traces for read /// messages. fn internal_event_reader<'a, T>( - last_event_count: &mut usize, + last_event_count: &'a mut usize, events: &'a Events, ) -> impl DoubleEndedIterator)> { // if the reader has seen some of the events in a buffer, find the proper index offset. @@ -223,43 +250,23 @@ fn internal_event_reader<'a, T>( } else { 0 }; - *last_event_count = events.event_count; - match events.state { - State::A => events - .events_b - .get(b_index..) - .unwrap_or_else(|| &[]) - .iter() - .map(map_instance_event_with_id) - .chain( - events - .events_a - .get(a_index..) - .unwrap_or_else(|| &[]) - .iter() - .map(map_instance_event_with_id), - ), - State::B => events - .events_a - .get(a_index..) - .unwrap_or_else(|| &[]) - .iter() - .map(map_instance_event_with_id) - .chain( - events - .events_b - .get(b_index..) - .unwrap_or_else(|| &[]) - .iter() - .map(map_instance_event_with_id), - ), - } + let a = events.events_a.get(a_index..).unwrap_or_else(|| &[]); + let b = events.events_b.get(b_index..).unwrap_or_else(|| &[]); + let unread_count = a.len() + b.len(); + *last_event_count = events.event_count - unread_count; + let iterator = match events.state { + State::A => b.iter().chain(a.iter()), + State::B => a.iter().chain(b.iter()), + }; + iterator + .map(map_instance_event_with_id) + .inspect(move |(_, id)| *last_event_count = (id.id + 1).max(*last_event_count)) } -impl<'w, 's, T: Component> EventReader<'w, 's, T> { - /// Iterates over the events this EventReader has not seen yet. This updates the EventReader's - /// event counter, which means subsequent event reads will not include events that happened - /// before now. +impl<'w, 's, T: Resource> EventReader<'w, 's, T> { + /// Iterates over the events this [`EventReader`] has not seen yet. This updates the + /// [`EventReader`]'s event counter, which means subsequent event reads will not include events + /// that happened before now. pub fn iter(&mut self) -> impl DoubleEndedIterator { self.iter_with_id().map(|(event, _id)| event) } @@ -271,10 +278,20 @@ impl<'w, 's, T: Component> EventReader<'w, 's, T> { (event, id) }) } + + /// Determines the number of events available to be read from this [`EventReader`] without consuming any. + pub fn len(&self) -> usize { + self.events.event_reader_len(self.last_event_count.0) + } + + /// Determines if are any events available to be read without consuming any. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } } -impl Events { - /// "Sends" an `event` by writing it to the current event buffer. [EventReader]s can then read +impl Events { + /// "Sends" an `event` by writing it to the current event buffer. [`EventReader`]s can then read /// the event. pub fn send(&mut self, event: T) { let event_id = EventId { @@ -293,7 +310,15 @@ impl Events { self.event_count += 1; } - /// Gets a new [ManualEventReader]. This will include all events already in the event buffers. + /// Sends the default value of the event. Useful when the event is an empty struct. + pub fn send_default(&mut self) + where + T: Default, + { + self.send(Default::default()); + } + + /// Gets a new [`ManualEventReader`]. This will include all events already in the event buffers. pub fn get_reader(&self) -> ManualEventReader { ManualEventReader { last_event_count: 0, @@ -301,8 +326,8 @@ impl Events { } } - /// Gets a new [ManualEventReader]. This will ignore all events already in the event buffers. It - /// will read all future events. + /// Gets a new [`ManualEventReader`]. This will ignore all events already in the event buffers. + /// It will read all future events. pub fn get_reader_current(&self) -> ManualEventReader { ManualEventReader { last_event_count: self.event_count, @@ -315,19 +340,19 @@ impl Events { pub fn update(&mut self) { match self.state { State::A => { - self.events_b = Vec::new(); + self.events_b.clear(); self.state = State::B; self.b_start_event_count = self.event_count; } State::B => { - self.events_a = Vec::new(); + self.events_a.clear(); self.state = State::A; self.a_start_event_count = self.event_count; } } } - /// A system that calls [Events::update] once per frame. + /// A system that calls [`Events::update`] once per frame. pub fn update_system(mut events: ResMut) { events.update(); } @@ -373,7 +398,7 @@ impl Events { /// Iterates over events that happened since the last "update" call. /// WARNING: You probably don't want to use this call. In most cases you should use an - /// `EventReader`. You should only use this if you know you only need to consume events + /// [`EventReader`]. You should only use this if you know you only need to consume events /// between the last `update()` call and your call to `iter_current_update_events`. /// If events happen outside that window, they will not be handled. For example, any events that /// happen after this call and before the next `update()` call will be dropped. @@ -383,6 +408,29 @@ impl Events { State::B => self.events_b.iter().map(map_instance_event), } } + + /// Determines how many events are in the reader after the given `last_event_count` parameter + fn event_reader_len(&self, last_event_count: usize) -> usize { + let a_count = if last_event_count <= self.a_start_event_count { + self.events_a.len() + } else { + self.events_a + .len() + .checked_sub(last_event_count - self.a_start_event_count) + .unwrap_or_default() + }; + + let b_count = if last_event_count <= self.b_start_event_count { + self.events_b.len() + } else { + self.events_b + .len() + .checked_sub(last_event_count - self.b_start_event_count) + .unwrap_or_default() + }; + + a_count + b_count + } } impl std::iter::Extend for Events { @@ -514,11 +562,11 @@ mod tests { ); } - fn get_events( - events: &Events, - reader: &mut ManualEventReader, - ) -> Vec { - reader.iter(events).cloned().collect::>() + fn get_events( + events: &Events, + reader: &mut ManualEventReader, + ) -> Vec { + reader.iter(events).cloned().collect::>() } #[derive(PartialEq, Eq, Debug)] @@ -584,4 +632,61 @@ mod tests { events.update(); assert!(events.is_empty()); } + + #[test] + fn test_event_reader_len_empty() { + let events = Events::::default(); + assert_eq!(events.get_reader().len(&events), 0); + assert!(events.get_reader().is_empty(&events)); + } + + #[test] + fn test_event_reader_len_filled() { + let mut events = Events::::default(); + events.send(TestEvent { i: 0 }); + assert_eq!(events.get_reader().len(&events), 1); + assert!(!events.get_reader().is_empty(&events)); + } + + #[test] + fn test_event_reader_len_current() { + let mut events = Events::::default(); + events.send(TestEvent { i: 0 }); + let reader = events.get_reader_current(); + assert!(reader.is_empty(&events)); + events.send(TestEvent { i: 0 }); + assert_eq!(reader.len(&events), 1); + assert!(!reader.is_empty(&events)); + } + + #[test] + fn test_event_reader_len_update() { + let mut events = Events::::default(); + events.send(TestEvent { i: 0 }); + events.send(TestEvent { i: 0 }); + let reader = events.get_reader(); + assert_eq!(reader.len(&events), 2); + events.update(); + events.send(TestEvent { i: 0 }); + assert_eq!(reader.len(&events), 3); + events.update(); + assert_eq!(reader.len(&events), 1); + events.update(); + assert!(reader.is_empty(&events)); + } + + #[derive(Clone, PartialEq, Debug, Default)] + struct EmptyTestEvent; + + #[test] + fn test_firing_empty_event() { + let mut events = Events::::default(); + events.send_default(); + + let mut reader = events.get_reader(); + assert_eq!( + get_events(&events, &mut reader), + vec![EmptyTestEvent::default()] + ); + } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index de7c17baf6018..d48ea55929773 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + pub mod archetype; pub mod bundle; pub mod change_detection; @@ -21,9 +23,10 @@ pub mod prelude { pub use crate::{ bundle::Bundle, change_detection::DetectChanges, + component::Component, entity::Entity, event::{EventReader, EventWriter}, - query::{Added, ChangeTrackers, Changed, Or, QueryState, With, Without}, + query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, schedule::{ AmbiguitySetLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel, RunCriteriaPiping, @@ -37,12 +40,14 @@ pub mod prelude { }; } +pub use bevy_ecs_macros::all_tuples; + #[cfg(test)] mod tests { use crate as bevy_ecs; use crate::{ bundle::Bundle, - component::{Component, ComponentDescriptor, ComponentId, StorageType}, + component::{Component, ComponentId}, entity::Entity, query::{ Added, ChangeTrackers, Changed, FilterFetch, FilteredAccess, With, Without, WorldQuery, @@ -59,14 +64,14 @@ mod tests { }, }; - #[derive(Debug, PartialEq, Eq)] + #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct A(usize); - #[derive(Debug, PartialEq, Eq)] + #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct B(usize); - #[derive(Debug, PartialEq, Eq)] + #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct C; - #[derive(Clone, Debug)] + #[derive(Component, Clone, Debug)] struct DropCk(Arc); impl DropCk { fn new_pair() -> (Self, Arc) { @@ -81,26 +86,41 @@ mod tests { } } + #[derive(Component, Clone, Debug)] + #[component(storage = "SparseSet")] + struct DropCkSparse(DropCk); + + #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] + #[component(storage = "Table")] + struct TableStored(&'static str); + #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] + #[component(storage = "SparseSet")] + struct SparseStored(u32); + #[test] fn random_access() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); - assert_eq!(*world.get::<&str>(e).unwrap(), "abc"); - assert_eq!(*world.get::(e).unwrap(), 123); - assert_eq!(*world.get::<&str>(f).unwrap(), "def"); - assert_eq!(*world.get::(f).unwrap(), 456); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), SparseStored(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), SparseStored(456), A(1))) + .id(); + assert_eq!(world.get::(e).unwrap().0, "abc"); + assert_eq!(world.get::(e).unwrap().0, 123); + assert_eq!(world.get::(f).unwrap().0, "def"); + assert_eq!(world.get::(f).unwrap().0, 456); // test archetype get_mut() - *world.get_mut::<&'static str>(e).unwrap() = "xyz"; - assert_eq!(*world.get::<&'static str>(e).unwrap(), "xyz"); + world.get_mut::(e).unwrap().0 = "xyz"; + assert_eq!(world.get::(e).unwrap().0, "xyz"); // test sparse set get_mut() - *world.get_mut::(f).unwrap() = 42; - assert_eq!(*world.get::(f).unwrap(), 42); + world.get_mut::(f).unwrap().0 = 42; + assert_eq!(world.get::(f).unwrap().0, 42); } #[test] @@ -109,79 +129,93 @@ mod tests { #[derive(Bundle, PartialEq, Debug)] struct Foo { - x: &'static str, - y: i32, + x: TableStored, + y: SparseStored, } - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - assert_eq!( - ::component_ids(world.components_mut()), + ::component_ids(&mut world.components, &mut world.storages), vec![ - world.components_mut().get_or_insert_id::<&'static str>(), - world.components_mut().get_or_insert_id::(), + world.init_component::(), + world.init_component::(), ] ); - let e1 = world.spawn().insert_bundle(Foo { x: "abc", y: 123 }).id(); - let e2 = world.spawn().insert_bundle(("def", 456, true)).id(); - assert_eq!(*world.get::<&str>(e1).unwrap(), "abc"); - assert_eq!(*world.get::(e1).unwrap(), 123); - assert_eq!(*world.get::<&str>(e2).unwrap(), "def"); - assert_eq!(*world.get::(e2).unwrap(), 456); + let e1 = world + .spawn() + .insert_bundle(Foo { + x: TableStored("abc"), + y: SparseStored(123), + }) + .id(); + let e2 = world + .spawn() + .insert_bundle((TableStored("def"), SparseStored(456), A(1))) + .id(); + assert_eq!(world.get::(e1).unwrap().0, "abc"); + assert_eq!(world.get::(e1).unwrap().0, 123); + assert_eq!(world.get::(e2).unwrap().0, "def"); + assert_eq!(world.get::(e2).unwrap().0, 456); // test archetype get_mut() - *world.get_mut::<&'static str>(e1).unwrap() = "xyz"; - assert_eq!(*world.get::<&'static str>(e1).unwrap(), "xyz"); + world.get_mut::(e1).unwrap().0 = "xyz"; + assert_eq!(world.get::(e1).unwrap().0, "xyz"); // test sparse set get_mut() - *world.get_mut::(e2).unwrap() = 42; - assert_eq!(*world.get::(e2).unwrap(), 42); + world.get_mut::(e2).unwrap().0 = 42; + assert_eq!(world.get::(e2).unwrap().0, 42); assert_eq!( world.entity_mut(e1).remove_bundle::().unwrap(), - Foo { x: "xyz", y: 123 } + Foo { + x: TableStored("xyz"), + y: SparseStored(123), + } ); #[derive(Bundle, PartialEq, Debug)] struct Nested { - a: usize, + a: A, #[bundle] foo: Foo, - b: u8, + b: B, } assert_eq!( - ::component_ids(world.components_mut()), + ::component_ids(&mut world.components, &mut world.storages), vec![ - world.components_mut().get_or_insert_id::(), - world.components_mut().get_or_insert_id::<&'static str>(), - world.components_mut().get_or_insert_id::(), - world.components_mut().get_or_insert_id::(), + world.init_component::(), + world.init_component::(), + world.init_component::(), + world.init_component::(), ] ); let e3 = world .spawn() .insert_bundle(Nested { - a: 1, - foo: Foo { x: "ghi", y: 789 }, - b: 2, + a: A(1), + foo: Foo { + x: TableStored("ghi"), + y: SparseStored(789), + }, + b: B(2), }) .id(); - assert_eq!(*world.get::<&str>(e3).unwrap(), "ghi"); - assert_eq!(*world.get::(e3).unwrap(), 789); - assert_eq!(*world.get::(e3).unwrap(), 1); - assert_eq!(*world.get::(e3).unwrap(), 2); + assert_eq!(world.get::(e3).unwrap().0, "ghi"); + assert_eq!(world.get::(e3).unwrap().0, 789); + assert_eq!(world.get::(e3).unwrap().0, 1); + assert_eq!(world.get::(e3).unwrap().0, 2); assert_eq!( world.entity_mut(e3).remove_bundle::().unwrap(), Nested { - a: 1, - foo: Foo { x: "ghi", y: 789 }, - b: 2, + a: A(1), + foo: Foo { + x: TableStored("ghi"), + y: SparseStored(789), + }, + b: B(2), } ); } @@ -189,113 +223,167 @@ mod tests { #[test] fn despawn_table_storage() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); assert_eq!(world.entities.len(), 2); assert!(world.despawn(e)); assert_eq!(world.entities.len(), 1); - assert!(world.get::<&str>(e).is_none()); - assert!(world.get::(e).is_none()); - assert_eq!(*world.get::<&str>(f).unwrap(), "def"); - assert_eq!(*world.get::(f).unwrap(), 456); + assert!(world.get::(e).is_none()); + assert!(world.get::(e).is_none()); + assert_eq!(world.get::(f).unwrap().0, "def"); + assert_eq!(world.get::(f).unwrap().0, 456); } #[test] fn despawn_mixed_storage() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), SparseStored(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), SparseStored(456))) + .id(); assert_eq!(world.entities.len(), 2); assert!(world.despawn(e)); assert_eq!(world.entities.len(), 1); - assert!(world.get::<&str>(e).is_none()); - assert!(world.get::(e).is_none()); - assert_eq!(*world.get::<&str>(f).unwrap(), "def"); - assert_eq!(*world.get::(f).unwrap(), 456); + assert!(world.get::(e).is_none()); + assert!(world.get::(e).is_none()); + assert_eq!(world.get::(f).unwrap().0, "def"); + assert_eq!(world.get::(f).unwrap().0, 456); } #[test] fn query_all() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); let ents = world - .query::<(Entity, &i32, &&str)>() + .query::<(Entity, &A, &TableStored)>() .iter(&world) .map(|(e, &i, &s)| (e, i, s)) .collect::>(); - assert_eq!(ents, &[(e, 123, "abc"), (f, 456, "def")]); + assert_eq!( + ents, + &[ + (e, A(123), TableStored("abc")), + (f, A(456), TableStored("def")) + ] + ); } #[test] fn query_all_for_each() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); let mut results = Vec::new(); world - .query::<(Entity, &i32, &&str)>() + .query::<(Entity, &A, &TableStored)>() .for_each(&world, |(e, &i, &s)| results.push((e, i, s))); - assert_eq!(results, &[(e, 123, "abc"), (f, 456, "def")]); + assert_eq!( + results, + &[ + (e, A(123), TableStored("abc")), + (f, A(456), TableStored("def")) + ] + ); } #[test] fn query_single_component() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let ents = world - .query::<(Entity, &i32)>() + .query::<(Entity, &A)>() .iter(&world) .map(|(e, &i)| (e, i)) .collect::>(); - assert_eq!(ents, &[(e, 123), (f, 456)]); + assert_eq!(ents, &[(e, A(123)), (f, A(456))]); } #[test] fn stateful_query_handles_new_archetype() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let mut query = world.query::<(Entity, &i32)>(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let mut query = world.query::<(Entity, &A)>(); let ents = query.iter(&world).map(|(e, &i)| (e, i)).collect::>(); - assert_eq!(ents, &[(e, 123)]); + assert_eq!(ents, &[(e, A(123))]); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let ents = query.iter(&world).map(|(e, &i)| (e, i)).collect::>(); - assert_eq!(ents, &[(e, 123), (f, 456)]); + assert_eq!(ents, &[(e, A(123)), (f, A(456))]); } #[test] fn query_single_component_for_each() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let mut results = Vec::new(); world - .query::<(Entity, &i32)>() + .query::<(Entity, &A)>() .for_each(&world, |(e, &i)| results.push((e, i))); - assert_eq!(results, &[(e, 123), (f, 456)]); + assert_eq!(results, &[(e, A(123)), (f, A(456))]); } #[test] fn par_for_each_dense() { let mut world = World::new(); let task_pool = TaskPool::default(); - let e1 = world.spawn().insert(1).id(); - let e2 = world.spawn().insert(2).id(); - let e3 = world.spawn().insert(3).id(); - let e4 = world.spawn().insert_bundle((4, true)).id(); - let e5 = world.spawn().insert_bundle((5, true)).id(); + let e1 = world.spawn().insert(A(1)).id(); + let e2 = world.spawn().insert(A(2)).id(); + let e3 = world.spawn().insert(A(3)).id(); + let e4 = world.spawn().insert_bundle((A(4), B(1))).id(); + let e5 = world.spawn().insert_bundle((A(5), B(1))).id(); let results = Arc::new(Mutex::new(Vec::new())); world - .query::<(Entity, &i32)>() - .par_for_each(&world, &task_pool, 2, |(e, &i)| results.lock().push((e, i))); + .query::<(Entity, &A)>() + .par_for_each(&world, &task_pool, 2, |(e, &A(i))| { + results.lock().push((e, i)) + }); results.lock().sort(); assert_eq!( &*results.lock(), @@ -306,19 +394,20 @@ mod tests { #[test] fn par_for_each_sparse() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); + let task_pool = TaskPool::default(); - let e1 = world.spawn().insert(1).id(); - let e2 = world.spawn().insert(2).id(); - let e3 = world.spawn().insert(3).id(); - let e4 = world.spawn().insert_bundle((4, true)).id(); - let e5 = world.spawn().insert_bundle((5, true)).id(); + let e1 = world.spawn().insert(SparseStored(1)).id(); + let e2 = world.spawn().insert(SparseStored(2)).id(); + let e3 = world.spawn().insert(SparseStored(3)).id(); + let e4 = world.spawn().insert_bundle((SparseStored(4), A(1))).id(); + let e5 = world.spawn().insert_bundle((SparseStored(5), A(1))).id(); let results = Arc::new(Mutex::new(Vec::new())); - world - .query::<(Entity, &i32)>() - .par_for_each(&world, &task_pool, 2, |(e, &i)| results.lock().push((e, i))); + world.query::<(Entity, &SparseStored)>().par_for_each( + &world, + &task_pool, + 2, + |(e, &SparseStored(i))| results.lock().push((e, i)), + ); results.lock().sort(); assert_eq!( &*results.lock(), @@ -329,194 +418,218 @@ mod tests { #[test] fn query_missing_component() { let mut world = World::new(); - world.spawn().insert_bundle(("abc", 123)); - world.spawn().insert_bundle(("def", 456)); - assert!(world.query::<(&bool, &i32)>().iter(&world).next().is_none()); + world.spawn().insert_bundle((TableStored("abc"), A(123))); + world.spawn().insert_bundle((TableStored("def"), A(456))); + assert!(world.query::<(&B, &A)>().iter(&world).next().is_none()); } #[test] fn query_sparse_component() { let mut world = World::new(); - world.spawn().insert_bundle(("abc", 123)); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + world.spawn().insert_bundle((TableStored("abc"), A(123))); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); let ents = world - .query::<(Entity, &bool)>() + .query::<(Entity, &B)>() .iter(&world) .map(|(e, &b)| (e, b)) .collect::>(); - assert_eq!(ents, &[(f, true)]); + assert_eq!(ents, &[(f, B(1))]); } #[test] fn query_filter_with() { let mut world = World::new(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + world.spawn().insert_bundle((A(123), B(1))); + world.spawn().insert(A(456)); let result = world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .iter(&world) .cloned() .collect::>(); - assert_eq!(result, vec![123]); + assert_eq!(result, vec![A(123)]); } #[test] fn query_filter_with_for_each() { let mut world = World::new(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + world.spawn().insert_bundle((A(123), B(1))); + world.spawn().insert(A(456)); let mut results = Vec::new(); world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .for_each(&world, |i| results.push(*i)); - assert_eq!(results, vec![123]); + assert_eq!(results, vec![A(123)]); } #[test] fn query_filter_with_sparse() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + + world.spawn().insert_bundle((A(123), SparseStored(321))); + world.spawn().insert(A(456)); let result = world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .iter(&world) .cloned() .collect::>(); - assert_eq!(result, vec![123]); + assert_eq!(result, vec![A(123)]); } #[test] fn query_filter_with_sparse_for_each() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + + world.spawn().insert_bundle((A(123), SparseStored(321))); + world.spawn().insert(A(456)); let mut results = Vec::new(); world - .query_filtered::<&u32, With>() + .query_filtered::<&A, With>() .for_each(&world, |i| results.push(*i)); - assert_eq!(results, vec![123]); + assert_eq!(results, vec![A(123)]); } #[test] fn query_filter_without() { let mut world = World::new(); - world.spawn().insert_bundle((123u32, 1.0f32)); - world.spawn().insert(456u32); + world.spawn().insert_bundle((A(123), B(321))); + world.spawn().insert(A(456)); let result = world - .query_filtered::<&u32, Without>() + .query_filtered::<&A, Without>() .iter(&world) .cloned() .collect::>(); - assert_eq!(result, vec![456]); + assert_eq!(result, vec![A(456)]); } #[test] fn query_optional_component_table() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), B(1))) + .id(); // this should be skipped - world.spawn().insert("abc"); + world.spawn().insert(TableStored("abc")); let ents = world - .query::<(Entity, Option<&bool>, &i32)>() + .query::<(Entity, Option<&B>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) .collect::>(); - assert_eq!(ents, &[(e, None, 123), (f, Some(true), 456)]); + assert_eq!(ents, &[(e, None, A(123)), (f, Some(B(1)), A(456))]); } #[test] fn query_optional_component_sparse() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456, true)).id(); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456), SparseStored(1))) + .id(); // // this should be skipped - // world.spawn().insert("abc"); + // SparseStored(1).spawn().insert("abc"); let ents = world - .query::<(Entity, Option<&bool>, &i32)>() + .query::<(Entity, Option<&SparseStored>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) .collect::>(); - assert_eq!(ents, &[(e, None, 123), (f, Some(true), 456)]); + assert_eq!( + ents, + &[(e, None, A(123)), (f, Some(SparseStored(1)), A(456))] + ); } #[test] fn query_optional_component_sparse_no_match() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - let f = world.spawn().insert_bundle(("def", 456)).id(); + + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let f = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); // // this should be skipped - world.spawn().insert("abc"); + world.spawn().insert(TableStored("abc")); let ents = world - .query::<(Entity, Option<&bool>, &i32)>() + .query::<(Entity, Option<&SparseStored>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) .collect::>(); - assert_eq!(ents, &[(e, None, 123), (f, None, 456)]); + assert_eq!(ents, &[(e, None, A(123)), (f, None, A(456))]); } #[test] fn add_remove_components() { let mut world = World::new(); - let e1 = world.spawn().insert(42).insert_bundle((true, "abc")).id(); - let e2 = world.spawn().insert(0).insert_bundle((false, "xyz")).id(); + let e1 = world + .spawn() + .insert(A(1)) + .insert_bundle((B(3), TableStored("abc"))) + .id(); + let e2 = world + .spawn() + .insert(A(2)) + .insert_bundle((B(4), TableStored("xyz"))) + .id(); assert_eq!( world - .query::<(Entity, &i32, &bool)>() + .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - &[(e1, 42, true), (e2, 0, false)] + &[(e1, A(1), B(3)), (e2, A(2), B(4))] ); - assert_eq!(world.entity_mut(e1).remove::(), Some(42)); + assert_eq!(world.entity_mut(e1).remove::(), Some(A(1))); assert_eq!( world - .query::<(Entity, &i32, &bool)>() + .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - &[(e2, 0, false)] + &[(e2, A(2), B(4))] ); assert_eq!( world - .query::<(Entity, &bool, &&str)>() + .query::<(Entity, &B, &TableStored)>() .iter(&world) - .map(|(e, &b, &s)| (e, b, s)) + .map(|(e, &B(b), &TableStored(s))| (e, b, s)) .collect::>(), - &[(e2, false, "xyz"), (e1, true, "abc")] + &[(e2, 4, "xyz"), (e1, 3, "abc")] ); - world.entity_mut(e1).insert(43); + world.entity_mut(e1).insert(A(43)); assert_eq!( world - .query::<(Entity, &i32, &bool)>() + .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - &[(e2, 0, false), (e1, 43, true)] + &[(e2, A(2), B(4)), (e1, A(43), B(3))] ); - world.entity_mut(e1).insert(1.0f32); + world.entity_mut(e1).insert(C); assert_eq!( world - .query::<(Entity, &f32)>() + .query::<(Entity, &C)>() .iter(&world) .map(|(e, &f)| (e, f)) .collect::>(), - &[(e1, 1.0)] + &[(e1, C)] ); } @@ -525,53 +638,57 @@ mod tests { let mut world = World::default(); let mut entities = Vec::with_capacity(10_000); for _ in 0..1000 { - entities.push(world.spawn().insert(0.0f32).id()); + entities.push(world.spawn().insert(B(0)).id()); } for (i, entity) in entities.iter().cloned().enumerate() { - world.entity_mut(entity).insert(i); + world.entity_mut(entity).insert(A(i)); } for (i, entity) in entities.iter().cloned().enumerate() { - assert_eq!(world.entity_mut(entity).remove::(), Some(i)); + assert_eq!(world.entity_mut(entity).remove::(), Some(A(i))); } } #[test] fn sparse_set_add_remove_many() { let mut world = World::default(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); + let mut entities = Vec::with_capacity(1000); for _ in 0..4 { - entities.push(world.spawn().insert(0.0f32).id()); + entities.push(world.spawn().insert(A(2)).id()); } for (i, entity) in entities.iter().cloned().enumerate() { - world.entity_mut(entity).insert(i); + world.entity_mut(entity).insert(SparseStored(i as u32)); } for (i, entity) in entities.iter().cloned().enumerate() { - assert_eq!(world.entity_mut(entity).remove::(), Some(i)); + assert_eq!( + world.entity_mut(entity).remove::(), + Some(SparseStored(i as u32)) + ); } } #[test] fn remove_missing() { let mut world = World::new(); - let e = world.spawn().insert_bundle(("abc", 123)).id(); - assert!(world.entity_mut(e).remove::().is_none()); + let e = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + assert!(world.entity_mut(e).remove::().is_none()); } #[test] fn spawn_batch() { let mut world = World::new(); - world.spawn_batch((0..100).map(|x| (x, "abc"))); + world.spawn_batch((0..100).map(|x| (A(x), TableStored("abc")))); let values = world - .query::<&i32>() + .query::<&A>() .iter(&world) - .copied() + .map(|v| v.0) .collect::>(); let expected = (0..100).collect::>(); assert_eq!(values, expected); @@ -580,17 +697,26 @@ mod tests { #[test] fn query_get() { let mut world = World::new(); - let a = world.spawn().insert_bundle(("abc", 123)).id(); - let b = world.spawn().insert_bundle(("def", 456)).id(); - let c = world.spawn().insert_bundle(("ghi", 789, true)).id(); + let a = world + .spawn() + .insert_bundle((TableStored("abc"), A(123))) + .id(); + let b = world + .spawn() + .insert_bundle((TableStored("def"), A(456))) + .id(); + let c = world + .spawn() + .insert_bundle((TableStored("ghi"), A(789), B(1))) + .id(); - let mut i32_query = world.query::<&i32>(); - assert_eq!(i32_query.get(&world, a).unwrap(), &123); - assert_eq!(i32_query.get(&world, b).unwrap(), &456); + let mut i32_query = world.query::<&A>(); + assert_eq!(i32_query.get(&world, a).unwrap().0, 123); + assert_eq!(i32_query.get(&world, b).unwrap().0, 456); - let mut i32_bool_query = world.query::<(&i32, &bool)>(); + let mut i32_bool_query = world.query::<(&A, &B)>(); assert!(i32_bool_query.get(&world, a).is_err()); - assert_eq!(i32_bool_query.get(&world, c).unwrap(), (&789, &true)); + assert_eq!(i32_bool_query.get(&world, c).unwrap(), (&A(789), &B(1))); assert!(world.despawn(a)); assert!(i32_query.get(&world, a).is_err()); } @@ -598,53 +724,49 @@ mod tests { #[test] fn remove_tracking() { let mut world = World::new(); - world - .register_component(ComponentDescriptor::new::<&'static str>( - StorageType::SparseSet, - )) - .unwrap(); - let a = world.spawn().insert_bundle(("abc", 123)).id(); - let b = world.spawn().insert_bundle(("abc", 123)).id(); + + let a = world.spawn().insert_bundle((SparseStored(0), A(123))).id(); + let b = world.spawn().insert_bundle((SparseStored(1), A(123))).id(); world.entity_mut(a).despawn(); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[a], "despawning results in 'removed component' state for table components" ); assert_eq!( - world.removed::<&'static str>().collect::>(), + world.removed::().collect::>(), &[a], "despawning results in 'removed component' state for sparse set components" ); - world.entity_mut(b).insert(10.0); + world.entity_mut(b).insert(B(1)); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[a], "archetype moves does not result in 'removed component' state" ); - world.entity_mut(b).remove::(); + world.entity_mut(b).remove::(); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[a, b], "removing a component results in a 'removed component' state" ); world.clear_trackers(); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[], "clearning trackers clears removals" ); assert_eq!( - world.removed::<&'static str>().collect::>(), + world.removed::().collect::>(), &[], "clearning trackers clears removals" ); assert_eq!( - world.removed::().collect::>(), + world.removed::().collect::>(), &[], "clearning trackers clears removals" ); @@ -673,61 +795,49 @@ mod tests { #[test] fn added_tracking() { let mut world = World::new(); - let a = world.spawn().insert(123i32).id(); + let a = world.spawn().insert(A(123)).id(); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 1 ); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 1 ); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_ok()); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_ok()); world.clear_trackers(); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 0 ); - assert_eq!(world.query::<&i32>().iter(&world).count(), 1); + assert_eq!(world.query::<&A>().iter(&world).count(), 1); assert_eq!( - world - .query_filtered::<(), Added>() - .iter(&world) - .count(), + world.query_filtered::<(), Added>().iter(&world).count(), 0 ); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_err()); - assert!(world.query::<&i32>().get(&world, a).is_ok()); + assert!(world.query::<&A>().get(&world, a).is_ok()); assert!(world - .query_filtered::<(), Added>() + .query_filtered::<(), Added>() .get(&world, a) .is_err()); } @@ -768,7 +878,7 @@ mod tests { let e1 = world.spawn().insert_bundle((A(0), B(0))).id(); let e2 = world.spawn().insert_bundle((A(0), B(0))).id(); let e3 = world.spawn().insert_bundle((A(0), B(0))).id(); - world.spawn().insert_bundle((A(0), B)); + world.spawn().insert_bundle((A(0), B(0))); world.clear_trackers(); @@ -796,7 +906,7 @@ mod tests { assert_eq!(get_filtered::>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)"); // spawning a new A entity should not change existing changed state - world.entity_mut(e1).insert_bundle((A(0), B)); + world.entity_mut(e1).insert_bundle((A(0), B(0))); assert_eq!( get_filtered::>(&mut world), vec![e3, e1], @@ -886,6 +996,8 @@ mod tests { let mut world = World::default(); assert!(world.get_resource::().is_none()); assert!(!world.contains_resource::()); + assert!(!world.is_resource_added::()); + assert!(!world.is_resource_changed::()); world.insert_resource(123); let resource_id = world @@ -900,6 +1012,8 @@ mod tests { assert_eq!(*world.get_resource::().expect("resource exists"), 123); assert!(world.contains_resource::()); + assert!(world.is_resource_added::()); + assert!(world.is_resource_changed::()); world.insert_resource(456u64); assert_eq!( @@ -975,36 +1089,39 @@ mod tests { #[test] fn remove_intersection() { let mut world = World::default(); - let e1 = world.spawn().insert_bundle((1, 1.0, "a")).id(); + let e1 = world + .spawn() + .insert_bundle((A(1), B(1), TableStored("a"))) + .id(); let mut e = world.entity_mut(e1); - assert_eq!(e.get::<&'static str>(), Some(&"a")); - assert_eq!(e.get::(), Some(&1)); - assert_eq!(e.get::(), Some(&1.0)); + assert_eq!(e.get::(), Some(&TableStored("a"))); + assert_eq!(e.get::(), Some(&A(1))); + assert_eq!(e.get::(), Some(&B(1))); assert_eq!( - e.get::(), + e.get::(), None, - "usize is not in the entity, so it should not exist" + "C is not in the entity, so it should not exist" ); - e.remove_bundle_intersection::<(i32, f64, usize)>(); + e.remove_bundle_intersection::<(A, B, C)>(); assert_eq!( - e.get::<&'static str>(), - Some(&"a"), - "&'static str is not in the removed bundle, so it should exist" + e.get::(), + Some(&TableStored("a")), + "TableStored is not in the removed bundle, so it should exist" ); assert_eq!( - e.get::(), + e.get::(), None, "i32 is in the removed bundle, so it should not exist" ); assert_eq!( - e.get::(), + e.get::(), None, "f64 is in the removed bundle, so it should not exist" ); assert_eq!( - e.get::(), + e.get::(), None, "usize is in the removed bundle, so it should not exist" ); @@ -1013,56 +1130,59 @@ mod tests { #[test] fn remove_bundle() { let mut world = World::default(); - world.spawn().insert_bundle((1, 1.0, 1usize)).id(); - let e2 = world.spawn().insert_bundle((2, 2.0, 2usize)).id(); - world.spawn().insert_bundle((3, 3.0, 3usize)).id(); + world.spawn().insert_bundle((A(1), B(1), TableStored("1"))); + let e2 = world + .spawn() + .insert_bundle((A(2), B(2), TableStored("2"))) + .id(); + world.spawn().insert_bundle((A(3), B(3), TableStored("3"))); - let mut query = world.query::<(&f64, &usize)>(); + let mut query = world.query::<(&B, &TableStored)>(); let results = query .iter(&world) - .map(|(a, b)| (*a, *b)) + .map(|(a, b)| (a.0, b.0)) .collect::>(); - assert_eq!(results, vec![(1.0, 1usize), (2.0, 2usize), (3.0, 3usize),]); + assert_eq!(results, vec![(1, "1"), (2, "2"), (3, "3"),]); let removed_bundle = world .entity_mut(e2) - .remove_bundle::<(f64, usize)>() + .remove_bundle::<(B, TableStored)>() .unwrap(); - assert_eq!(removed_bundle, (2.0, 2usize)); + assert_eq!(removed_bundle, (B(2), TableStored("2"))); let results = query .iter(&world) - .map(|(a, b)| (*a, *b)) + .map(|(a, b)| (a.0, b.0)) .collect::>(); - assert_eq!(results, vec![(1.0, 1usize), (3.0, 3usize),]); + assert_eq!(results, vec![(1, "1"), (3, "3"),]); - let mut i32_query = world.query::<&i32>(); - let results = i32_query.iter(&world).cloned().collect::>(); + let mut a_query = world.query::<&A>(); + let results = a_query.iter(&world).map(|a| a.0).collect::>(); assert_eq!(results, vec![1, 3, 2]); let entity_ref = world.entity(e2); assert_eq!( - entity_ref.get::(), - Some(&2), - "i32 is not in the removed bundle, so it should exist" + entity_ref.get::(), + Some(&A(2)), + "A is not in the removed bundle, so it should exist" ); assert_eq!( - entity_ref.get::(), + entity_ref.get::(), None, - "f64 is in the removed bundle, so it should not exist" + "B is in the removed bundle, so it should not exist" ); assert_eq!( - entity_ref.get::(), + entity_ref.get::(), None, - "usize is in the removed bundle, so it should not exist" + "TableStored is in the removed bundle, so it should not exist" ); } #[test] fn non_send_resource() { let mut world = World::default(); - world.insert_non_send(123i32); - world.insert_non_send(456i64); + world.insert_non_send_resource(123i32); + world.insert_non_send_resource(456i64); assert_eq!(*world.get_non_send_resource::().unwrap(), 123); assert_eq!(*world.get_non_send_resource_mut::().unwrap(), 456); } @@ -1071,7 +1191,7 @@ mod tests { #[should_panic] fn non_send_resource_panic() { let mut world = World::default(); - world.insert_non_send(0i32); + world.insert_non_send_resource(0i32); std::thread::spawn(move || { let _ = world.get_non_send_resource_mut::(); }) @@ -1119,7 +1239,7 @@ mod tests { #[should_panic] fn duplicate_components_panic() { let mut world = World::new(); - world.spawn().insert_bundle((1, 2)); + world.spawn().insert_bundle((A(1), A(2))); } #[test] @@ -1148,7 +1268,7 @@ mod tests { fn multiple_worlds_same_query_iter() { let mut world_a = World::new(); let world_b = World::new(); - let mut query = world_a.query::<&i32>(); + let mut query = world_a.query::<&A>(); query.iter(&world_a); query.iter(&world_b); } @@ -1156,19 +1276,19 @@ mod tests { #[test] fn query_filters_dont_collide_with_fetches() { let mut world = World::new(); - world.query_filtered::<&mut i32, Changed>(); + world.query_filtered::<&mut A, Changed>(); } #[test] fn filtered_query_access() { let mut world = World::new(); - let query = world.query_filtered::<&mut i32, Changed>(); + let query = world.query_filtered::<&mut A, Changed>(); let mut expected = FilteredAccess::::default(); - let i32_id = world.components.get_id(TypeId::of::()).unwrap(); - let f64_id = world.components.get_id(TypeId::of::()).unwrap(); - expected.add_write(i32_id); - expected.add_read(f64_id); + let a_id = world.components.get_id(TypeId::of::()).unwrap(); + let b_id = world.components.get_id(TypeId::of::()).unwrap(); + expected.add_write(a_id); + expected.add_read(b_id); assert!( query.component_access.eq(&expected), "ComponentId access from query fetch and query filter should be combined" @@ -1180,9 +1300,9 @@ mod tests { fn multiple_worlds_same_query_get() { let mut world_a = World::new(); let world_b = World::new(); - let mut query = world_a.query::<&i32>(); - let _ = query.get(&world_a, Entity::new(0)); - let _ = query.get(&world_b, Entity::new(0)); + let mut query = world_a.query::<&A>(); + let _ = query.get(&world_a, Entity::from_raw(0)); + let _ = query.get(&world_b, Entity::from_raw(0)); } #[test] @@ -1190,7 +1310,7 @@ mod tests { fn multiple_worlds_same_query_for_each() { let mut world_a = World::new(); let world_b = World::new(); - let mut query = world_a.query::<&i32>(); + let mut query = world_a.query::<&A>(); query.for_each(&world_a, |_| {}); query.for_each(&world_b, |_| {}); } @@ -1224,10 +1344,11 @@ mod tests { let (dropck1, dropped1) = DropCk::new_pair(); let (dropck2, dropped2) = DropCk::new_pair(); let mut world = World::default(); + world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); - world.spawn().insert(dropck1).insert(dropck2); + .spawn() + .insert(DropCkSparse(dropck1)) + .insert(DropCkSparse(dropck2)); assert_eq!(dropped1.load(Ordering::Relaxed), 1); assert_eq!(dropped2.load(Ordering::Relaxed), 0); drop(world); @@ -1238,15 +1359,13 @@ mod tests { #[test] fn clear_entities() { let mut world = World::default(); - world - .register_component(ComponentDescriptor::new::(StorageType::SparseSet)) - .unwrap(); + world.insert_resource::(0); - world.spawn().insert(1u32); - world.spawn().insert(1.0f32); + world.spawn().insert(A(1)); + world.spawn().insert(SparseStored(1)); - let mut q1 = world.query::<&u32>(); - let mut q2 = world.query::<&f32>(); + let mut q1 = world.query::<&A>(); + let mut q2 = world.query::<&SparseStored>(); assert_eq!(q1.iter(&world).len(), 1); assert_eq!(q2.iter(&world).len(), 1); @@ -1281,8 +1400,8 @@ mod tests { let mut world_a = World::default(); let mut world_b = World::default(); - let e1 = world_a.spawn().insert(1u32).id(); - let e2 = world_a.spawn().insert(2u32).id(); + let e1 = world_a.spawn().insert(A(1)).id(); + let e2 = world_a.spawn().insert(A(2)).id(); let e3 = world_a.entities().reserve_entity(); world_a.flush(); @@ -1292,7 +1411,7 @@ mod tests { .reserve_entities(world_a_max_entities as u32); world_b.entities.flush_as_invalid(); - let e4 = world_b.spawn().insert(4u32).id(); + let e4 = world_b.spawn().insert(A(4)).id(); assert_eq!( e4, Entity { @@ -1301,31 +1420,31 @@ mod tests { }, "new entity is created immediately after world_a's max entity" ); - assert!(world_b.get::(e1).is_none()); + assert!(world_b.get::(e1).is_none()); assert!(world_b.get_entity(e1).is_none()); - assert!(world_b.get::(e2).is_none()); + assert!(world_b.get::(e2).is_none()); assert!(world_b.get_entity(e2).is_none()); - assert!(world_b.get::(e3).is_none()); + assert!(world_b.get::(e3).is_none()); assert!(world_b.get_entity(e3).is_none()); - world_b.get_or_spawn(e1).unwrap().insert(1.0f32); + world_b.get_or_spawn(e1).unwrap().insert(B(1)); assert_eq!( - world_b.get::(e1), - Some(&1.0f32), + world_b.get::(e1), + Some(&B(1)), "spawning into 'world_a' entities works" ); - world_b.get_or_spawn(e4).unwrap().insert(4.0f32); + world_b.get_or_spawn(e4).unwrap().insert(B(4)); assert_eq!( - world_b.get::(e4), - Some(&4.0f32), + world_b.get::(e4), + Some(&B(4)), "spawning into existing `world_b` entities works" ); assert_eq!( - world_b.get::(e4), - Some(&4u32), + world_b.get::(e4), + Some(&A(4)), "spawning into existing `world_b` entities works" ); @@ -1338,13 +1457,13 @@ mod tests { "attempting to spawn on top of an entity with a mismatched entity generation fails" ); assert_eq!( - world_b.get::(e4), - Some(&4.0f32), + world_b.get::(e4), + Some(&B(4)), "failed mismatched spawn doesn't change existing entity" ); assert_eq!( - world_b.get::(e4), - Some(&4u32), + world_b.get::(e4), + Some(&A(4)), "failed mismatched spawn doesn't change existing entity" ); @@ -1355,10 +1474,10 @@ mod tests { world_b .get_or_spawn(high_non_existent_entity) .unwrap() - .insert(10.0f32); + .insert(B(10)); assert_eq!( - world_b.get::(high_non_existent_entity), - Some(&10.0f32), + world_b.get::(high_non_existent_entity), + Some(&B(10)), "inserting into newly allocated high / non-continous entity id works" ); @@ -1406,7 +1525,7 @@ mod tests { fn insert_or_spawn_batch() { let mut world = World::default(); let e0 = world.spawn().insert(A(0)).id(); - let e1 = Entity::new(1); + let e1 = Entity::from_raw(1); let values = vec![(e0, (B(0), C)), (e1, (B(1), C))]; @@ -1443,7 +1562,7 @@ mod tests { fn insert_or_spawn_batch_invalid() { let mut world = World::default(); let e0 = world.spawn().insert(A(0)).id(); - let e1 = Entity::new(1); + let e1 = Entity::from_raw(1); let e2 = world.spawn().id(); let invalid_e2 = Entity { generation: 1, diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index f735f36580f32..328df83834ed6 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -115,6 +115,18 @@ impl Access { .map(SparseSetIndex::get_sparse_set_index) .collect() } + + /// Returns all read accesses. + pub fn reads(&self) -> impl Iterator + '_ { + self.reads_and_writes + .difference(&self.writes) + .map(T::get_sparse_set_index) + } + + /// Returns all write accesses. + pub fn writes(&self) -> impl Iterator + '_ { + self.writes.ones().map(T::get_sparse_set_index) + } } #[derive(Clone, Eq, PartialEq)] @@ -174,6 +186,10 @@ impl FilteredAccess { self.with.union_with(&access.with); self.without.union_with(&access.without); } + + pub fn read_all(&mut self) { + self.access.read_all(); + } } pub struct FilteredAccessSet { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 9e20c208761e7..5c3ed28038f70 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, change_detection::Ticks, - component::{Component, ComponentId, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, query::{Access, FilteredAccess}, storage::{ComponentSparseSet, Table, Tables}, @@ -21,13 +21,13 @@ use std::{ /// /// See [`Query`](crate::system::Query) for a primer on queries. /// -/// # Basic WorldQueries +/// # Basic [`WorldQuery`]'s /// /// Here is a small list of the most important world queries to know about where `C` stands for a /// [`Component`] and `WQ` stands for a [`WorldQuery`]: /// - `&C`: Queries immutably for the component `C` /// - `&mut C`: Queries mutably for the component `C` -/// - `Option`: Queries the inner WorldQuery `WQ` but instead of discarding the entity if the world +/// - `Option`: Queries the inner [`WorldQuery`] `WQ` but instead of discarding the entity if the world /// query fails it returns [`None`]. See [`Query`](crate::system::Query). /// - `(WQ1, WQ2, ...)`: Queries all contained world queries allowing to query for more than one thing. /// This is the `And` operator for filters. See [`Or`]. @@ -43,8 +43,12 @@ use std::{ pub trait WorldQuery { type Fetch: for<'world, 'state> Fetch<'world, 'state, State = Self::State>; type State: FetchState; + type ReadOnlyFetch: for<'world, 'state> Fetch<'world, 'state, State = Self::State> + + ReadOnlyFetch; } +pub type QueryItem<'w, 's, Q> = <::Fetch as Fetch<'w, 's>>::Item; + pub trait Fetch<'world, 'state>: Sized { type Item; type State: FetchState; @@ -53,8 +57,8 @@ pub trait Fetch<'world, 'state>: Sized { /// /// # Safety /// - /// `state` must have been initialized (via [FetchState::init]) using the same `world` passed in - /// to this function. + /// `state` must have been initialized (via [`FetchState::init`]) using the same `world` passed + /// in to this function. unsafe fn init( world: &World, state: &Self::State, @@ -67,15 +71,15 @@ pub trait Fetch<'world, 'state>: Sized { /// for "dense" queries. If this returns true, [`Fetch::set_table`] and [`Fetch::table_fetch`] /// will be called for iterators. If this returns false, [`Fetch::set_archetype`] and /// [`Fetch::archetype_fetch`] will be called for iterators. - fn is_dense(&self) -> bool; + const IS_DENSE: bool; /// Adjusts internal state to account for the next [`Archetype`]. This will always be called on /// archetypes that match this [`Fetch`]. /// /// # Safety /// - /// `archetype` and `tables` must be from the [`World`] [`Fetch::init`] was called on. `state` must - /// be the [Self::State] this was initialized with. + /// `archetype` and `tables` must be from the [`World`] [`Fetch::init`] was called on. `state` + /// must be the [`Self::State`] this was initialized with. unsafe fn set_archetype(&mut self, state: &Self::State, archetype: &Archetype, tables: &Tables); /// Adjusts internal state to account for the next [`Table`]. This will always be called on tables @@ -84,7 +88,7 @@ pub trait Fetch<'world, 'state>: Sized { /// # Safety /// /// `table` must be from the [`World`] [`Fetch::init`] was called on. `state` must be the - /// [Self::State] this was initialized with. + /// [`Self::State`] this was initialized with. unsafe fn set_table(&mut self, state: &Self::State, table: &Table); /// Fetch [`Self::Item`] for the given `archetype_index` in the current [`Archetype`]. This must @@ -128,12 +132,17 @@ pub unsafe trait FetchState: Send + Sync + Sized { fn matches_table(&self, table: &Table) -> bool; } -/// A fetch that is read only. This must only be implemented for read-only fetches. +/// A fetch that is read only. +/// +/// # Safety +/// +/// This must only be implemented for read-only fetches. pub unsafe trait ReadOnlyFetch {} impl WorldQuery for Entity { type Fetch = EntityFetch; type State = EntityState; + type ReadOnlyFetch = EntityFetch; } /// The [`Fetch`] of [`Entity`]. @@ -178,10 +187,7 @@ impl<'w, 's> Fetch<'w, 's> for EntityFetch { type Item = Entity; type State = EntityState; - #[inline] - fn is_dense(&self) -> bool { - true - } + const IS_DENSE: bool = true; unsafe fn init( _world: &World, @@ -223,12 +229,12 @@ impl<'w, 's> Fetch<'w, 's> for EntityFetch { impl WorldQuery for &T { type Fetch = ReadFetch; type State = ReadState; + type ReadOnlyFetch = ReadFetch; } /// The [`FetchState`] of `&T`. pub struct ReadState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } @@ -236,10 +242,9 @@ pub struct ReadState { // read unsafe impl FetchState for ReadState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); ReadState { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -275,7 +280,6 @@ unsafe impl FetchState for ReadState { /// The [`Fetch`] of `&T`. pub struct ReadFetch { - storage_type: StorageType, table_components: NonNull, entity_table_rows: *const usize, entities: *const Entity, @@ -285,7 +289,6 @@ pub struct ReadFetch { impl Clone for ReadFetch { fn clone(&self) -> Self { Self { - storage_type: self.storage_type, table_components: self.table_components, entity_table_rows: self.entity_table_rows, entities: self.entities, @@ -301,13 +304,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { type Item = &'w T; type State = ReadState; - #[inline] - fn is_dense(&self) -> bool { - match self.storage_type { + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } - } + }; unsafe fn init( world: &World, @@ -316,13 +318,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { _change_tick: u32, ) -> Self { let mut value = Self { - storage_type: state.storage_type, table_components: NonNull::dangling(), entities: ptr::null::(), entity_table_rows: ptr::null::(), sparse_set: ptr::null::(), }; - if state.storage_type == StorageType::SparseSet { + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world .storages() .sparse_sets @@ -339,7 +340,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { archetype: &Archetype, tables: &Tables, ) { - match state.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = archetype.entity_table_rows().as_ptr(); let column = tables[archetype.table_id()] @@ -362,7 +363,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match self.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); &*self.table_components.as_ptr().add(table_row) @@ -383,11 +384,11 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ReadFetch { impl WorldQuery for &mut T { type Fetch = WriteFetch; type State = WriteState; + type ReadOnlyFetch = ReadOnlyWriteFetch; } /// The [`Fetch`] of `&mut T`. pub struct WriteFetch { - storage_type: StorageType, table_components: NonNull, table_ticks: *const UnsafeCell, entities: *const Entity, @@ -400,7 +401,6 @@ pub struct WriteFetch { impl Clone for WriteFetch { fn clone(&self) -> Self { Self { - storage_type: self.storage_type, table_components: self.table_components, table_ticks: self.table_ticks, entities: self.entities, @@ -412,10 +412,31 @@ impl Clone for WriteFetch { } } +/// The [`ReadOnlyFetch`] of `&mut T`. +pub struct ReadOnlyWriteFetch { + table_components: NonNull, + entities: *const Entity, + entity_table_rows: *const usize, + sparse_set: *const ComponentSparseSet, +} + +/// SAFETY: access is read only +unsafe impl ReadOnlyFetch for ReadOnlyWriteFetch {} + +impl Clone for ReadOnlyWriteFetch { + fn clone(&self) -> Self { + Self { + table_components: self.table_components, + entities: self.entities, + entity_table_rows: self.entity_table_rows, + sparse_set: self.sparse_set, + } + } +} + /// The [`FetchState`] of `&mut T`. pub struct WriteState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } @@ -423,10 +444,9 @@ pub struct WriteState { // written unsafe impl FetchState for WriteState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); WriteState { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -464,13 +484,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { type Item = Mut<'w, T>; type State = WriteState; - #[inline] - fn is_dense(&self) -> bool { - match self.storage_type { + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } - } + }; unsafe fn init( world: &World, @@ -479,7 +498,6 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { change_tick: u32, ) -> Self { let mut value = Self { - storage_type: state.storage_type, table_components: NonNull::dangling(), entities: ptr::null::(), entity_table_rows: ptr::null::(), @@ -488,7 +506,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { last_change_tick, change_tick, }; - if state.storage_type == StorageType::SparseSet { + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world .storages() .sparse_sets @@ -505,7 +523,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { archetype: &Archetype, tables: &Tables, ) { - match state.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = archetype.entity_table_rows().as_ptr(); let column = tables[archetype.table_id()] @@ -527,7 +545,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match self.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); Mut { @@ -568,9 +586,88 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WriteFetch { } } +impl<'w, 's, T: Component> Fetch<'w, 's> for ReadOnlyWriteFetch { + type Item = &'w T; + type State = WriteState; + + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { + StorageType::Table => true, + StorageType::SparseSet => false, + } + }; + + unsafe fn init( + world: &World, + state: &Self::State, + _last_change_tick: u32, + _change_tick: u32, + ) -> Self { + let mut value = Self { + table_components: NonNull::dangling(), + entities: ptr::null::(), + entity_table_rows: ptr::null::(), + sparse_set: ptr::null::(), + }; + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { + value.sparse_set = world + .storages() + .sparse_sets + .get(state.component_id) + .unwrap(); + } + value + } + + #[inline] + unsafe fn set_archetype( + &mut self, + state: &Self::State, + archetype: &Archetype, + tables: &Tables, + ) { + match T::Storage::STORAGE_TYPE { + StorageType::Table => { + self.entity_table_rows = archetype.entity_table_rows().as_ptr(); + let column = tables[archetype.table_id()] + .get_column(state.component_id) + .unwrap(); + self.table_components = column.get_data_ptr().cast::(); + } + StorageType::SparseSet => self.entities = archetype.entities().as_ptr(), + } + } + + #[inline] + unsafe fn set_table(&mut self, state: &Self::State, table: &Table) { + let column = table.get_column(state.component_id).unwrap(); + self.table_components = column.get_data_ptr().cast::(); + } + + #[inline] + unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { + match T::Storage::STORAGE_TYPE { + StorageType::Table => { + let table_row = *self.entity_table_rows.add(archetype_index); + &*self.table_components.as_ptr().add(table_row) + } + StorageType::SparseSet => { + let entity = *self.entities.add(archetype_index); + &*(*self.sparse_set).get(entity).unwrap().cast::() + } + } + } + + #[inline] + unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item { + &*self.table_components.as_ptr().add(table_row) + } +} + impl WorldQuery for Option { type Fetch = OptionFetch; type State = OptionState; + type ReadOnlyFetch = OptionFetch; } /// The [`Fetch`] of `Option`. @@ -580,7 +677,7 @@ pub struct OptionFetch { matches: bool, } -/// SAFETY: OptionFetch is read only because T is read only +/// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyFetch for OptionFetch {} /// The [`FetchState`] of `Option`. @@ -625,10 +722,7 @@ impl<'w, 's, T: Fetch<'w, 's>> Fetch<'w, 's> for OptionFetch { type Item = Option; type State = OptionState; - #[inline] - fn is_dense(&self) -> bool { - self.fetch.is_dense() - } + const IS_DENSE: bool = T::IS_DENSE; unsafe fn init( world: &World, @@ -693,12 +787,14 @@ impl<'w, 's, T: Fetch<'w, 's>> Fetch<'w, 's> for OptionFetch { /// # Examples /// /// ``` -/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::query::ChangeTrackers; /// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] +/// # #[derive(Component, Debug)] /// # struct Name {}; +/// # #[derive(Component)] /// # struct Transform {}; /// # /// fn print_moving_objects_system(query: Query<(&Name, ChangeTrackers)>) { @@ -710,7 +806,7 @@ impl<'w, 's, T: Fetch<'w, 's>> Fetch<'w, 's> for OptionFetch { /// } /// } /// } -/// # print_moving_objects_system.system(); +/// # bevy_ecs::system::assert_is_system(print_moving_objects_system); /// ``` #[derive(Clone)] pub struct ChangeTrackers { @@ -747,12 +843,12 @@ impl ChangeTrackers { impl WorldQuery for ChangeTrackers { type Fetch = ChangeTrackersFetch; type State = ChangeTrackersState; + type ReadOnlyFetch = ChangeTrackersFetch; } /// The [`FetchState`] of [`ChangeTrackers`]. pub struct ChangeTrackersState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } @@ -760,10 +856,9 @@ pub struct ChangeTrackersState { // read unsafe impl FetchState for ChangeTrackersState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); Self { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -799,7 +894,6 @@ unsafe impl FetchState for ChangeTrackersState { /// The [`Fetch`] of [`ChangeTrackers`]. pub struct ChangeTrackersFetch { - storage_type: StorageType, table_ticks: *const ComponentTicks, entity_table_rows: *const usize, entities: *const Entity, @@ -812,7 +906,6 @@ pub struct ChangeTrackersFetch { impl Clone for ChangeTrackersFetch { fn clone(&self) -> Self { Self { - storage_type: self.storage_type, table_ticks: self.table_ticks, entity_table_rows: self.entity_table_rows, entities: self.entities, @@ -831,13 +924,12 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { type Item = ChangeTrackers; type State = ChangeTrackersState; - #[inline] - fn is_dense(&self) -> bool { - match self.storage_type { + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } - } + }; unsafe fn init( world: &World, @@ -846,7 +938,6 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { change_tick: u32, ) -> Self { let mut value = Self { - storage_type: state.storage_type, table_ticks: ptr::null::(), entities: ptr::null::(), entity_table_rows: ptr::null::(), @@ -855,7 +946,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { last_change_tick, change_tick, }; - if state.storage_type == StorageType::SparseSet { + if T::Storage::STORAGE_TYPE == StorageType::SparseSet { value.sparse_set = world .storages() .sparse_sets @@ -872,7 +963,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { archetype: &Archetype, tables: &Tables, ) { - match state.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { self.entity_table_rows = archetype.entity_table_rows().as_ptr(); let column = tables[archetype.table_id()] @@ -894,7 +985,7 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for ChangeTrackersFetch { #[inline] unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> Self::Item { - match self.storage_type { + match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *self.entity_table_rows.add(archetype_index); ChangeTrackers { @@ -940,12 +1031,7 @@ macro_rules! impl_tuple_fetch { ($($name::init(_world, $name, _last_change_tick, _change_tick),)*) } - - #[inline] - fn is_dense(&self) -> bool { - let ($($name,)*) = self; - true $(&& $name.is_dense())* - } + const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; #[inline] unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &Archetype, _tables: &Tables) { @@ -1008,6 +1094,7 @@ macro_rules! impl_tuple_fetch { impl<$($name: WorldQuery),*> WorldQuery for ($($name,)*) { type Fetch = ($($name::Fetch,)*); type State = ($($name::State,)*); + type ReadOnlyFetch = ($($name::ReadOnlyFetch,)*); } /// SAFETY: each item in the tuple is read only @@ -1016,4 +1103,159 @@ macro_rules! impl_tuple_fetch { }; } +/// The `AnyOf` query parameter fetches entities with any of the component types included in T. +/// +/// `Query>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), (Or(With, With, With)>`. +/// Each of the components in `T` is returned as an `Option`, as with `Option` queries. +/// Entities are guaranteed to have at least one of the components in `T`. +pub struct AnyOf(T); + +macro_rules! impl_anytuple_fetch { + ($(($name: ident, $state: ident)),*) => { + #[allow(non_snake_case)] + impl<'w, 's, $($name: Fetch<'w, 's>),*> Fetch<'w, 's> for AnyOf<($(($name, bool),)*)> { + type Item = ($(Option<$name::Item>,)*); + type State = AnyOf<($($name::State,)*)>; + + #[allow(clippy::unused_unit)] + unsafe fn init(_world: &World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self { + let ($($name,)*) = &state.0; + AnyOf(($(($name::init(_world, $name, _last_change_tick, _change_tick), false),)*)) + } + + + const IS_DENSE: bool = true $(&& $name::IS_DENSE)*; + + #[inline] + unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &Archetype, _tables: &Tables) { + let ($($name,)*) = &mut self.0; + let ($($state,)*) = &_state.0; + $( + $name.1 = $state.matches_archetype(_archetype); + if $name.1 { + $name.0.set_archetype($state, _archetype, _tables); + } + )* + } + + #[inline] + unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) { + let ($($name,)*) = &mut self.0; + let ($($state,)*) = &_state.0; + $( + $name.1 = $state.matches_table(_table); + if $name.1 { + $name.0.set_table($state, _table); + } + )* + } + + #[inline] + #[allow(clippy::unused_unit)] + unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item { + let ($($name,)*) = &mut self.0; + ($( + $name.1.then(|| $name.0.table_fetch(_table_row)), + )*) + } + + #[inline] + #[allow(clippy::unused_unit)] + unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item { + let ($($name,)*) = &mut self.0; + ($( + $name.1.then(|| $name.0.archetype_fetch(_archetype_index)), + )*) + } + } + + // SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple + #[allow(non_snake_case)] + #[allow(clippy::unused_unit)] + unsafe impl<$($name: FetchState),*> FetchState for AnyOf<($($name,)*)> { + fn init(_world: &mut World) -> Self { + AnyOf(($($name::init(_world),)*)) + } + + fn update_component_access(&self, _access: &mut FilteredAccess) { + let ($($name,)*) = &self.0; + $($name.update_component_access(_access);)* + } + + fn update_archetype_component_access(&self, _archetype: &Archetype, _access: &mut Access) { + let ($($name,)*) = &self.0; + $( + if $name.matches_archetype(_archetype) { + $name.update_archetype_component_access(_archetype, _access); + } + )* + } + + fn matches_archetype(&self, _archetype: &Archetype) -> bool { + let ($($name,)*) = &self.0; + false $(|| $name.matches_archetype(_archetype))* + } + + fn matches_table(&self, _table: &Table) -> bool { + let ($($name,)*) = &self.0; + false $(|| $name.matches_table(_table))* + } + } + + impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> { + type Fetch = AnyOf<($(($name::Fetch, bool),)*)>; + type ReadOnlyFetch = AnyOf<($(($name::ReadOnlyFetch, bool),)*)>; + + type State = AnyOf<($($name::State,)*)>; + } + + /// SAFETY: each item in the tuple is read only + unsafe impl<$($name: ReadOnlyFetch),*> ReadOnlyFetch for AnyOf<($(($name, bool),)*)> {} + + }; +} + all_tuples!(impl_tuple_fetch, 0, 15, F, S); +all_tuples!(impl_anytuple_fetch, 0, 15, F, S); + +/// [`Fetch`] that does not actually fetch anything +/// +/// Mostly useful when something is generic over the Fetch and you don't want to fetch as you will discard the result +pub struct NopFetch { + state: PhantomData, +} + +impl<'w, 's, State: FetchState> Fetch<'w, 's> for NopFetch { + type Item = (); + type State = State; + + const IS_DENSE: bool = true; + + #[inline(always)] + unsafe fn init( + _world: &World, + _state: &Self::State, + _last_change_tick: u32, + _change_tick: u32, + ) -> Self { + Self { state: PhantomData } + } + + #[inline(always)] + unsafe fn set_archetype( + &mut self, + _state: &Self::State, + _archetype: &Archetype, + _tables: &Tables, + ) { + } + + #[inline(always)] + unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} + + #[inline(always)] + unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item {} + + #[inline(always)] + unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item {} +} diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 69dd298cd31e0..e84ec3db26394 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,8 +1,8 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, - component::{Component, ComponentId, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, - query::{Access, Fetch, FetchState, FilteredAccess, WorldQuery}, + query::{Access, Fetch, FetchState, FilteredAccess, ReadOnlyFetch, WorldQuery}, storage::{ComponentSparseSet, Table, Tables}, world::World, }; @@ -50,12 +50,14 @@ where /// # Examples /// /// ``` -/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::query::With; /// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] -/// # struct IsBeautiful {}; +/// # #[derive(Component)] +/// # struct IsBeautiful; +/// # #[derive(Component)] /// # struct Name { name: &'static str }; /// # /// fn compliment_entity_system(query: Query<&Name, With>) { @@ -63,35 +65,33 @@ where /// println!("{} is looking lovely today!", name.name); /// } /// } -/// # compliment_entity_system.system(); +/// # bevy_ecs::system::assert_is_system(compliment_entity_system); /// ``` pub struct With(PhantomData); impl WorldQuery for With { type Fetch = WithFetch; type State = WithState; + type ReadOnlyFetch = WithFetch; } /// The [`Fetch`] of [`With`]. pub struct WithFetch { - storage_type: StorageType, marker: PhantomData, } /// The [`FetchState`] of [`With`]. pub struct WithState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } // SAFETY: no component access or archetype component access unsafe impl FetchState for WithState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); Self { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -124,20 +124,21 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithFetch { unsafe fn init( _world: &World, - state: &Self::State, + _state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> Self { Self { - storage_type: state.storage_type, marker: PhantomData, } } - #[inline] - fn is_dense(&self) -> bool { - self.storage_type == StorageType::Table - } + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { + StorageType::Table => true, + StorageType::SparseSet => false, + } + }; #[inline] unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} @@ -162,6 +163,9 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithFetch { } } +// SAFETY: no component access or archetype component access +unsafe impl ReadOnlyFetch for WithFetch {} + /// Filter that selects entities without a component `T`. /// /// This is the negation of [`With`]. @@ -169,12 +173,14 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithFetch { /// # Examples /// /// ``` -/// # use bevy_ecs::system::Query; +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::query::Without; /// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] +/// # #[derive(Component)] /// # struct Permit; +/// # #[derive(Component)] /// # struct Name { name: &'static str }; /// # /// fn no_permit_system(query: Query<&Name, Without>) { @@ -182,35 +188,33 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithFetch { /// println!("{} has no permit!", name.name); /// } /// } -/// # no_permit_system.system(); +/// # bevy_ecs::system::assert_is_system(no_permit_system); /// ``` pub struct Without(PhantomData); impl WorldQuery for Without { type Fetch = WithoutFetch; type State = WithoutState; + type ReadOnlyFetch = WithoutFetch; } /// The [`Fetch`] of [`Without`]. pub struct WithoutFetch { - storage_type: StorageType, marker: PhantomData, } /// The [`FetchState`] of [`Without`]. pub struct WithoutState { component_id: ComponentId, - storage_type: StorageType, marker: PhantomData, } // SAFETY: no component access or archetype component access unsafe impl FetchState for WithoutState { fn init(world: &mut World) -> Self { - let component_info = world.components.get_or_insert_info::(); + let component_id = world.init_component::(); Self { - component_id: component_info.id(), - storage_type: component_info.storage_type(), + component_id, marker: PhantomData, } } @@ -243,20 +247,21 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithoutFetch { unsafe fn init( _world: &World, - state: &Self::State, + _state: &Self::State, _last_change_tick: u32, _change_tick: u32, ) -> Self { Self { - storage_type: state.storage_type, marker: PhantomData, } } - #[inline] - fn is_dense(&self) -> bool { - self.storage_type == StorageType::Table - } + const IS_DENSE: bool = { + match T::Storage::STORAGE_TYPE { + StorageType::Table => true, + StorageType::SparseSet => false, + } + }; #[inline] unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {} @@ -281,6 +286,9 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithoutFetch { } } +// SAFETY: no component access or archetype component access +unsafe impl ReadOnlyFetch for WithoutFetch {} + /// A filter that tests if any of the given filters apply. /// /// This is useful for example if a system with multiple components in a query only wants to run @@ -292,14 +300,16 @@ impl<'w, 's, T: Component> Fetch<'w, 's> for WithoutFetch { /// # Examples /// /// ``` +/// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; -/// # use bevy_ecs::system::Query; -/// # use bevy_ecs::system::IntoSystem; /// # use bevy_ecs::query::Changed; /// # use bevy_ecs::query::Or; +/// # use bevy_ecs::system::IntoSystem; +/// # use bevy_ecs::system::Query; /// # -/// # #[derive(Debug)] +/// # #[derive(Component, Debug)] /// # struct Color {}; +/// # #[derive(Component)] /// # struct Style {}; /// # /// fn print_cool_entity_system(query: Query, Changed diff --git a/examples/wasm/winit_wasm.rs b/examples/wasm/winit_wasm.rs deleted file mode 100644 index f7a4353dfc3f2..0000000000000 --- a/examples/wasm/winit_wasm.rs +++ /dev/null @@ -1,89 +0,0 @@ -use bevy::{ - input::{ - keyboard::KeyboardInput, - mouse::{MouseButtonInput, MouseMotion, MouseWheel}, - }, - prelude::*, -}; - -fn main() { - App::new() - .insert_resource(WindowDescriptor { - width: 300., - height: 300., - ..Default::default() - }) - .add_plugins(DefaultPlugins) - // One time greet - .add_startup_system(hello_wasm_system) - // Track ticks (sanity check, whether game loop is running) - .add_system(counter) - // Track input events - .add_system(track_input_events) - .run(); -} - -fn hello_wasm_system() { - info!("hello wasm"); -} - -fn counter(mut state: Local, time: Res