diff --git a/.github/bors.toml b/.github/bors.toml index 4e8d0332954d4e..7d7911af1b40ff 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -13,6 +13,7 @@ status = [ "check-missing-examples-in-docs", "check-unused-dependencies", "ci", + "miri", "check-benches", ] diff --git a/.github/contributing/example_style_guide.md b/.github/contributing/example_style_guide.md index 782d5550c73c7f..8b6ee4c7af0cdd 100644 --- a/.github/contributing/example_style_guide.md +++ b/.github/contributing/example_style_guide.md @@ -36,7 +36,7 @@ For more advice on writing examples, see the [relevant section](../../CONTRIBUTI 2. Prefer `for` loops over `.for_each`. The latter is faster (for now), but it is less clear for beginners, less idiomatic, and less flexible. 3. Use `.single` and `.single_mut` where appropriate. 4. In Queries, prefer `With` filters over actually fetching unused data with `&T`. -5. Prefer disjoint queries using `With` and `Without` over query sets when you need more than one query in a single system. +5. Prefer disjoint queries using `With` and `Without` over param sets when you need more than one query in a single system. 6. Prefer structs with named fields over tuple structs except in the case of single-field wrapper types. 7. Use enum-labels over string-labels for system / stage / etc. labels. diff --git a/.github/example-run/headless_defaults.ron b/.github/example-run/headless_defaults.ron new file mode 100644 index 00000000000000..22e43495b5e42a --- /dev/null +++ b/.github/example-run/headless_defaults.ron @@ -0,0 +1,3 @@ +( + exit_after: Some(100) +) diff --git a/.github/linters/markdown-link-check.json b/.github/linters/markdown-link-check.json index 67bd0974f2efa9..9e2a95efc3ff67 100644 --- a/.github/linters/markdown-link-check.json +++ b/.github/linters/markdown-link-check.json @@ -3,6 +3,9 @@ { "pattern": "^https?://github\\.com/" }, + { + "pattern": "^https?://docs\\.github\\.com/" + }, { "pattern": "^https?://reddit\\.com/" } diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7892ff102f8e0d..722f3aabe3aa69 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,3 +6,22 @@ ## Solution - Describe the solution used to achieve the objective above. + +--- + +## Changelog + +> This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section. + +- What changed as a result of this PR? +- If applicable, organize changes under "Added", "Changed", or "Fixed" sub-headings +- Stick to one or two sentences. If more detail is needed for a particular change, consider adding it to the "Solution" section + - If you can't summarize the work, your change may be unreasonably large / unrelated. Consider splitting your PR to make it easier to review and merge! + +## Migration Guide + +> This section is optional. If there are no breaking changes, you can delete this section. + +- If this PR is a breaking change (relative to the last release of Bevy), describe how a user might need to migrate their code to support these changes +- Simply adding new functionality is not a breaking change. +- Fixing behavior that was definitely a bug, rather than a questionable design choice is not a breaking change. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c49157a7fd8240..428b0e359fc692 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.cargo/bin/ @@ -50,7 +50,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.cargo/bin/ @@ -70,12 +70,44 @@ jobs: # See tools/ci/src/main.rs for the commands this runs run: cargo run -p ci -- nonlocal + miri: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-ci-${{ hashFiles('**/Cargo.toml') }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: miri + override: true + - name: Install alsa and udev + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + - name: CI job + run: cargo miri test -p bevy_ecs + env: + # -Zrandomize-layout makes sure we dont rely on the layout of anything that might change + RUSTFLAGS: -Zrandomize-layout + # -Zmiri-disable-isolation is needed because our executor uses `fastrand` which accesses system time. + # -Zmiri-ignore-leaks is needed because running bevy_ecs tests finds a memory leak but its impossible + # to track down because allocids are nondeterministic. + # -Zmiri-tag-raw-pointers is not strictly "necessary" but enables a lot of extra UB checks relating + # to raw pointer aliasing rules that we should be trying to uphold. + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-ignore-leaks -Zmiri-tag-raw-pointers + check-benches: runs-on: ubuntu-latest needs: ci steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.cargo/bin/ @@ -101,7 +133,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.cargo/bin/ @@ -128,7 +160,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: toolchain: stable - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.cargo/bin/ @@ -228,7 +260,7 @@ jobs: sudo apt-get update sudo apt install -y xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.cargo/bin/ @@ -242,13 +274,13 @@ jobs: toolchain: stable - name: Build bevy run: | - 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" + 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,bevy_audio,vorbis" - 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 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" + 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,bevy_audio,vorbis" sleep 10 done zip traces.zip trace*.json @@ -295,7 +327,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/.cargo/bin/ diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000000..3ba82d145d2974 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,56 @@ +name: Deploy Docs + +on: + push: + branches: + - 'main' + +env: + CARGO_TERM_COLOR: always + RUSTDOCFLAGS: --html-in-header header.html + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Install alsa and udev + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev + + # This does the following: + # - Replaces the docs icon with one that clearly denotes it's not the released package on crates.io + # - Adds a meta tag that forces Google not to index any page on the site. + - name: Pre-docs-build + run: | + sed -i.bak "s/icon.png/icon-docs-dev.png/" src/lib.rs + echo "" > header.html + + - name: Build docs + run: cargo doc --all-features --no-deps -p bevy + + # This adds the following: + # - A top level redirect to the bevy crate documentation + # - A CNAME file for redirecting the docs domain to the API reference + # - A robots.txt file to forbid any crawling of the site (to defer to the docs.rs site on search engines). + # - A .nojekyll file to disable Jekyll GitHub Pages builds. + - name: Finalize documentation + run: | + echo "" > target/doc/index.html + echo "dev-docs.bevyengine.org" > target/doc/CNAME + echo "User-Agent: *\nDisallow: /" > target/doc/robots.txt + touch target/doc/.nojekyll + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@v4.3.0 + with: + branch: gh-pages + folder: target/doc diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index f3db038aaa191f..7cc954acf9c808 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -18,7 +18,7 @@ jobs: toolchain: stable override: true - - uses: actions/cache@v2.1.6 + - uses: actions/cache@v3 with: path: | target diff --git a/CHANGELOG.md b/CHANGELOG.md index 9275603a37875b..a70b76da9460dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,294 @@ current changes on git with [previous release tags][git_tag_comparison]. [git_tag_comparison]: https://github.com/bevyengine/bevy/compare/v0.6.0...main +## Version 0.7.0 (2022-04-15) + +### Added + +- [Mesh Skinning][4238] +- [Animation Player][4375] +- [Gltf animations][3751] +- [Mesh vertex buffer layouts][3959] +- [Render to a texture][3412] +- [KTX2/DDS/.basis compressed texture support][3884] +- [Audio control - play, pause, volume, speed, loop][3948] +- [Auto-label function systems with SystemTypeIdLabel][4224] +- [Query::get_many][4298] +- [Dynamic light clusters][3968] +- [Always update clusters and remove per-frame allocations][4169] +- [`ParamSet` for conflicting `SystemParam`:s][2765] +- [default() shorthand][4071] +- [use marker components for cameras instead of name strings][3635] +- [Implement `WorldQuery` derive macro][2713] +- [Implement AnyOf queries][2889] +- [Compute Pipeline Specialization][3979] +- [Make get_resource (and friends) infallible][4047] +- [bevy_pbr: Support flipping tangent space normal map y for DirectX normal maps][4433] +- [Faster view frustum culling][4181] +- [Use storage buffers for clustered forward point lights][3989] +- [Add &World as SystemParam][2923] +- [Add text wrapping support to Text2d][4347] +- [Scene Viewer to display glTF files][4183] +- [Internal Asset Hot Reloading][3966] +- [Add FocusPolicy to NodeBundle and ImageBundle][3952] +- [Allow iter combinations on queries with filters][3656] +- [bevy_render: Support overriding wgpu features and limits][3912] +- [bevy_render: Use RenderDevice to get limits/features and expose AdapterInfo][3931] +- [Reduce power usage with configurable event loop][3974] +- [can specify an anchor for a sprite][3463] +- [Implement len and is_empty for EventReaders][2969] +- [Add more FromWorld implementations][3945] +- [Add cart's fork of ecs_bench_suite][4225] +- [bevy_derive: Add derives for `Deref` and `DerefMut`][4328] +- [Add clear_schedule][3941] +- [Add Query::contains][3090] +- [bevy_render: Support removal of nodes, edges, subgraphs][3048] +- [Implement init_resource for `Commands` and `World`][3079] +- [Added method to restart the current state][3328] +- [Simplify sending empty events][2935] +- [impl Command for `impl FnOnce(&mut World)`][2996] +- [Useful error message when two assets have the save UUID][3739] +- [bevy_asset: Add AssetServerSettings watch_for_changes member][3643] +- [Add conversio from Color to u32][4088] +- [Introduce `SystemLabel`'s for `RenderAssetPlugin`, and change `Image` preparation system to run before others][3917] +- [Add a helper for storage buffers similar to `UniformVec`][4079] +- [StandardMaterial: expose a cull_mode option][3982] +- [Expose draw indirect][4056] +- [Add view transform to view uniform][3885] +- [Add a size method on Image.][3696] +- [add Visibility for lights][3958] +- [bevy_render: Provide a way to opt-out of the built-in frustum culling][3711] +- [use error scope to handle errors on shader module creation][3675] +- [include sources in shader validation error][3724] +- [insert the gltf mesh name on the entity if there is one][4119] +- [expose extras from gltf nodes][2154] +- [gltf: add a name to nodes without names][4396] +- [Enable drag-and-drop events on windows][3772] +- [Add transform hierarchy stress test][4170] +- [Add TransformBundle][3054] +- [Add Transform::rotate_around method][3107] +- [example on how to create an animation in code][4399] +- [Add examples for Transforms][2441] +- [Add mouse grab example][4114] +- [examples: add screenspace texture shader example][4063] +- [Add generic systems example][2636] +- [add examples on how to have a data source running in another thread / in a task pool thread][2915] +- [Simple 2d rotation example][3065] +- [Add move sprite example.][2414] +- [add an example using UI & states to create a game menu][2960] +- [CI runs `cargo miri test -p bevy_ecs`][4310] +- [Tracy spans around main 3D passes][4182] +- [Add automatic docs deployment to GitHub Pages][3535] + +### Changed + +- [Proper prehashing][3963] +- [Move import_path definitions into shader source][3976] +- [Make `System` responsible for updating its own archetypes][4115] +- [Some small changes related to run criteria piping][3923] +- [Remove unnecessary system labels][4340] +- [Increment last event count on next instead of iter][2382] +- [Obviate the need for `RunSystem`, and remove it][3817] +- [Cleanup some things which shouldn't be components][2982] +- [Remove the config api][3633] +- [Deprecate `.system`][3302] +- [Hide docs for concrete impls of Fetch, FetchState, and SystemParamState][4250] +- [Move the CoreStage::Startup to a seperate StartupSchedule label][2434] +- [`iter_mut` on Assets: send modified event only when asset is iterated over][3565] +- [check if resource for asset already exists before adding it][3560] +- [bevy_render: Batch insertion for prepare_uniform_components][4179] +- [Change default `ColorMaterial` color to white][3981] +- [bevy_render: Only auto-disable mappable primary buffers for discrete GPUs][3803] +- [bevy_render: Do not automatically enable MAPPABLE_PRIMARY_BUFFERS][3698] +- [increase the maximum number of point lights with shadows to the max supported by the device][4435] +- [perf: only recalculate frusta of changed lights][4086] +- [bevy_pbr: Optimize assign_lights_to_clusters][3984] +- [improve error messages for render graph runner][3930] +- [Skinned extraction speedup][4428] +- [Sprites - keep color as 4 f32][4361] +- [Change scaling mode to FixedHorizontal][4055] +- [Replace VSync with PresentMode][3812] +- [do not set cursor grab on window creation if not asked for][3617] +- [bevy_transform: Use Changed in the query for much faster transform_propagate_system][4180] +- [Split bevy_hierarchy out from bevy_transform][4168] +- [Make transform builder methods const][3045] +- [many_cubes: Add a cube pattern suitable for benchmarking culling changes][4126] +- [Make many_cubes example more interesting][4015] +- [Run tests (including doc tests) in `cargo run -p ci` command][3849] +- [Use more ergonomic span syntax][4246] + +### Fixed + +- [Remove unsound lifetime annotations on `EntityMut`][4096] +- [Remove unsound lifetime annotations on `Query` methods][4243] +- [Remove `World::components_mut`][4092] +- [unsafeify `World::entities_mut`][4093] +- [Use ManuallyDrop instead of forget in insert_resource_with_id][2947] +- [Backport soundness fix][3685] +- [Fix clicked UI nodes getting reset when hovering child nodes][4194] +- [Fix ui interactions when cursor disappears suddenly][3926] +- [Fix node update][3785] +- [Fix derive(SystemParam) macro][4400] +- [SystemParam Derive fixes][2838] +- [Do not crash if RenderDevice doesn't exist][4427] +- [Fixed case of R == G, following original conversion formula][4383] +- [Fixed the frustum-sphere collision and added tests][4035] +- [bevy_render: Fix Quad flip][3741] +- [Fix HDR asset support][3795] +- [fix cluster tiling calculations][4148] +- [bevy_pbr: Do not panic when more than 256 point lights are added the scene][3697] +- [fix issues with too many point lights][3916] +- [shader preprocessor - do not import if scope is not valid][4012] +- [support all line endings in shader preprocessor][3603] +- [Fix animation: shadow and wireframe support][4367] +- [add AnimationPlayer component only on scene roots that are also animation roots][4417] +- [Fix loading non-TriangleList meshes without normals in gltf loader][4376] +- [gltf-loader: disable backface culling if material is double-sided][4270] +- [Fix glTF perspective camera projection][4006] +- [fix mul_vec3 transformation order: should be scale -> rotate -> translate][3811] + +[2154]: https://github.com/bevyengine/bevy/pull/2154 +[2382]: https://github.com/bevyengine/bevy/pull/2382 +[2414]: https://github.com/bevyengine/bevy/pull/2414 +[2434]: https://github.com/bevyengine/bevy/pull/2434 +[2441]: https://github.com/bevyengine/bevy/pull/2441 +[2636]: https://github.com/bevyengine/bevy/pull/2636 +[2713]: https://github.com/bevyengine/bevy/pull/2713 +[2765]: https://github.com/bevyengine/bevy/pull/2765 +[2838]: https://github.com/bevyengine/bevy/pull/2838 +[2889]: https://github.com/bevyengine/bevy/pull/2889 +[2915]: https://github.com/bevyengine/bevy/pull/2915 +[2923]: https://github.com/bevyengine/bevy/pull/2923 +[2935]: https://github.com/bevyengine/bevy/pull/2935 +[2947]: https://github.com/bevyengine/bevy/pull/2947 +[2960]: https://github.com/bevyengine/bevy/pull/2960 +[2969]: https://github.com/bevyengine/bevy/pull/2969 +[2982]: https://github.com/bevyengine/bevy/pull/2982 +[2996]: https://github.com/bevyengine/bevy/pull/2996 +[3045]: https://github.com/bevyengine/bevy/pull/3045 +[3048]: https://github.com/bevyengine/bevy/pull/3048 +[3054]: https://github.com/bevyengine/bevy/pull/3054 +[3065]: https://github.com/bevyengine/bevy/pull/3065 +[3079]: https://github.com/bevyengine/bevy/pull/3079 +[3090]: https://github.com/bevyengine/bevy/pull/3090 +[3107]: https://github.com/bevyengine/bevy/pull/3107 +[3302]: https://github.com/bevyengine/bevy/pull/3302 +[3328]: https://github.com/bevyengine/bevy/pull/3328 +[3412]: https://github.com/bevyengine/bevy/pull/3412 +[3463]: https://github.com/bevyengine/bevy/pull/3463 +[3535]: https://github.com/bevyengine/bevy/pull/3535 +[3560]: https://github.com/bevyengine/bevy/pull/3560 +[3565]: https://github.com/bevyengine/bevy/pull/3565 +[3603]: https://github.com/bevyengine/bevy/pull/3603 +[3617]: https://github.com/bevyengine/bevy/pull/3617 +[3633]: https://github.com/bevyengine/bevy/pull/3633 +[3635]: https://github.com/bevyengine/bevy/pull/3635 +[3643]: https://github.com/bevyengine/bevy/pull/3643 +[3656]: https://github.com/bevyengine/bevy/pull/3656 +[3675]: https://github.com/bevyengine/bevy/pull/3675 +[3685]: https://github.com/bevyengine/bevy/pull/3685 +[3696]: https://github.com/bevyengine/bevy/pull/3696 +[3697]: https://github.com/bevyengine/bevy/pull/3697 +[3698]: https://github.com/bevyengine/bevy/pull/3698 +[3711]: https://github.com/bevyengine/bevy/pull/3711 +[3724]: https://github.com/bevyengine/bevy/pull/3724 +[3739]: https://github.com/bevyengine/bevy/pull/3739 +[3741]: https://github.com/bevyengine/bevy/pull/3741 +[3751]: https://github.com/bevyengine/bevy/pull/3751 +[3772]: https://github.com/bevyengine/bevy/pull/3772 +[3785]: https://github.com/bevyengine/bevy/pull/3785 +[3795]: https://github.com/bevyengine/bevy/pull/3795 +[3803]: https://github.com/bevyengine/bevy/pull/3803 +[3811]: https://github.com/bevyengine/bevy/pull/3811 +[3812]: https://github.com/bevyengine/bevy/pull/3812 +[3817]: https://github.com/bevyengine/bevy/pull/3817 +[3849]: https://github.com/bevyengine/bevy/pull/3849 +[3884]: https://github.com/bevyengine/bevy/pull/3884 +[3885]: https://github.com/bevyengine/bevy/pull/3885 +[3912]: https://github.com/bevyengine/bevy/pull/3912 +[3916]: https://github.com/bevyengine/bevy/pull/3916 +[3917]: https://github.com/bevyengine/bevy/pull/3917 +[3923]: https://github.com/bevyengine/bevy/pull/3923 +[3926]: https://github.com/bevyengine/bevy/pull/3926 +[3930]: https://github.com/bevyengine/bevy/pull/3930 +[3931]: https://github.com/bevyengine/bevy/pull/3931 +[3941]: https://github.com/bevyengine/bevy/pull/3941 +[3945]: https://github.com/bevyengine/bevy/pull/3945 +[3948]: https://github.com/bevyengine/bevy/pull/3948 +[3952]: https://github.com/bevyengine/bevy/pull/3952 +[3958]: https://github.com/bevyengine/bevy/pull/3958 +[3959]: https://github.com/bevyengine/bevy/pull/3959 +[3963]: https://github.com/bevyengine/bevy/pull/3963 +[3966]: https://github.com/bevyengine/bevy/pull/3966 +[3968]: https://github.com/bevyengine/bevy/pull/3968 +[3974]: https://github.com/bevyengine/bevy/pull/3974 +[3976]: https://github.com/bevyengine/bevy/pull/3976 +[3979]: https://github.com/bevyengine/bevy/pull/3979 +[3981]: https://github.com/bevyengine/bevy/pull/3981 +[3982]: https://github.com/bevyengine/bevy/pull/3982 +[3984]: https://github.com/bevyengine/bevy/pull/3984 +[3989]: https://github.com/bevyengine/bevy/pull/3989 +[4006]: https://github.com/bevyengine/bevy/pull/4006 +[4012]: https://github.com/bevyengine/bevy/pull/4012 +[4015]: https://github.com/bevyengine/bevy/pull/4015 +[4035]: https://github.com/bevyengine/bevy/pull/4035 +[4047]: https://github.com/bevyengine/bevy/pull/4047 +[4055]: https://github.com/bevyengine/bevy/pull/4055 +[4056]: https://github.com/bevyengine/bevy/pull/4056 +[4063]: https://github.com/bevyengine/bevy/pull/4063 +[4071]: https://github.com/bevyengine/bevy/pull/4071 +[4079]: https://github.com/bevyengine/bevy/pull/4079 +[4086]: https://github.com/bevyengine/bevy/pull/4086 +[4088]: https://github.com/bevyengine/bevy/pull/4088 +[4092]: https://github.com/bevyengine/bevy/pull/4092 +[4093]: https://github.com/bevyengine/bevy/pull/4093 +[4096]: https://github.com/bevyengine/bevy/pull/4096 +[4114]: https://github.com/bevyengine/bevy/pull/4114 +[4115]: https://github.com/bevyengine/bevy/pull/4115 +[4119]: https://github.com/bevyengine/bevy/pull/4119 +[4126]: https://github.com/bevyengine/bevy/pull/4126 +[4148]: https://github.com/bevyengine/bevy/pull/4148 +[4168]: https://github.com/bevyengine/bevy/pull/4168 +[4169]: https://github.com/bevyengine/bevy/pull/4169 +[4170]: https://github.com/bevyengine/bevy/pull/4170 +[4179]: https://github.com/bevyengine/bevy/pull/4179 +[4180]: https://github.com/bevyengine/bevy/pull/4180 +[4181]: https://github.com/bevyengine/bevy/pull/4181 +[4182]: https://github.com/bevyengine/bevy/pull/4182 +[4183]: https://github.com/bevyengine/bevy/pull/4183 +[4194]: https://github.com/bevyengine/bevy/pull/4194 +[4224]: https://github.com/bevyengine/bevy/pull/4224 +[4225]: https://github.com/bevyengine/bevy/pull/4225 +[4238]: https://github.com/bevyengine/bevy/pull/4238 +[4243]: https://github.com/bevyengine/bevy/pull/4243 +[4246]: https://github.com/bevyengine/bevy/pull/4246 +[4250]: https://github.com/bevyengine/bevy/pull/4250 +[4252]: https://github.com/bevyengine/bevy/pull/4252 +[4270]: https://github.com/bevyengine/bevy/pull/4270 +[4298]: https://github.com/bevyengine/bevy/pull/4298 +[4310]: https://github.com/bevyengine/bevy/pull/4310 +[4328]: https://github.com/bevyengine/bevy/pull/4328 +[4332]: https://github.com/bevyengine/bevy/pull/4332 +[4340]: https://github.com/bevyengine/bevy/pull/4340 +[4347]: https://github.com/bevyengine/bevy/pull/4347 +[4361]: https://github.com/bevyengine/bevy/pull/4361 +[4367]: https://github.com/bevyengine/bevy/pull/4367 +[4375]: https://github.com/bevyengine/bevy/pull/4375 +[4376]: https://github.com/bevyengine/bevy/pull/4376 +[4383]: https://github.com/bevyengine/bevy/pull/4383 +[4396]: https://github.com/bevyengine/bevy/pull/4396 +[4399]: https://github.com/bevyengine/bevy/pull/4399 +[4400]: https://github.com/bevyengine/bevy/pull/4400 +[4403]: https://github.com/bevyengine/bevy/pull/4403 +[4405]: https://github.com/bevyengine/bevy/pull/4405 +[4417]: https://github.com/bevyengine/bevy/pull/4417 +[4420]: https://github.com/bevyengine/bevy/pull/4420 +[4426]: https://github.com/bevyengine/bevy/pull/4426 +[4427]: https://github.com/bevyengine/bevy/pull/4427 +[4428]: https://github.com/bevyengine/bevy/pull/4428 +[4433]: https://github.com/bevyengine/bevy/pull/4433 +[4435]: https://github.com/bevyengine/bevy/pull/4435 + ## Version 0.6.0 (2022-01-08) ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6c0e91dea1c14..b43e4749327b35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,14 +56,14 @@ Bevy also currently has the following "development process" goals: * **Focus**: The Bevy Org should focus on building a small number of features excellently over merging every new community-contributed feature quickly. Sometimes this means pull requests will sit unmerged for a long time. This is the price of focus and we are willing to pay it. Fortunately Bevy is modular to its core. 3rd party plugins are a great way to work around this policy. * **User-facing API ergonomics come first**: Solid user experience should receive significant focus and investment. It should rarely be compromised in the interest of internal implementation details. * **Modularity over deep integration**: Individual crates and features should be "pluggable" whenever possible. Don't tie crates, features, or types together that don't need to be. -* **Don't merge everything ... don't merge too early**: Every feature we add increases maintenance burden and compile times. Only merge features that are "generally" useful. Don't merge major changes or new features unless we have relative consensus that the design is correct _and_ that we have the developer capacity to support it. When possible, make a 3rd party Plugin / crate first, then consider merging once the API has been tested in the wild. Bevy's modular structure means that the only difference between "official engine features" and "third party plugins" is our endorsement and the repo the code lives in. We should take advantage of that whenever possible. -* **Control and consistency over 3rd party code reuse**: Only add a dependency if it is _absolutely_ necessary. Every dependency we add decreases our autonomy and consistency. Dependencies also have the potential to increase compile times and risk pulling in sub-dependencies we don't want / need. +* **Don't merge everything ... don't merge too early**: Every feature we add increases maintenance burden and compile times. Only merge features that are "generally" useful. Don't merge major changes or new features unless we have relative consensus that the design is correct *and* that we have the developer capacity to support it. When possible, make a 3rd party Plugin / crate first, then consider merging once the API has been tested in the wild. Bevy's modular structure means that the only difference between "official engine features" and "third party plugins" is our endorsement and the repo the code lives in. We should take advantage of that whenever possible. +* **Control and consistency over 3rd party code reuse**: Only add a dependency if it is *absolutely* necessary. Every dependency we add decreases our autonomy and consistency. Dependencies also have the potential to increase compile times and risk pulling in sub-dependencies we don't want / need. * **Don't re-invent every wheel**: As a counter to the previous point, don't re-invent everything at all costs. If there is a crate in the Rust ecosystem that is the "de-facto" standard (ex: wgpu, winit, cpal), we should heavily consider using it. Bevy should be a positive force in the ecosystem. We should drive the improvements we need into these core ecosystem crates. * **Rust-first**: Engine and user-facing code should optimize and encourage Rust-only workflows. Adding additional languages increases internal complexity, fractures the Bevy ecosystem, and makes it harder for users to understand the engine. Never compromise a Rust interface in the interest of compatibility with other languages. * **Thoughtful public interfaces over maximal configurability**: Symbols and apis should be private by default. Every public API should be thoughtfully and consistently designed. Don't expose unnecessary internal implementation details. Don't allow users to "shoot themselves in the foot". Favor one "happy path" api over multiple apis for different use cases. * **Welcome new contributors**: Invest in new contributors. Help them fill knowledge and skill gaps. Don't ever gatekeep Bevy development according to notions of required skills or credentials. Help new developers find their niche. -* **Civil discourse**: We need to collectively discuss ideas and the best ideas _should_ win. But conversations need to remain respectful at all times. Remember that we're all in this together. Always follow our [Code of Conduct](https://github.com/bevyengine/bevy/blob/main/CODE_OF_CONDUCT.md). -* **Test what you need to**: Write useful tests. Don't write tests that aren't useful. We _generally_ aren't strict about unit testing every line of code. We don't want you to waste your time. But at the same time: +* **Civil discourse**: We need to collectively discuss ideas and the best ideas *should* win. But conversations need to remain respectful at all times. Remember that we're all in this together. Always follow our [Code of Conduct](https://github.com/bevyengine/bevy/blob/main/CODE_OF_CONDUCT.md). +* **Test what you need to**: Write useful tests. Don't write tests that aren't useful. We *generally* aren't strict about unit testing every line of code. We don't want you to waste your time. But at the same time: * Most new features should have at least one minimal [example](https://github.com/bevyengine/bevy/tree/main/examples). These also serve as simple integration tests, as they are run as part of our CI process. * The more complex or "core" a feature is, the more strict we are about unit tests. Use your best judgement here. We will let you know if your pull request needs more tests. We use [Rust's built in testing framework](https://doc.rust-lang.org/book/ch11-01-writing-tests.html). @@ -81,18 +81,55 @@ Check out the next section for details on how this plays out. 2. Have demonstrated themselves to be polite and welcoming representatives of the project with an understanding of our goals and direction. 3. Have asked to join the Bevy Org. Reach out to @cart on Discord or email us at bevyengine@gmail.com if you are interested. Everyone is welcome to do this. We generally accept membership requests, so don't hesitate if you are interested! -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. +Some Bevy Org members are also 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 Org members can vist the Bevy Engine Org page and navigate to the Triage Team to request membership. 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 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. +Merge rights within the org are relatively centralized: this requires a large amount of trust when it comes to ethics, technical ability, and ability to enforce consistent project direction. + +The current structure is as follows: + +* @cart is our project lead, and has final say on controversial decisions +* There is a small group of other maintainers (@alice-i-cecile, @mockersf and @superdump), who have merge rights but abide by the following rules: + * Trivial PRs can be merged without approvals + * Relatively uncontroversial PRs can be merged following approval from at least two other community members with appropriate expertise. + * Controversial PRs are added to a backlog for @cart to address once two maintainers agree that they are ready. + * If 45 days elapse without action on a controversial PR (approval, feedback or an explicit request to defer), they can be merged without project lead approval. +* The Bevy org is made up of trusted community contributors: this is a relatively low bar, and org members help triage and maintain the project. +* Community contributors (this means you!) can freely open issues, submit PRs and review PRs to improve Bevy. + * As discussed above, community reviews on PRs are incredibly helpful to enable maintainers to merge in uncontroversial PRs in a timely fashion. + +### Classifying PRs + +This strategy relies on a classification of PRs into three categories: **trivial**, **uncontroversial** and **controversial**. +When making PRs, try to split out more controversial changes from less controversial ones, in order to make your work easier to review and merge. +PRs that are deemed controversial will receive the `S-Controversial` label, and will have to go through the more thorough review process. + +PRs are trivial if there is no reasonable argument against them. This might include: + +* fixing dead links +* removing dead code or dependencies +* typo and grammar fixes + +PRs are controversial if there is serious design discussion required, or a large impact to contributors or users. Factors that increase controversy include: + +1. Changes to project-wide workflow or style. +2. New architecture for a large feature. +3. PRs where a serious tradeoff must be made. +4. Heavy user impact. +5. New ways for users to make mistakes (footguns). +6. Introductions of `unsafe` code. +7. Large-scale code reorganization. +8. High levels of technical complexity. +9. Adding a dependency. + +Finally, changes are "relatively uncontroversial" if they are neither trivial or controversial. +Most PRs should fall into this category. ## How we work together -Making a game engine is a huge project and facilitating collaboration is a lot of work. At the moment @cart is our only paid contributor, so [go sponsor him!](https://github.com/sponsors/cart) +Making a game engine is a huge project and facilitating collaboration is a lot of work. +At the moment @cart is our only paid contributor, so [go sponsor him!](https://github.com/sponsors/cart) We track issues and pull requests that must be included in releases using [Milestones](https://github.com/bevyengine/bevy/milestones). -You can see what we're planning by following along at the [Bevy Roadmap](https://github.com/bevyengine/bevy/projects/1). -If you'd like an up-to-the-minute look at our progress on a specific project, feel free to ask on Discord. - ### Making changes to Bevy Most changes don't require much "process". If your change is relatively straightforward, just do the following: diff --git a/CREDITS.md b/CREDITS.md index da6c38d6a8ba1b..9b7304c154cc3a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -21,3 +21,6 @@ * 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) +* glTF animated fox from [glTF Sample Models](https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox) + * Low poly fox [by PixelMannen](https://opengameart.org/content/fox-and-shiba) (CC0 1.0 Universal) + * Rigging and animation [by @tomkranis on Sketchfab](https://sketchfab.com/models/371dea88d7e04a76af5763f2a36866bc) ([CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/)) diff --git a/Cargo.toml b/Cargo.toml index 5efa16115826e6..3f5c13b5109b7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy" -version = "0.6.0" +version = "0.8.0-dev" edition = "2021" categories = ["game-engines", "graphics", "gui", "rendering"] description = "A refreshingly simple data-driven game engine and app framework" @@ -17,6 +17,7 @@ members = ["crates/*", "examples/ios", "tools/ci", "errors"] [features] default = [ + "animation", "bevy_audio", "bevy_gilrs", "bevy_winit", @@ -43,6 +44,7 @@ render = [ ] # Optional bevy crates +bevy_animation = ["bevy_internal/bevy_animation"] bevy_audio = ["bevy_internal/bevy_audio"] bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] @@ -64,10 +66,15 @@ wgpu_trace = ["bevy_internal/wgpu_trace"] # Image format support for texture loading (PNG and HDR are enabled by default) hdr = ["bevy_internal/hdr"] png = ["bevy_internal/png"] -dds = ["bevy_internal/dds"] tga = ["bevy_internal/tga"] jpeg = ["bevy_internal/jpeg"] bmp = ["bevy_internal/bmp"] +basis-universal = ["bevy_internal/basis-universal"] +dds = ["bevy_internal/dds"] +ktx2 = ["bevy_internal/ktx2"] +# For ktx2 supercompression +zlib = ["bevy_internal/zlib"] +zstd = ["bevy_internal/zstd"] # Audio format support (vorbis is enabled by default) flac = ["bevy_internal/flac"] @@ -93,12 +100,15 @@ bevy_ci_testing = ["bevy_internal/bevy_ci_testing"] # Enable the "debug asset server" for hot reloading internal assets debug_asset_server = ["bevy_internal/debug_asset_server"] +# Enable animation support, and glTF animation loading +animation = ["bevy_internal/animation"] + [dependencies] -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 } +bevy_dylib = { path = "crates/bevy_dylib", version = "0.8.0-dev", default-features = false, optional = true } +bevy_internal = { path = "crates/bevy_internal", version = "0.8.0-dev", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] -bevy_internal = { path = "crates/bevy_internal", version = "0.6.0", default-features = false, features = [ +bevy_internal = { path = "crates/bevy_internal", version = "0.8.0-dev", default-features = false, features = [ "webgl", ] } @@ -117,14 +127,6 @@ name = "hello_world" path = "examples/hello_world.rs" # 2D Rendering -[[example]] -name = "contributors" -path = "examples/2d/contributors.rs" - -[[example]] -name = "many_sprites" -path = "examples/2d/many_sprites.rs" - [[example]] name = "move_sprite" path = "examples/2d/move_sprite.rs" @@ -178,10 +180,6 @@ path = "examples/3d/lighting.rs" 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" @@ -218,6 +216,10 @@ path = "examples/3d/texture.rs" name = "render_to_texture" path = "examples/3d/render_to_texture.rs" +[[example]] +name = "two_passes" +path = "examples/3d/two_passes.rs" + [[example]] name = "update_gltf_scene" path = "examples/3d/update_gltf_scene.rs" @@ -226,6 +228,23 @@ path = "examples/3d/update_gltf_scene.rs" name = "wireframe" path = "examples/3d/wireframe.rs" +# Animation +[[example]] +name = "animated_fox" +path = "examples/animation/animated_fox.rs" + +[[example]] +name = "animated_transform" +path = "examples/animation/animated_transform.rs" + +[[example]] +name = "custom_skinned_mesh" +path = "examples/animation/custom_skinned_mesh.rs" + +[[example]] +name = "gltf_skinned_mesh" +path = "examples/animation/gltf_skinned_mesh.rs" + # Application [[example]] name = "custom_loop" @@ -387,15 +406,19 @@ path = "examples/ecs/timers.rs" # Games [[example]] name = "alien_cake_addict" -path = "examples/game/alien_cake_addict.rs" +path = "examples/games/alien_cake_addict.rs" [[example]] name = "breakout" -path = "examples/game/breakout.rs" +path = "examples/games/breakout.rs" + +[[example]] +name = "contributors" +path = "examples/games/contributors.rs" [[example]] name = "game_menu" -path = "examples/game/game_menu.rs" +path = "examples/games/game_menu.rs" # Input [[example]] @@ -497,10 +520,54 @@ path = "examples/shader/animate_shader.rs" name = "compute_shader_game_of_life" path = "examples/shader/compute_shader_game_of_life.rs" -# Tools +# Stress tests + [[example]] name = "bevymark" -path = "examples/tools/bevymark.rs" +path = "examples/stress_tests/bevymark.rs" + +[[example]] +name = "many_cubes" +path = "examples/stress_tests/many_cubes.rs" + +[[example]] +name = "many_lights" +path = "examples/stress_tests/many_lights.rs" + +[[example]] +name = "many_sprites" +path = "examples/stress_tests/many_sprites.rs" + +[[example]] +name = "transform_hierarchy" +path = "examples/stress_tests/transform_hierarchy.rs" + +# Tools + +[[example]] +name = "scene_viewer" +path = "examples/tools/scene_viewer.rs" + +# Transforms +[[example]] +name = "global_vs_local_translation" +path = "examples/transforms/global_vs_local_translation.rs" + +[[example]] +name = "3d_rotation" +path = "examples/transforms/3d_rotation.rs" + +[[example]] +name = "scale" +path = "examples/transforms/scale.rs" + +[[example]] +name = "transform" +path = "examples/transforms/transform.rs" + +[[example]] +name = "translation" +path = "examples/transforms/translation.rs" # UI (User Interface) [[example]] diff --git a/README.md b/README.md index dacc47768ec8b9..a5ca4d81805706 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ Bevy is a refreshingly simple data-driven game engine built in Rust. It is free Bevy is still in the _very_ early stages of development. APIs can and will change (now is the time to make suggestions!). Important features are missing. Documentation is sparse. Please don't build any serious projects in Bevy unless you are prepared to be broken by API changes constantly. +**MSRV:** Bevy relies heavily on improvements in the Rust language and compiler. +As a result, the Minimum Supported Rust Version (MSRV) is "the latest stable release" of Rust. + ## Design Goals * **Capable**: Offer a complete 2D and 3D feature set diff --git a/assets/models/SimpleSkin/SimpleSkin.gltf b/assets/models/SimpleSkin/SimpleSkin.gltf new file mode 100644 index 00000000000000..6e68616c727629 --- /dev/null +++ b/assets/models/SimpleSkin/SimpleSkin.gltf @@ -0,0 +1 @@ +{"scenes":[{"nodes":[0]}],"nodes":[{"skin":0,"mesh":0,"children":[1]},{"children":[2],"translation":[0,1,0]},{"rotation":[0,0,0,1]}],"meshes":[{"primitives":[{"attributes":{"POSITION":1,"JOINTS_0":2,"WEIGHTS_0":3},"indices":0}]}],"skins":[{"inverseBindMatrices":4,"joints":[1,2]}],"animations":[{"channels":[{"sampler":0,"target":{"node":2,"path":"rotation"}}],"samplers":[{"input":5,"interpolation":"LINEAR","output":6}]}],"buffers":[{"uri":"data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAD8AAAAAAACAPwAAAD8AAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAAAAAwD8AAAAAAACAPwAAwD8AAAAAAAAAAAAAAEAAAAAAAACAPwAAAEAAAAAA","byteLength":168},{"uri":"data:application/gltf-buffer;base64,AAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=","byteLength":320},{"uri":"data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAvwAAgL8AAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAL8AAIC/AAAAAAAAgD8=","byteLength":128},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAD8AAIA/AADAPwAAAEAAACBAAABAQAAAYEAAAIBAAACQQAAAoEAAALBAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAPT9ND/0/TQ/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAPT9NL/0/TQ/AAAAAAAAAAD0/TS/9P00PwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAAAAAAAAAIA/","byteLength":240}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":48,"target":34963},{"buffer":0,"byteOffset":48,"byteLength":120,"target":34962},{"buffer":1,"byteOffset":0,"byteLength":320,"byteStride":16},{"buffer":2,"byteOffset":0,"byteLength":128},{"buffer":3,"byteOffset":0,"byteLength":240}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":24,"type":"SCALAR","max":[9],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":10,"type":"VEC3","max":[1,2,0],"min":[0,0,0]},{"bufferView":2,"byteOffset":0,"componentType":5123,"count":10,"type":"VEC4","max":[0,1,0,0],"min":[0,1,0,0]},{"bufferView":2,"byteOffset":160,"componentType":5126,"count":10,"type":"VEC4","max":[1,1,0,0],"min":[0,0,0,0]},{"bufferView":3,"byteOffset":0,"componentType":5126,"count":2,"type":"MAT4","max":[1,0,0,0,0,1,0,0,0,0,1,0,-0.5,-1,0,1],"min":[1,0,0,0,0,1,0,0,0,0,1,0,-0.5,-1,0,1]},{"bufferView":4,"byteOffset":0,"componentType":5126,"count":12,"type":"SCALAR","max":[5.5],"min":[0]},{"bufferView":4,"byteOffset":48,"componentType":5126,"count":12,"type":"VEC4","max":[0,0,0.707,1],"min":[0,0,-0.707,0.707]}],"asset":{"version":"2.0"}} \ No newline at end of file diff --git a/assets/models/animated/Fox.glb b/assets/models/animated/Fox.glb new file mode 100644 index 00000000000000..2bb946e2d4815a Binary files /dev/null and b/assets/models/animated/Fox.glb differ diff --git a/assets/sounds/breakout_collision.ogg b/assets/sounds/breakout_collision.ogg new file mode 100644 index 00000000000000..0211d70cfb845f Binary files /dev/null and b/assets/sounds/breakout_collision.ogg differ diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 1eac8d1d197947..40c2c4f4b4c064 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -7,10 +7,21 @@ publish = false license = "MIT OR Apache-2.0" [dev-dependencies] -criterion = "0.3" +glam = "0.20" +criterion = { version = "0.3", features = ["html_reports"] } bevy_ecs = { path = "../crates/bevy_ecs" } bevy_tasks = { path = "../crates/bevy_tasks" } +[[bench]] +name = "archetype_updates" +path = "benches/bevy_ecs/archetype_updates.rs" +harness = false + +[[bench]] +name = "ecs_bench_suite" +path = "benches/bevy_ecs/ecs_bench_suite/mod.rs" +harness = false + [[bench]] name = "system_stage" path = "benches/bevy_ecs/stages.rs" diff --git a/benches/benches/bevy_ecs/archetype_updates.rs b/benches/benches/bevy_ecs/archetype_updates.rs new file mode 100644 index 00000000000000..dabe7fe0e7a54f --- /dev/null +++ b/benches/benches/bevy_ecs/archetype_updates.rs @@ -0,0 +1,119 @@ +use bevy_ecs::{ + component::Component, + schedule::{Stage, SystemStage}, + world::World, +}; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; + +criterion_group!(benches, no_archetypes, added_archetypes); +criterion_main!(benches); + +#[derive(Component)] +struct A(f32); + +fn setup(system_count: usize) -> (World, SystemStage) { + let mut world = World::new(); + fn empty() {} + let mut stage = SystemStage::parallel(); + for _ in 0..system_count { + stage.add_system(empty); + } + stage.run(&mut world); + (world, stage) +} + +/// create `count` entities with distinct archetypes +fn add_archetypes(world: &mut World, count: u16) { + for i in 0..count { + let mut e = world.spawn(); + if i & 1 << 0 != 0 { + e.insert(A::<0>(1.0)); + } + if i & 1 << 1 != 0 { + e.insert(A::<1>(1.0)); + } + if i & 1 << 2 != 0 { + e.insert(A::<2>(1.0)); + } + if i & 1 << 3 != 0 { + e.insert(A::<3>(1.0)); + } + if i & 1 << 4 != 0 { + e.insert(A::<4>(1.0)); + } + if i & 1 << 5 != 0 { + e.insert(A::<5>(1.0)); + } + if i & 1 << 6 != 0 { + e.insert(A::<6>(1.0)); + } + if i & 1 << 7 != 0 { + e.insert(A::<7>(1.0)); + } + if i & 1 << 8 != 0 { + e.insert(A::<8>(1.0)); + } + if i & 1 << 9 != 0 { + e.insert(A::<9>(1.0)); + } + if i & 1 << 10 != 0 { + e.insert(A::<10>(1.0)); + } + if i & 1 << 11 != 0 { + e.insert(A::<11>(1.0)); + } + if i & 1 << 12 != 0 { + e.insert(A::<12>(1.0)); + } + if i & 1 << 13 != 0 { + e.insert(A::<13>(1.0)); + } + if i & 1 << 14 != 0 { + e.insert(A::<14>(1.0)); + } + if i & 1 << 15 != 0 { + e.insert(A::<15>(1.0)); + } + } +} + +fn no_archetypes(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group("no_archetypes"); + for i in 0..=5 { + let system_count = i * 20; + let (mut world, mut stage) = setup(system_count); + group.bench_with_input( + BenchmarkId::new("system_count", system_count), + &system_count, + |bencher, &_system_count| { + bencher.iter(|| { + stage.run(&mut world); + }); + }, + ); + } +} + +fn added_archetypes(criterion: &mut Criterion) { + const SYSTEM_COUNT: usize = 100; + let mut group = criterion.benchmark_group("added_archetypes"); + for archetype_count in [100, 200, 500, 1000, 2000, 5000, 10000] { + group.bench_with_input( + BenchmarkId::new("archetype_count", archetype_count), + &archetype_count, + |bencher, &archetype_count| { + bencher.iter_batched( + || { + let (mut world, stage) = setup(SYSTEM_COUNT); + add_archetypes(&mut world, archetype_count); + (world, stage) + }, + |(mut world, mut stage)| { + stage.run(&mut world); + }, + criterion::BatchSize::LargeInput, + ) + }, + ); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/add_remove.rs b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove.rs new file mode 100644 index 00000000000000..0c5bc56b45b8e8 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove.rs @@ -0,0 +1,30 @@ +use bevy::prelude::*; + +#[derive(Component)] +struct A(f32); +#[derive(Component)] +struct B(f32); + +pub struct Benchmark(World, Vec); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + + let entities = world + .spawn_batch((0..10000).map(|_| (A(0.0),))) + .collect::>(); + + Self(world, entities) + } + + pub fn run(&mut self) { + for entity in &self.1 { + self.0.insert_one(*entity, B(0.0)).unwrap(); + } + + for entity in &self.1 { + self.0.remove_one::(*entity).unwrap(); + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_big_sparse_set.rs b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_big_sparse_set.rs new file mode 100644 index 00000000000000..db4f83d99edfb5 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_big_sparse_set.rs @@ -0,0 +1,55 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct A(Mat4); +#[derive(Component, Copy, Clone)] +struct B(Mat4); + +#[derive(Component, Copy, Clone)] +struct C(Mat4); +#[derive(Component, Copy, Clone)] +struct D(Mat4); + +#[derive(Component, Copy, Clone)] +struct E(Mat4); + +#[derive(Component, Copy, Clone)] +#[component(storage = "SparseSet")] +struct F(Mat4); +pub struct Benchmark(World, Vec); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + let mut entities = Vec::with_capacity(10_000); + for _ in 0..10_000 { + entities.push( + world + .spawn() + .insert_bundle(( + A(Mat4::from_scale(Vec3::ONE)), + B(Mat4::from_scale(Vec3::ONE)), + C(Mat4::from_scale(Vec3::ONE)), + D(Mat4::from_scale(Vec3::ONE)), + E(Mat4::from_scale(Vec3::ONE)), + )) + .id(), + ); + } + + Self(world, entities) + } + + pub fn run(&mut self) { + for entity in &self.1 { + self.0 + .entity_mut(*entity) + .insert(F(Mat4::from_scale(Vec3::ONE))); + } + + for entity in &self.1 { + self.0.entity_mut(*entity).remove::(); + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_big_table.rs b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_big_table.rs new file mode 100644 index 00000000000000..0d2ba46c73a6a3 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_big_table.rs @@ -0,0 +1,54 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct A(Mat4); +#[derive(Component, Copy, Clone)] +struct B(Mat4); + +#[derive(Component, Copy, Clone)] +struct C(Mat4); +#[derive(Component, Copy, Clone)] +struct D(Mat4); + +#[derive(Component, Copy, Clone)] +struct E(Mat4); + +#[derive(Component, Copy, Clone)] +struct F(Mat4); +pub struct Benchmark(World, Vec); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + let mut entities = Vec::with_capacity(10_000); + for _ in 0..10_000 { + entities.push( + world + .spawn() + .insert_bundle(( + A(Mat4::from_scale(Vec3::ONE)), + B(Mat4::from_scale(Vec3::ONE)), + C(Mat4::from_scale(Vec3::ONE)), + D(Mat4::from_scale(Vec3::ONE)), + E(Mat4::from_scale(Vec3::ONE)), + )) + .id(), + ); + } + + Self(world, entities) + } + + pub fn run(&mut self) { + for entity in &self.1 { + self.0 + .entity_mut(*entity) + .insert(F(Mat4::from_scale(Vec3::ONE))); + } + + for entity in &self.1 { + self.0.entity_mut(*entity).remove::(); + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_sparse_set.rs b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_sparse_set.rs new file mode 100644 index 00000000000000..6cb90f641c7882 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_sparse_set.rs @@ -0,0 +1,31 @@ +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct A(f32); +#[derive(Component)] +#[component(storage = "SparseSet")] +struct B(f32); + +pub struct Benchmark(World, Vec); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + let mut entities = Vec::with_capacity(10_000); + for _ in 0..10_000 { + entities.push(world.spawn().insert(A(0.0)).id()); + } + + Self(world, entities) + } + + pub fn run(&mut self) { + for entity in &self.1 { + self.0.entity_mut(*entity).insert(B(0.0)); + } + + for entity in &self.1 { + self.0.entity_mut(*entity).remove::(); + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_table.rs b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_table.rs new file mode 100644 index 00000000000000..67043a5bbd8c20 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/add_remove_table.rs @@ -0,0 +1,30 @@ +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct A(f32); +#[derive(Component)] +struct B(f32); + +pub struct Benchmark(World, Vec); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + let mut entities = Vec::with_capacity(10_000); + for _ in 0..10_000 { + entities.push(world.spawn().insert(A(0.0)).id()); + } + + Self(world, entities) + } + + pub fn run(&mut self) { + for entity in &self.1 { + self.0.entity_mut(*entity).insert(B(0.0)); + } + + for entity in &self.1 { + self.0.entity_mut(*entity).remove::(); + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/frag_iter.rs b/benches/benches/bevy_ecs/ecs_bench_suite/frag_iter.rs new file mode 100644 index 00000000000000..dcc675f6551fab --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/frag_iter.rs @@ -0,0 +1,35 @@ +use bevy_ecs::prelude::*; + +macro_rules! create_entities { + ($world:ident; $( $variants:ident ),*) => { + $( + #[derive(Component)] + struct $variants(f32); + for _ in 0..20 { + $world.spawn().insert_bundle(($variants(0.0), Data(1.0))); + } + )* + }; +} + +#[derive(Component)] +struct Data(f32); + +pub struct Benchmark<'w>(World, QueryState<&'w mut Data>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); + + let query = world.query::<&mut Data>(); + Self(world, query) + } + + pub fn run(&mut self) { + for mut data in self.1.iter_mut(&mut self.0) { + data.0 *= 2.0; + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/frag_iter_foreach.rs b/benches/benches/bevy_ecs/ecs_bench_suite/frag_iter_foreach.rs new file mode 100644 index 00000000000000..16cade377f3330 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/frag_iter_foreach.rs @@ -0,0 +1,35 @@ +use bevy_ecs::prelude::*; + +macro_rules! create_entities { + ($world:ident; $( $variants:ident ),*) => { + $( + #[derive(Component)] + struct $variants(f32); + for _ in 0..20 { + $world.spawn().insert_bundle(($variants(0.0), Data(1.0))); + } + )* + }; +} + +#[derive(Component)] +struct Data(f32); + +pub struct Benchmark<'w>(World, QueryState<&'w mut Data>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + create_entities!(world; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); + + let query = world.query::<&mut Data>(); + Self(world, query) + } + + pub fn run(&mut self) { + self.1.for_each_mut(&mut self.0, |mut data| { + data.0 *= 2.0; + }); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/get_component.rs b/benches/benches/bevy_ecs/ecs_bench_suite/get_component.rs new file mode 100644 index 00000000000000..42e20a10923df3 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/get_component.rs @@ -0,0 +1,23 @@ +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct A(f32); + +pub struct Benchmark<'w>(World, Entity, QueryState<&'w mut A>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + let entity = world.spawn().insert(A(0.0)).id(); + let query = world.query::<&mut A>(); + Self(world, entity, query) + } + + pub fn run(&mut self) { + for _x in 0..100000 { + let mut a = unsafe { self.2.get_unchecked(&mut self.0, self.1).unwrap() }; + a.0 += 1.0; + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/get_component_system.rs b/benches/benches/bevy_ecs/ecs_bench_suite/get_component_system.rs new file mode 100644 index 00000000000000..c746a223304d43 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/get_component_system.rs @@ -0,0 +1,29 @@ +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct A(f32); + +pub struct Benchmark(World, Entity, Box>); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::new(); + + let entity = world.spawn().insert(A(0.0)).id(); + fn query_system(In(entity): In, mut query: Query<&mut A>) { + for _ in 0..100_000 { + let mut a = query.get_mut(entity).unwrap(); + a.0 += 1.0; + } + } + + let mut system = IntoSystem::into_system(query_system); + system.initialize(&mut world); + system.update_archetype_component_access(&world); + Self(world, entity, Box::new(system)) + } + + pub fn run(&mut self) { + self.2.run(self.1, &mut self.0); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/heavy_compute.rs b/benches/benches/bevy_ecs/ecs_bench_suite/heavy_compute.rs new file mode 100644 index 00000000000000..4ddae1781ea1b6 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/heavy_compute.rs @@ -0,0 +1,53 @@ +use bevy_ecs::prelude::*; +use bevy_tasks::TaskPool; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +struct Velocity(Vec3); + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +pub struct Benchmark(World, Box>); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + + world.spawn_batch((0..1000).map(|_| { + ( + Transform(Mat4::from_axis_angle(Vec3::X, 1.2)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + ) + })); + + fn sys(task_pool: Res, mut query: Query<(&mut Position, &mut Transform)>) { + query.par_for_each_mut(&task_pool, 128, |(mut pos, mut mat)| { + for _ in 0..100 { + mat.0 = mat.0.inverse(); + } + + pos.0 = mat.0.transform_vector3(pos.0); + }); + } + + world.insert_resource(TaskPool::default()); + let mut system = IntoSystem::into_system(sys); + system.initialize(&mut world); + system.update_archetype_component_access(&world); + + Self(world, Box::new(system)) + } + + pub fn run(&mut self) { + self.1.run((), &mut self.0); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/mod.rs b/benches/benches/bevy_ecs/ecs_bench_suite/mod.rs new file mode 100644 index 00000000000000..bb127d63a7cca4 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/mod.rs @@ -0,0 +1,174 @@ +use criterion::*; + +mod add_remove_big_sparse_set; +mod add_remove_big_table; +mod add_remove_sparse_set; +mod add_remove_table; +mod frag_iter; +mod frag_iter_foreach; +mod get_component; +mod get_component_system; +mod heavy_compute; +mod schedule; +mod simple_insert; +mod simple_insert_unbatched; +mod simple_iter; +mod simple_iter_foreach; +mod simple_iter_sparse; +mod simple_iter_sparse_foreach; +mod simple_iter_system; +mod sparse_frag_iter; +mod sparse_frag_iter_foreach; + +fn bench_simple_insert(c: &mut Criterion) { + let mut group = c.benchmark_group("simple_insert"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("base", |b| { + let mut bench = simple_insert::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("unbatched", |b| { + let mut bench = simple_insert_unbatched::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_simple_iter(c: &mut Criterion) { + let mut group = c.benchmark_group("simple_iter"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("base", |b| { + let mut bench = simple_iter::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("system", |b| { + let mut bench = simple_iter_system::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("sparse", |b| { + let mut bench = simple_iter_sparse::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("foreach", |b| { + let mut bench = simple_iter_foreach::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("sparse_foreach", |b| { + let mut bench = simple_iter_sparse_foreach::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_frag_iter_bc(c: &mut Criterion) { + let mut group = c.benchmark_group("fragmented_iter"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("base", |b| { + let mut bench = frag_iter::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("foreach", |b| { + let mut bench = frag_iter_foreach::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_sparse_frag_iter(c: &mut Criterion) { + let mut group = c.benchmark_group("sparse_fragmented_iter"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("base", |b| { + let mut bench = sparse_frag_iter::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("foreach", |b| { + let mut bench = sparse_frag_iter_foreach::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_schedule(c: &mut Criterion) { + let mut group = c.benchmark_group("schedule"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("base", |b| { + let mut bench = schedule::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_heavy_compute(c: &mut Criterion) { + let mut group = c.benchmark_group("heavy_compute"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("base", |b| { + let mut bench = heavy_compute::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_add_remove(c: &mut Criterion) { + let mut group = c.benchmark_group("add_remove_component"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("table", |b| { + let mut bench = add_remove_table::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("sparse_set", |b| { + let mut bench = add_remove_sparse_set::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_add_remove_big(c: &mut Criterion) { + let mut group = c.benchmark_group("add_remove_component_big"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("table", |b| { + let mut bench = add_remove_big_table::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("sparse_set", |b| { + let mut bench = add_remove_big_sparse_set::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +fn bench_get_component(c: &mut Criterion) { + let mut group = c.benchmark_group("get_component"); + group.warm_up_time(std::time::Duration::from_millis(500)); + group.measurement_time(std::time::Duration::from_secs(4)); + group.bench_function("base", |b| { + let mut bench = get_component::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.bench_function("system", |b| { + let mut bench = get_component_system::Benchmark::new(); + b.iter(move || bench.run()); + }); + group.finish(); +} + +criterion_group!( + benchmarks, + bench_simple_insert, + bench_simple_iter, + bench_frag_iter_bc, + bench_sparse_frag_iter, + bench_schedule, + bench_heavy_compute, + bench_add_remove, + bench_add_remove_big, + bench_get_component, +); +criterion_main!(benchmarks); diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/schedule.rs b/benches/benches/bevy_ecs/ecs_bench_suite/schedule.rs new file mode 100644 index 00000000000000..dcd1fc7d3ac0a2 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/schedule.rs @@ -0,0 +1,58 @@ +use bevy_ecs::prelude::*; + +#[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); + +fn ab(mut query: Query<(&mut A, &mut B)>) { + query.for_each_mut(|(mut a, mut b)| { + std::mem::swap(&mut a.0, &mut b.0); + }); +} + +fn cd(mut query: Query<(&mut C, &mut D)>) { + query.for_each_mut(|(mut c, mut d)| { + std::mem::swap(&mut c.0, &mut d.0); + }); +} + +fn ce(mut query: Query<(&mut C, &mut E)>) { + query.for_each_mut(|(mut c, mut e)| { + std::mem::swap(&mut c.0, &mut e.0); + }); +} + +pub struct Benchmark(World, SystemStage); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::default(); + + world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0)))); + + world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0)))); + + world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0)))); + + world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0)))); + + let mut stage = SystemStage::parallel(); + stage.add_system(ab); + stage.add_system(cd); + stage.add_system(ce); + stage.run(&mut world); + + Self(world, stage) + } + + pub fn run(&mut self) { + self.1.run(&mut self.0); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/simple_insert.rs b/benches/benches/bevy_ecs/ecs_bench_suite/simple_insert.rs new file mode 100644 index 00000000000000..17ebc24593e3c5 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/simple_insert.rs @@ -0,0 +1,34 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +struct Velocity(Vec3); + +pub struct Benchmark; + +impl Benchmark { + pub fn new() -> Self { + Self + } + + pub fn run(&mut self) { + let mut world = World::new(); + world.spawn_batch((0..10_000).map(|_| { + ( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + ) + })); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/simple_insert_unbatched.rs b/benches/benches/bevy_ecs/ecs_bench_suite/simple_insert_unbatched.rs new file mode 100644 index 00000000000000..daca82a18c8958 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/simple_insert_unbatched.rs @@ -0,0 +1,34 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +struct Velocity(Vec3); + +pub struct Benchmark; + +impl Benchmark { + pub fn new() -> Self { + Self + } + + pub fn run(&mut self) { + let mut world = World::new(); + for _ in 0..10_000 { + world.spawn().insert_bundle(( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + )); + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter.rs b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter.rs new file mode 100644 index 00000000000000..5d99e19dba1f0c --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter.rs @@ -0,0 +1,41 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +struct Velocity(Vec3); + +pub struct Benchmark<'w>(World, QueryState<(&'w Velocity, &'w mut Position)>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + // TODO: batch this + for _ in 0..10_000 { + world.spawn().insert_bundle(( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + )); + } + + let query = world.query::<(&Velocity, &mut Position)>(); + Self(world, query) + } + + pub fn run(&mut self) { + for (velocity, mut position) in self.1.iter_mut(&mut self.0) { + position.0 += velocity.0; + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_foreach.rs b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_foreach.rs new file mode 100644 index 00000000000000..ae5b5c87576760 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_foreach.rs @@ -0,0 +1,42 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +struct Velocity(Vec3); + +pub struct Benchmark<'w>(World, QueryState<(&'w Velocity, &'w mut Position)>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + // TODO: batch this + for _ in 0..10_000 { + world.spawn().insert_bundle(( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + )); + } + + let query = world.query::<(&Velocity, &mut Position)>(); + Self(world, query) + } + + pub fn run(&mut self) { + self.1 + .for_each_mut(&mut self.0, |(velocity, mut position)| { + position.0 += velocity.0; + }); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_sparse.rs b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_sparse.rs new file mode 100644 index 00000000000000..676d4c4c4eff99 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_sparse.rs @@ -0,0 +1,43 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +#[component(storage = "SparseSet")] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +#[component(storage = "SparseSet")] +struct Velocity(Vec3); + +pub struct Benchmark<'w>(World, QueryState<(&'w Velocity, &'w mut Position)>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + // TODO: batch this + for _ in 0..10_000 { + world.spawn().insert_bundle(( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + )); + } + + let query = world.query::<(&Velocity, &mut Position)>(); + Self(world, query) + } + + pub fn run(&mut self) { + for (velocity, mut position) in self.1.iter_mut(&mut self.0) { + position.0 += velocity.0; + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_sparse_foreach.rs b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_sparse_foreach.rs new file mode 100644 index 00000000000000..98e7ea3f74fa77 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_sparse_foreach.rs @@ -0,0 +1,44 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +#[component(storage = "SparseSet")] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +#[component(storage = "SparseSet")] +struct Velocity(Vec3); + +pub struct Benchmark<'w>(World, QueryState<(&'w Velocity, &'w mut Position)>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + // TODO: batch this + for _ in 0..10_000 { + world.spawn().insert_bundle(( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + )); + } + + let query = world.query::<(&Velocity, &mut Position)>(); + Self(world, query) + } + + pub fn run(&mut self) { + self.1 + .for_each_mut(&mut self.0, |(velocity, mut position)| { + position.0 += velocity.0; + }); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_system.rs b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_system.rs new file mode 100644 index 00000000000000..039de135c24397 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/simple_iter_system.rs @@ -0,0 +1,47 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +struct Velocity(Vec3); + +pub struct Benchmark(World, Box>); + +impl Benchmark { + pub fn new() -> Self { + let mut world = World::new(); + + // TODO: batch this + for _ in 0..10_000 { + world.spawn().insert_bundle(( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + )); + } + + fn query_system(mut query: Query<(&Velocity, &mut Position)>) { + for (velocity, mut position) in query.iter_mut() { + position.0 += velocity.0; + } + } + + let mut system = IntoSystem::into_system(query_system); + system.initialize(&mut world); + system.update_archetype_component_access(&world); + Self(world, Box::new(system)) + } + + pub fn run(&mut self) { + self.1.run((), &mut self.0); + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/sparse_frag_iter.rs b/benches/benches/bevy_ecs/ecs_bench_suite/sparse_frag_iter.rs new file mode 100644 index 00000000000000..0778b65429b613 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/sparse_frag_iter.rs @@ -0,0 +1,46 @@ +use bevy_ecs::prelude::*; + +macro_rules! create_entities { + ($world:ident; $( $variants:ident ),*) => { + $( + #[derive(Component)] + struct $variants(f32); + for _ in 0..5 { + $world.spawn().insert($variants(0.0)); + } + )* + }; +} +#[derive(Component)] +struct Data(f32); + +pub struct Benchmark<'w>(World, QueryState<&'w mut Data>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + for _ in 0..5 { + world.spawn().insert(Data(1.0)); + } + + create_entities!(world; C00, C01, C02, C03, C04, C05, C06, C07, C08, C09); + create_entities!(world; C10, C11, C12, C13, C14, C15, C16, C17, C18, C19); + create_entities!(world; C20, C21, C22, C23, C24, C25, C26, C27, C28, C29); + create_entities!(world; C30, C31, C32, C33, C34, C35, C36, C37, C38, C39); + create_entities!(world; C40, C41, C42, C43, C44, C45, C46, C47, C48, C49); + create_entities!(world; C50, C51, C52, C53, C54, C55, C56, C57, C58, C59); + create_entities!(world; C60, C61, C62, C63, C64, C65, C66, C67, C68, C69); + create_entities!(world; C70, C71, C72, C73, C74, C75, C76, C77, C78, C79); + create_entities!(world; C80, C81, C82, C83, C84, C85, C86, C87, C88, C89); + create_entities!(world; C90, C91, C92, C93, C94, C95, C96, C97, C98, C99); + let query = world.query::<&mut Data>(); + Self(world, query) + } + + pub fn run(&mut self) { + for mut data in self.1.iter_mut(&mut self.0) { + data.0 *= 2.0; + } + } +} diff --git a/benches/benches/bevy_ecs/ecs_bench_suite/sparse_frag_iter_foreach.rs b/benches/benches/bevy_ecs/ecs_bench_suite/sparse_frag_iter_foreach.rs new file mode 100644 index 00000000000000..4cec9fe20c7397 --- /dev/null +++ b/benches/benches/bevy_ecs/ecs_bench_suite/sparse_frag_iter_foreach.rs @@ -0,0 +1,46 @@ +use bevy_ecs::prelude::*; + +macro_rules! create_entities { + ($world:ident; $( $variants:ident ),*) => { + $( + #[derive(Component)] + struct $variants(f32); + for _ in 0..5 { + $world.spawn().insert($variants(0.0)); + } + )* + }; +} + +#[derive(Component)] +struct Data(f32); + +pub struct Benchmark<'w>(World, QueryState<&'w mut Data>); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + for _ in 0..5 { + world.spawn().insert(Data(1.0)); + } + + create_entities!(world; C00, C01, C02, C03, C04, C05, C06, C07, C08, C09); + create_entities!(world; C10, C11, C12, C13, C14, C15, C16, C17, C18, C19); + create_entities!(world; C20, C21, C22, C23, C24, C25, C26, C27, C28, C29); + create_entities!(world; C30, C31, C32, C33, C34, C35, C36, C37, C38, C39); + create_entities!(world; C40, C41, C42, C43, C44, C45, C46, C47, C48, C49); + create_entities!(world; C50, C51, C52, C53, C54, C55, C56, C57, C58, C59); + create_entities!(world; C60, C61, C62, C63, C64, C65, C66, C67, C68, C69); + create_entities!(world; C70, C71, C72, C73, C74, C75, C76, C77, C78, C79); + create_entities!(world; C80, C81, C82, C83, C84, C85, C86, C87, C88, C89); + create_entities!(world; C90, C91, C92, C93, C94, C95, C96, C97, C98, C99); + let query = world.query::<&mut Data>(); + Self(world, query) + } + + pub fn run(&mut self) { + self.1.for_each_mut(&mut self.0, |mut data| { + data.0 *= 2.0; + }); + } +} diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml new file mode 100644 index 00000000000000..fdb9bd164ce6b6 --- /dev/null +++ b/crates/bevy_animation/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bevy_animation" +version = "0.8.0-dev" +edition = "2021" +description = "Provides animation functionality for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.8.0-dev" } +bevy_asset = { path = "../bevy_asset", version = "0.8.0-dev" } +bevy_core = { path = "../bevy_core", version = "0.8.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.8.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] } +bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev" } +bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" } +bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.8.0-dev" } diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs new file mode 100644 index 00000000000000..19895163342978 --- /dev/null +++ b/crates/bevy_animation/src/lib.rs @@ -0,0 +1,296 @@ +//! Animation for the game engine Bevy + +#![warn(missing_docs)] + +use std::ops::Deref; + +use bevy_app::{App, CoreStage, Plugin}; +use bevy_asset::{AddAsset, Assets, Handle}; +use bevy_core::{Name, Time}; +use bevy_ecs::{ + change_detection::DetectChanges, + entity::Entity, + prelude::Component, + reflect::ReflectComponent, + schedule::ParallelSystemDescriptorCoercion, + system::{Query, Res}, +}; +use bevy_hierarchy::{Children, HierarchySystem}; +use bevy_math::{Quat, Vec3}; +use bevy_reflect::{Reflect, TypeUuid}; +use bevy_transform::{prelude::Transform, TransformSystem}; +use bevy_utils::{tracing::warn, HashMap}; + +#[allow(missing_docs)] +pub mod prelude { + #[doc(hidden)] + pub use crate::{ + AnimationClip, AnimationPlayer, AnimationPlugin, EntityPath, Keyframes, VariableCurve, + }; +} + +/// List of keyframes for one of the attribute of a [`Transform`]. +#[derive(Clone, Debug)] +pub enum Keyframes { + /// Keyframes for rotation. + Rotation(Vec), + /// Keyframes for translation. + Translation(Vec), + /// Keyframes for scale. + Scale(Vec), +} + +/// Describes how an attribute of a [`Transform`] should be animated. +/// +/// `keyframe_timestamps` and `keyframes` should have the same length. +#[derive(Clone, Debug)] +pub struct VariableCurve { + /// Timestamp for each of the keyframes. + pub keyframe_timestamps: Vec, + /// List of the keyframes. + pub keyframes: Keyframes, +} + +/// Path to an entity, with [`Name`]s. Each entity in a path must have a name. +#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)] +pub struct EntityPath { + /// Parts of the path + pub parts: Vec, +} + +/// A list of [`VariableCurve`], and the [`EntityPath`] to which they apply. +#[derive(Clone, TypeUuid, Debug, Default)] +#[uuid = "d81b7179-0448-4eb0-89fe-c067222725bf"] +pub struct AnimationClip { + curves: HashMap>, + duration: f32, +} + +impl AnimationClip { + #[inline] + /// Hashmap of the [`VariableCurve`]s per [`EntityPath`]. + pub fn curves(&self) -> &HashMap> { + &self.curves + } + + /// Add a [`VariableCurve`] to an [`EntityPath`]. + pub fn add_curve_to_path(&mut self, path: EntityPath, curve: VariableCurve) { + // Update the duration of the animation by this curve duration if it's longer + self.duration = self + .duration + .max(*curve.keyframe_timestamps.last().unwrap_or(&0.0)); + self.curves.entry(path).or_default().push(curve); + } +} + +/// Animation controls +#[derive(Component, Reflect)] +#[reflect(Component)] +pub struct AnimationPlayer { + paused: bool, + repeat: bool, + speed: f32, + elapsed: f32, + animation_clip: Handle, +} + +impl Default for AnimationPlayer { + fn default() -> Self { + Self { + paused: false, + repeat: false, + speed: 1.0, + elapsed: 0.0, + animation_clip: Default::default(), + } + } +} + +impl AnimationPlayer { + /// Start playing an animation, resetting state of the player + pub fn play(&mut self, handle: Handle) -> &mut Self { + *self = Self { + animation_clip: handle, + ..Default::default() + }; + self + } + + /// Set the animation to repeat + pub fn repeat(&mut self) -> &mut Self { + self.repeat = true; + self + } + + /// Stop the animation from repeating + pub fn stop_repeating(&mut self) -> &mut Self { + self.repeat = false; + self + } + + /// Pause the animation + pub fn pause(&mut self) { + self.paused = true; + } + + /// Unpause the animation + pub fn resume(&mut self) { + self.paused = false; + } + + /// Is the animation paused + pub fn is_paused(&self) -> bool { + self.paused + } + + /// Speed of the animation playback + pub fn speed(&self) -> f32 { + self.speed + } + + /// Set the speed of the animation playback + pub fn set_speed(&mut self, speed: f32) -> &mut Self { + self.speed = speed; + self + } + + /// Time elapsed playing the animation + pub fn elapsed(&self) -> f32 { + self.elapsed + } + + /// Seek to a specific time in the animation + pub fn set_elapsed(&mut self, elapsed: f32) -> &mut Self { + self.elapsed = elapsed; + self + } +} + +/// System that will play all animations, using any entity with a [`AnimationPlayer`] +/// and a [`Handle`] as an animation root +pub fn animation_player( + time: Res