Compare commits
151 Commits
custom
...
release-0.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5c759a1be8 | ||
![]() |
79b248c7ce | ||
![]() |
cd9fec3547 | ||
![]() |
dd929ad867 | ||
![]() |
6231ed02dd | ||
![]() |
9afe666f6d | ||
![]() |
489c1e1a87 | ||
![]() |
a4ea708b69 | ||
![]() |
ff3d632f31 | ||
![]() |
7b33038393 | ||
![]() |
d7b6e81fb2 | ||
![]() |
6a4b48ba6b | ||
![]() |
650e7c9eb4 | ||
![]() |
0793d05256 | ||
![]() |
dcb506ea70 | ||
![]() |
c661da1d42 | ||
![]() |
216dba23cb | ||
![]() |
7d63efe4c2 | ||
![]() |
8f17fdafc3 | ||
![]() |
dff6471049 | ||
![]() |
5e2c04516c | ||
![]() |
3830e71076 | ||
![]() |
d65eb39277 | ||
![]() |
c217238c5e | ||
![]() |
61c683fb6a | ||
![]() |
833ee3f577 | ||
![]() |
d8886408bf | ||
![]() |
3a6176b6cb | ||
![]() |
ebfe545f79 | ||
![]() |
0886e6a302 | ||
![]() |
587cffdcde | ||
![]() |
27cafdae9b | ||
![]() |
680c994100 | ||
![]() |
c4ea4776c4 | ||
![]() |
9daf16bb87 | ||
![]() |
2870d89d5c | ||
![]() |
2e577bcdc9 | ||
![]() |
6882420c7f | ||
![]() |
df3fcbd116 | ||
![]() |
295ed1fdb4 | ||
![]() |
420ca6c43c | ||
![]() |
42412f3500 | ||
![]() |
70a0c211ff | ||
![]() |
728c5b98d4 | ||
![]() |
524fb01457 | ||
![]() |
7f3fea9a5b | ||
![]() |
d0583c8b54 | ||
![]() |
4bd56b6da1 | ||
![]() |
0e1858bc4f | ||
![]() |
1bc5ecda9b | ||
![]() |
5d9e44b9dc | ||
![]() |
e941264b6f | ||
![]() |
7ed1f6a9b6 | ||
![]() |
c6b80c5664 | ||
![]() |
4275669b07 | ||
![]() |
a6fde1059c | ||
![]() |
b231ebbc19 | ||
![]() |
201dd62a74 | ||
![]() |
ff070da7e2 | ||
![]() |
1db0214f24 | ||
![]() |
309c224ca8 | ||
![]() |
24fdad3a36 | ||
![]() |
e7333510c3 | ||
![]() |
31b861401c | ||
![]() |
99c465dcb5 | ||
![]() |
d7a0cc6bce | ||
![]() |
fda2e4b59c | ||
![]() |
dc56614b86 | ||
![]() |
4a05c737a2 | ||
![]() |
036d0026be | ||
![]() |
73b43aa6cf | ||
![]() |
20638f3a10 | ||
![]() |
26f7313212 | ||
![]() |
bea8823aa9 | ||
![]() |
7b98db6d7c | ||
![]() |
7c603874bf | ||
![]() |
5d7da827b7 | ||
![]() |
4f0b0e0989 | ||
![]() |
cc1764772e | ||
![]() |
99db59c176 | ||
![]() |
9a4de9c54d | ||
![]() |
a6feb5ba74 | ||
![]() |
e65e4be98c | ||
![]() |
27aa9d2b7e | ||
![]() |
f8e165be0a | ||
![]() |
4736fe0dea | ||
![]() |
f89f7f306c | ||
![]() |
82f01569e8 | ||
![]() |
65daab8517 | ||
![]() |
9eb547ec1f | ||
![]() |
8c1b9a6c18 | ||
![]() |
a0e7429363 | ||
![]() |
a41ed7822f | ||
![]() |
b56a693c34 | ||
![]() |
073db8cf36 | ||
![]() |
8af12c8775 | ||
![]() |
e73063e1d5 | ||
![]() |
cf66df1d0d | ||
![]() |
783fc29cd3 | ||
![]() |
47ad37ec84 | ||
![]() |
472a6e8283 | ||
![]() |
5d5e67fa35 | ||
![]() |
b74b3b2123 | ||
![]() |
4608708d8d | ||
![]() |
f2e66726b6 | ||
![]() |
165f399489 | ||
![]() |
6cf04c213b | ||
![]() |
1c838ff6b3 | ||
![]() |
03f63e72cf | ||
![]() |
55ee49b7ad | ||
![]() |
eca8220761 | ||
![]() |
5ed296ff03 | ||
![]() |
cc4681fc44 | ||
![]() |
ccfae7ebe7 | ||
![]() |
1ea1b76c75 | ||
![]() |
ac50539aab | ||
![]() |
727b0f6e27 | ||
![]() |
7c0b1b9029 | ||
![]() |
84be2b3f1e | ||
![]() |
eca7e87d47 | ||
![]() |
cdd1f71596 | ||
![]() |
92176ce576 | ||
![]() |
e14f3ba1aa | ||
![]() |
9a82ecfcba | ||
![]() |
93f48edbc3 | ||
![]() |
7cd90990f9 | ||
![]() |
65dbfe249b | ||
![]() |
effbcdfc92 | ||
![]() |
3ced49f672 | ||
![]() |
a3916b4af4 | ||
![]() |
c060e3e1fe | ||
![]() |
854983dc7e | ||
![]() |
a944598812 | ||
![]() |
cbebcb0d3f | ||
![]() |
8c5769ab92 | ||
![]() |
1ae616fef1 | ||
![]() |
a2c5b0d415 | ||
![]() |
be65fbb691 | ||
![]() |
91db570d6e | ||
![]() |
621cd23ffc | ||
![]() |
7ae3c94b0f | ||
![]() |
b14684ee12 | ||
![]() |
971723e4b4 | ||
![]() |
4df95384ba | ||
![]() |
c71fa76f12 | ||
![]() |
25306e7532 | ||
![]() |
ce19b0f63f | ||
![]() |
79a93de606 | ||
![]() |
5f2e927ae5 | ||
![]() |
43204447e8 | ||
![]() |
2d11d9a48d |
@ -7,9 +7,9 @@
|
||||
#
|
||||
# ## LLD
|
||||
#
|
||||
# LLD is a linker from the LLVM project that supports Linux, Windows, macOS, and Wasm. It has the greatest
|
||||
# LLD is a linker from the LLVM project that supports Linux, Windows, MacOS, and WASM. It has the greatest
|
||||
# platform support and the easiest installation process. It is enabled by default in this file for Linux
|
||||
# and Windows. On macOS, the default linker yields higher performance than LLD and is used instead.
|
||||
# and Windows. On MacOS, the default linker yields higher performance than LLD and is used instead.
|
||||
#
|
||||
# To install, please scroll to the corresponding table for your target (eg. `[target.x86_64-pc-windows-msvc]`
|
||||
# for Windows) and follow the steps under `LLD linker`.
|
||||
@ -21,18 +21,18 @@
|
||||
# Mold is a newer linker written by one of the authors of LLD. It boasts even greater performance, specifically
|
||||
# through its high parallelism, though it only supports Linux.
|
||||
#
|
||||
# Mold is disabled by default in this file. If you wish to enable it, follow the installation instructions for
|
||||
# Mold is disabled by default in this file. If you wish to enable it, follow the installation instructions for
|
||||
# your corresponding target, disable LLD by commenting out its `-Clink-arg=...` line, and enable Mold by
|
||||
# *uncommenting* its `-Clink-arg=...` line.
|
||||
#
|
||||
# There is a fork of Mold named Sold that supports macOS, but it is unmaintained and is about the same speed as
|
||||
# There is a fork of Mold named Sold that supports MacOS, but it is unmaintained and is about the same speed as
|
||||
# the default ld64 linker. For this reason, it is not included in this file.
|
||||
#
|
||||
# For more information, please see Mold's repository at <https://github.com/rui314/mold>.
|
||||
#
|
||||
# # Nightly configuration
|
||||
#
|
||||
# Be warned that the following features require nightly Rust, which is experimental and may contain bugs. If you
|
||||
# Be warned that the following features require nightly Rust, which is expiremental and may contain bugs. If you
|
||||
# are having issues, skip this section and use stable Rust instead.
|
||||
#
|
||||
# There are a few unstable features that can improve performance. To use them, first install nightly Rust
|
||||
@ -51,7 +51,7 @@
|
||||
# crates to share monomorphized generic code, so they do not duplicate work.
|
||||
#
|
||||
# In other words, instead of crate 1 generating `Foo<String>` and crate 2 generating `Foo<String>` separately,
|
||||
# only one crate generates `Foo<String>` and the other adds on to the pre-existing work.
|
||||
# only one crate generates `Foo<String>` and the other adds on to the pre-exiting work.
|
||||
#
|
||||
# Note that you may have some issues with this flag on Windows. If compiling fails due to the 65k symbol limit,
|
||||
# you may have to disable this setting. For more information and possible solutions to this error, see
|
||||
@ -83,21 +83,12 @@ rustflags = [
|
||||
# - Ubuntu: `sudo apt-get install mold clang`
|
||||
# - Fedora: `sudo dnf install mold clang`
|
||||
# - Arch: `sudo pacman -S mold clang`
|
||||
# "-Clink-arg=-fuse-ld=mold",
|
||||
# "-Clink-arg=-fuse-ld=/usr/bin/mold",
|
||||
|
||||
# Nightly
|
||||
# "-Zshare-generics=y",
|
||||
# "-Zthreads=0",
|
||||
]
|
||||
# Some systems may experience linker performance issues when running doc tests.
|
||||
# See https://github.com/bevyengine/bevy/issues/12207 for details.
|
||||
rustdocflags = [
|
||||
# LLD linker
|
||||
"-Clink-arg=-fuse-ld=lld",
|
||||
|
||||
# Mold linker
|
||||
# "-Clink-arg=-fuse-ld=mold",
|
||||
]
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = [
|
||||
@ -143,19 +134,18 @@ rustflags = [
|
||||
# rustup component add llvm-tools
|
||||
# ```
|
||||
linker = "rust-lld.exe"
|
||||
rustdocflags = ["-Clinker=rust-lld.exe"]
|
||||
rustflags = [
|
||||
# Nightly
|
||||
# "-Zshare-generics=n", # This needs to be off if you use dynamic linking on Windows.
|
||||
# "-Zshare-generics=y",
|
||||
# "-Zthreads=0",
|
||||
]
|
||||
|
||||
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'.
|
||||
# In most cases the gains are negligible, but if you are on macOS and have slow compile times you should see significant gains.
|
||||
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
|
||||
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
|
||||
# [profile.dev]
|
||||
# debug = 1
|
||||
|
||||
# This enables you to run the CI tool using `cargo ci`.
|
||||
# This is enables you to run the CI tool using `cargo ci`.
|
||||
# This is not enabled by default, you need to copy this file to `config.toml`.
|
||||
[alias]
|
||||
ci = "run --package ci --"
|
||||
|
2
.github/ISSUE_TEMPLATE/docs_improvement.md
vendored
@ -10,4 +10,4 @@ assignees: ''
|
||||
|
||||
Provide a link to the documentation and describe how it could be improved. In what ways is it incomplete, incorrect, or misleading?
|
||||
|
||||
If you have suggestions on exactly what the new docs should say, feel free to include them here. Alternatively, make the changes yourself and [create a pull request](https://bevyengine.org/learn/contribute/helping-out/writing-docs/) instead.
|
||||
If you have suggestions on exactly what the new docs should say, feel free to include them here. Or alternatively, make the changes yourself and [create a pull request](https://bevyengine.org/learn/book/contributing/code/) instead.
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -2,7 +2,7 @@
|
||||
name: Feature Request
|
||||
about: Propose a new feature!
|
||||
title: ''
|
||||
labels: C-Feature, S-Needs-Triage
|
||||
labels: C-Enhancement, S-Needs-Triage
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
name: Performance Regression
|
||||
about: Bevy running slowly after upgrading? Report a performance regression.
|
||||
title: ''
|
||||
labels: C-Bug, C-Performance, P-Regression, S-Needs-Triage
|
||||
labels: C-Bug, C-Performance, C-Regression, S-Needs-Triage
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
|
10
.github/actions/install-linux-deps/action.yml
vendored
@ -12,7 +12,7 @@
|
||||
# repository before you can use this action.
|
||||
#
|
||||
# This action will only install dependencies when the current operating system is Linux. It will do
|
||||
# nothing on any other OS (macOS, Windows).
|
||||
# nothing on any other OS (MacOS, Windows).
|
||||
|
||||
name: Install Linux dependencies
|
||||
description: Installs the dependencies necessary to build Bevy on Linux.
|
||||
@ -20,19 +20,19 @@ inputs:
|
||||
alsa:
|
||||
description: Install alsa (libasound2-dev)
|
||||
required: false
|
||||
default: "true"
|
||||
default: true
|
||||
udev:
|
||||
description: Install udev (libudev-dev)
|
||||
required: false
|
||||
default: "true"
|
||||
default: true
|
||||
wayland:
|
||||
description: Install Wayland (libwayland-dev)
|
||||
required: false
|
||||
default: "false"
|
||||
default: false
|
||||
xkb:
|
||||
description: Install xkb (libxkbcommon-dev)
|
||||
required: false
|
||||
default: "false"
|
||||
default: false
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
|
39
.github/contributing/engine_style_guide.md
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
# Style guide: Engine
|
||||
|
||||
## Contributing
|
||||
|
||||
For more advice on contributing to the engine, see the [relevant section](../../CONTRIBUTING.md#Contributing-code) of `CONTRIBUTING.md`.
|
||||
|
||||
## General guidelines
|
||||
|
||||
1. Prefer granular imports over glob imports like `bevy_ecs::prelude::*`.
|
||||
2. Use a consistent comment style:
|
||||
1. `///` doc comments belong above `#[derive(Trait)]` invocations.
|
||||
2. `//` comments should generally go above the line in question, rather than in-line.
|
||||
3. Avoid `/* */` block comments, even when writing long comments.
|
||||
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.
|
||||
4. When using [Bevy error codes](https://bevyengine.org/learn/errors/) include a link to the relevant error on the Bevy website in the returned error message `... See: https://bevyengine.org/learn/errors/#b0003`.
|
||||
|
||||
## 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
|
64
.github/contributing/example_style_guide.md
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# Style guide: Examples
|
||||
|
||||
For more advice on writing examples, see the [relevant section](../../CONTRIBUTING.md#writing-examples) of CONTRIBUTING.md.
|
||||
|
||||
## Organization
|
||||
|
||||
1. Examples should live in an appropriate subfolder of `/examples`.
|
||||
2. Examples should be a single file if possible.
|
||||
3. Assets live in `./assets`. Try to avoid adding new assets unless strictly necessary to keep the repo small. Don't add "large" asset files.
|
||||
4. Each example should try to follow this order:
|
||||
1. Imports
|
||||
2. A `fn main()` block
|
||||
3. Example logic
|
||||
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
|
||||
|
||||
1. Use simple, descriptive variable names.
|
||||
1. Avoid names like `MyComponent` in favor of more descriptive terms like `Events`.
|
||||
2. Prefer single letter differentiators like `EventsA` and `EventsB` to nonsense words like `EventsFoo` and `EventsBar`.
|
||||
3. Avoid repeating the type of variables in their name where possible. For example, `Color` should be preferred to `ColorComponent`.
|
||||
2. Prefer glob imports of `bevy::prelude::*` and `bevy::sub_crate::*` over granular imports (for terseness).
|
||||
3. Use a consistent comment style:
|
||||
1. `///` doc comments belong above `#[derive(Trait)]` invocations.
|
||||
2. `//` comments should generally go above the line in question, rather than in-line.
|
||||
3. Avoid `/* */` block comments, even when writing long comments.
|
||||
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.
|
||||
4. Use comments to organize long and complex stretches of code that can't sensibly be refactored into separate functions.
|
||||
5. Avoid making variables `pub` unless it is needed for your example.
|
||||
|
||||
## Code conventions
|
||||
|
||||
1. Refactor configurable values ("magic numbers") out into constants with clear names.
|
||||
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<T>` filters over actually fetching unused data with `&T`.
|
||||
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 app / schedule / etc. labels.
|
||||
|
||||
## "Feature" examples
|
||||
|
||||
These examples demonstrate the usage of specific engine features in clear, minimal ways.
|
||||
|
||||
1. Focus on demonstrating exactly one feature in an example
|
||||
2. Try to keep your names divorced from the context of a specific game, and focused on the feature you are demonstrating.
|
||||
3. Where they exist, show good alternative approaches to accomplish the same task and explain why you may prefer one over the other.
|
||||
4. Examples should have a visible effect when run, either in the command line or a graphical window.
|
||||
|
||||
## "Game" examples
|
||||
|
||||
These examples show how to build simple games in Bevy in a cohesive way.
|
||||
|
||||
1. Each of these examples lives in the [/examples/games] folder.
|
||||
2. Aim for minimum but viable status: the game should be playable and not obviously buggy but does not need to be polished, featureful, or terribly fun.
|
||||
3. Focus on code quality and demonstrating good, extensible patterns for users.
|
||||
1. Make good use of enums and states to organize your game logic.
|
||||
2. Keep components as small as possible but no smaller: all of the data on a component should generally be accessed at once.
|
||||
3. Keep systems small: they should have a clear single purpose.
|
||||
4. Avoid duplicating logic across similar entities whenever possible by sharing systems and components.
|
||||
4. Use `///` doc comments to explain what each function / struct does as if the example were part of a polished production codebase.
|
||||
5. Arrange your code into modules within the same file to allow for simple code folding / organization.
|
@ -1,4 +1,5 @@
|
||||
(
|
||||
events: [
|
||||
(300, AppExit),
|
||||
]
|
||||
)
|
2
.github/example-run/ambiguity_detection.ron
vendored
@ -1,2 +0,0 @@
|
||||
(
|
||||
)
|
9
.github/example-run/breakout.ron
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
(
|
||||
setup: (
|
||||
fixed_frame_time: Some(0.03),
|
||||
),
|
||||
events: [
|
||||
(200, Screenshot),
|
||||
(900, AppExit),
|
||||
]
|
||||
)
|
@ -1,4 +1,5 @@
|
||||
(
|
||||
events: [
|
||||
(900, AppExit),
|
||||
]
|
||||
)
|
9
.github/example-run/load_gltf.ron
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
(
|
||||
setup: (
|
||||
frame_time: Some(0.03),
|
||||
),
|
||||
events: [
|
||||
(100, Screenshot),
|
||||
(300, AppExit),
|
||||
]
|
||||
)
|
4
.github/example-run/testbed_ui.ron
vendored
@ -1,4 +0,0 @@
|
||||
(
|
||||
events: [
|
||||
]
|
||||
)
|
8
.github/linters/.markdown-lint.yml
vendored
@ -1,9 +1,3 @@
|
||||
{
|
||||
"MD013": false,
|
||||
"no-inline-html": {
|
||||
"allowed_elements": [
|
||||
"details",
|
||||
"summary"
|
||||
]
|
||||
}
|
||||
"MD013": false
|
||||
}
|
24
.github/pull_request_template.md
vendored
@ -16,26 +16,14 @@
|
||||
|
||||
---
|
||||
|
||||
## Showcase
|
||||
## Changelog
|
||||
|
||||
> This section is optional. If this PR does not include a visual change or does not add a new feature, you can delete this section.
|
||||
> This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section.
|
||||
|
||||
- Help others understand the result of this PR by showcasing your awesome work!
|
||||
- If this PR adds a new feature or public API, consider adding a brief pseudo-code snippet of it in action
|
||||
- If this PR includes a visual change, consider adding a screenshot, GIF, or video
|
||||
- If you want, you could even include a before/after comparison!
|
||||
- If the Migration Guide adequately covers the changes, you can delete this section
|
||||
|
||||
While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases:
|
||||
|
||||
<details>
|
||||
<summary>Click to view showcase</summary>
|
||||
|
||||
```rust
|
||||
println!("My super cool code.");
|
||||
```
|
||||
|
||||
</details>
|
||||
- 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
|
||||
|
||||
|
4
.github/start-wasm-example/package.json
vendored
@ -8,9 +8,9 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.49.1"
|
||||
"@playwright/test": "^1.28.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ test.beforeEach(async ({ page }) => {
|
||||
|
||||
const MAX_TIMEOUT_FOR_TEST = 300_000;
|
||||
|
||||
test.describe('Wasm example', () => {
|
||||
test.describe('WASM example', () => {
|
||||
test('Wait for success', async ({ page }, testInfo) => {
|
||||
let start = new Date().getTime();
|
||||
|
||||
|
2
.github/workflows/action-on-PR-labeled.yml
vendored
@ -14,7 +14,7 @@ permissions:
|
||||
jobs:
|
||||
comment-on-breaking-change-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'M-Needs-Migration-Guide' && !contains(github.event.pull_request.body, '## Migration Guide')
|
||||
if: github.event.label.name == 'C-Breaking-Change' && !contains(github.event.pull_request.body, '## Migration Guide')
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
|
51
.github/workflows/ci-comment-failures.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "missing-examples"
|
||||
@ -48,21 +48,8 @@ jobs:
|
||||
return "true"
|
||||
- run: unzip missing-examples.zip
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' }}
|
||||
- name: "Check if last comment is already from actions"
|
||||
- name: 'Comment on PR'
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' }}
|
||||
id: check-last-comment
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR=`cat ./NR`
|
||||
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot]' ]]
|
||||
then
|
||||
echo "result=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "result=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: "Comment on PR"
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' && steps.check-last-comment.outputs.result == 'false' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -101,7 +88,7 @@ jobs:
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "missing-features"
|
||||
@ -119,21 +106,8 @@ jobs:
|
||||
return "true"
|
||||
- run: unzip missing-features.zip
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' }}
|
||||
- name: "Check if last comment is already from actions"
|
||||
- name: 'Comment on PR'
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' }}
|
||||
id: check-last-comment
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR=`cat ./NR`
|
||||
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot]' ]]
|
||||
then
|
||||
echo "result=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "result=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: "Comment on PR"
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' && steps.check-last-comment.outputs.result == 'false' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -172,7 +146,7 @@ jobs:
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "msrv"
|
||||
@ -190,21 +164,8 @@ jobs:
|
||||
return "true"
|
||||
- run: unzip msrv.zip
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' }}
|
||||
- name: "Check if last comment is already from actions"
|
||||
- name: 'Comment on PR'
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' }}
|
||||
id: check-last-comment
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PR=`cat ./NR`
|
||||
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot]' ]]
|
||||
then
|
||||
echo "result=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "result=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: "Comment on PR"
|
||||
if: ${{ steps.find-artifact.outputs.result == 'true' && steps.check-last-comment.outputs.result == 'false' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
190
.github/workflows/ci.yml
vendored
@ -5,16 +5,13 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
# If nightly is breaking CI, modify this variable to target a specific nightly version.
|
||||
NIGHTLY_TOOLCHAIN: nightly
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
concurrency:
|
||||
group: ${{github.workflow}}-${{github.ref}}
|
||||
@ -45,6 +42,7 @@ jobs:
|
||||
# See tools/ci/src/main.rs for the commands this runs
|
||||
run: cargo run -p ci -- test
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0 -D warnings"
|
||||
|
||||
ci:
|
||||
@ -74,7 +72,8 @@ jobs:
|
||||
run: cargo run -p ci -- lints
|
||||
|
||||
miri:
|
||||
runs-on: macos-latest
|
||||
# Explicity use MacOS 14 to take advantage of M1 chip.
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -93,15 +92,16 @@ jobs:
|
||||
components: miri
|
||||
- name: CI job
|
||||
# To run the tests one item at a time for troubleshooting, use
|
||||
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
|
||||
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-permissive-provenance -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
|
||||
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
|
||||
# https://github.com/rust-lang/miri#miri--z-flags-and-environment-variables
|
||||
# -Zmiri-disable-isolation is needed because our executor uses `fastrand` which accesses system time.
|
||||
# -Zmiri-permissive-provenance disables warnings against int2ptr casts (since those are used by once_cell)
|
||||
# -Zmiri-ignore-leaks is necessary because a bunch of tests don't join all threads before finishing.
|
||||
MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation
|
||||
MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation -Zmiri-permissive-provenance
|
||||
|
||||
check-compiles:
|
||||
runs-on: ubuntu-latest
|
||||
@ -128,79 +128,6 @@ jobs:
|
||||
- name: Check Compile
|
||||
# See tools/ci/src/main.rs for the commands this runs
|
||||
run: cargo run -p ci -- compile
|
||||
check-compiles-no-std:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: ci
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
crates/bevy_ecs_compile_fail_tests/target/
|
||||
crates/bevy_reflect_compile_fail_tests/target/
|
||||
key: ${{ runner.os }}-cargo-check-compiles-no-std-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-unknown-none
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
- name: Check Compile
|
||||
run: cargo check -p bevy --no-default-features --features default_no_std --target x86_64-unknown-none
|
||||
check-compiles-no-std-portable-atomic:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: ci
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
crates/bevy_ecs_compile_fail_tests/target/
|
||||
crates/bevy_reflect_compile_fail_tests/target/
|
||||
key: ${{ runner.os }}-cargo-check-compiles-no-std-portable-atomic-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: thumbv6m-none-eabi
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
- name: Check Compile
|
||||
run: cargo check -p bevy --no-default-features --features default_no_std --target thumbv6m-none-eabi
|
||||
|
||||
check-compiles-no-std-examples:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: ci
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
crates/bevy_ecs_compile_fail_tests/target/
|
||||
crates/bevy_reflect_compile_fail_tests/target/
|
||||
key: ${{ runner.os }}-cargo-check-compiles-no-std-examples-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-unknown-none
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
- name: Check Compile
|
||||
run: cd examples/no_std/library && cargo check --no-default-features --features libm,critical-section --target x86_64-unknown-none
|
||||
|
||||
build-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
@ -246,7 +173,7 @@ jobs:
|
||||
- name: Check wasm
|
||||
run: cargo check --target wasm32-unknown-unknown -Z build-std=std,panic_abort
|
||||
env:
|
||||
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory -D warnings"
|
||||
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory"
|
||||
|
||||
markdownlint:
|
||||
runs-on: ubuntu-latest
|
||||
@ -259,7 +186,7 @@ jobs:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
- name: Run Markdown Lint
|
||||
uses: super-linter/super-linter/slim@v7.3.0
|
||||
uses: docker://ghcr.io/github/super-linter:slim-v4
|
||||
env:
|
||||
MULTI_STATUS: false
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
@ -280,10 +207,10 @@ jobs:
|
||||
- name: Taplo info
|
||||
if: failure()
|
||||
run: |
|
||||
echo 'To fix toml fmt, please run `taplo fmt`.'
|
||||
echo 'To check for a diff, run `taplo fmt --check --diff`.'
|
||||
echo 'To fix toml fmt, please run `taplo fmt`'
|
||||
echo 'To check for a diff, run `taplo fmt --check --diff'
|
||||
echo 'You can find taplo here: https://taplo.tamasfe.dev/'
|
||||
echo 'Or if you use VSCode, use the `Even Better Toml` extension.'
|
||||
echo 'Or if you use VSCode, use the `Even Better Toml` extension with 2 spaces'
|
||||
echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml'
|
||||
|
||||
typos:
|
||||
@ -292,7 +219,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.30.2
|
||||
uses: crate-ci/typos@v1.21.0
|
||||
- name: Typos info
|
||||
if: failure()
|
||||
run: |
|
||||
@ -302,6 +229,52 @@ jobs:
|
||||
echo 'if you use VSCode, you can also install `Typos Spell Checker'
|
||||
echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode'
|
||||
|
||||
|
||||
run-examples-macos-metal:
|
||||
# Explicity use MacOS 14 to take advantage of M1 chip.
|
||||
runs-on: macos-14
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Disable audio
|
||||
# Disable audio through a patch. on github m1 runners, audio timeouts after 15 minutes
|
||||
run: git apply --ignore-whitespace tools/example-showcase/disable-audio.patch
|
||||
- name: Build bevy
|
||||
# this uses the same command as when running the example to ensure build is reused
|
||||
run: |
|
||||
TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome"
|
||||
- name: Run examples
|
||||
run: |
|
||||
for example in .github/example-run/*.ron; do
|
||||
example_name=`basename $example .ron`
|
||||
echo -n $example_name > last_example_run
|
||||
echo "running $example_name - "`date`
|
||||
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
|
||||
sleep 10
|
||||
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
|
||||
mkdir screenshots-$example_name
|
||||
mv screenshot-*.png screenshots-$example_name/
|
||||
fi
|
||||
done
|
||||
mkdir traces && mv trace*.json traces/
|
||||
mkdir screenshots && mv screenshots-* screenshots/
|
||||
- name: save traces
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: example-traces-macos
|
||||
path: traces
|
||||
- name: save screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-macos
|
||||
path: screenshots
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: example-run-macos
|
||||
path: example-run/
|
||||
|
||||
check-doc:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
@ -326,7 +299,8 @@ jobs:
|
||||
# See tools/ci/src/main.rs for the commands this runs
|
||||
run: cargo run -p ci -- doc
|
||||
env:
|
||||
RUSTFLAGS: "-C debuginfo=0 -D warnings"
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0"
|
||||
# This currently report a lot of false positives
|
||||
# Enable it again once it's fixed - https://github.com/bevyengine/bevy/issues/1983
|
||||
# - name: Installs cargo-deadlinks
|
||||
@ -340,7 +314,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: check for missing metadata
|
||||
id: missing-metadata
|
||||
run: cargo run -p build-templated-pages -- check-missing examples
|
||||
@ -375,7 +348,6 @@ jobs:
|
||||
needs: check-missing-examples-in-docs
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: check for missing features
|
||||
id: missing-features
|
||||
run: cargo run -p build-templated-pages -- check-missing features
|
||||
@ -419,7 +391,6 @@ jobs:
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-msrv-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: get MSRV
|
||||
id: msrv
|
||||
run: |
|
||||
@ -449,19 +420,44 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for internal Bevy imports
|
||||
- name: Check for bevy_internal imports
|
||||
shell: bash
|
||||
run: |
|
||||
errors=""
|
||||
for file in $(find examples tests -name '*.rs' -not -path 'examples/mobile/*'); do
|
||||
if grep -q "use bevy_" "$file"; then
|
||||
errors+="ERROR: Detected internal Bevy import in $file\n"
|
||||
for file in $(find examples tests -name '*.rs'); do
|
||||
if grep -q "use bevy_internal" "$file"; then
|
||||
errors+="ERROR: Detected 'use bevy_internal' in $file\n"
|
||||
fi
|
||||
done
|
||||
if [ -n "$errors" ]; then
|
||||
echo -e "$errors"
|
||||
echo " Avoid importing internal Bevy crates, they should not be used directly"
|
||||
echo " Fix the issue by replacing 'bevy_*' with 'bevy'"
|
||||
echo " Example: 'use bevy::sprite::Mesh2d;' instead of 'bevy_internal::sprite::Mesh2d;'"
|
||||
echo " Avoid importing bevy_internal, it should not be used directly"
|
||||
echo " Fix the issue by replacing 'bevy_internal' with 'bevy'"
|
||||
echo " Example: 'use bevy::sprite::MaterialMesh2dBundle;' instead of 'bevy_internal::sprite::MaterialMesh2dBundle;'"
|
||||
exit 1
|
||||
fi
|
||||
check-cfg:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-check-doc-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
with:
|
||||
wayland: true
|
||||
xkb: true
|
||||
- name: Build and check cfg typos
|
||||
# See tools/ci/src/main.rs for the commands this runs
|
||||
run: cargo run -p ci -- cfg-check
|
||||
|
138
.github/workflows/daily.yml
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
name: Daily Jobs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build-for-iOS:
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Add iOS targets
|
||||
run: rustup target add aarch64-apple-ios x86_64-apple-ios
|
||||
|
||||
- name: Build app for iOS
|
||||
run: |
|
||||
cd examples/mobile
|
||||
make xcodebuild-iphone
|
||||
mkdir Payload
|
||||
mv build/Build/Products/Debug-iphoneos/bevy_mobile_example.app Payload
|
||||
zip -r bevy_mobile_example.zip Payload
|
||||
mv bevy_mobile_example.zip bevy_mobile_example.ipa
|
||||
|
||||
- name: Upload to Browser Stack
|
||||
run: |
|
||||
curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
|
||||
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
|
||||
-F "file=@examples/mobile/bevy_mobile_example.ipa" \
|
||||
-F "custom_id=$GITHUB_RUN_ID"
|
||||
|
||||
build-for-Android:
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Add Android targets
|
||||
run: rustup target add aarch64-linux-android armv7-linux-androideabi
|
||||
|
||||
- name: Install Cargo APK
|
||||
run: cargo install --force cargo-apk
|
||||
|
||||
- name: Build app for Android
|
||||
run: ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME cargo apk build --package bevy_mobile_example
|
||||
env:
|
||||
# This will reduce the APK size from 1GB to ~200MB
|
||||
CARGO_PROFILE_DEV_DEBUG: false
|
||||
|
||||
- name: Upload to Browser Stack
|
||||
run: |
|
||||
curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
|
||||
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
|
||||
-F "file=@target/debug/apk/bevyexample.apk" \
|
||||
-F "custom_id=$GITHUB_RUN_ID"
|
||||
|
||||
nonce:
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
result: ${{ steps.nonce.outputs.result }}
|
||||
steps:
|
||||
- id: nonce
|
||||
run: echo "result=${{ github.run_id }}-$(date +%s)" >> $GITHUB_OUTPUT
|
||||
|
||||
run:
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: [nonce, build-for-iOS, build-for-Android]
|
||||
env:
|
||||
PERCY_PARALLEL_NONCE: ${{ needs.nonce.outputs.result }}
|
||||
PERCY_PARALLEL_TOTAL: ${{ strategy.job-total }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- device: "iPhone 13"
|
||||
os_version: "15"
|
||||
- device: "iPhone 14"
|
||||
os_version: "16"
|
||||
- device: "iPhone 15"
|
||||
os_version: "17"
|
||||
- device: "Xiaomi Redmi Note 11"
|
||||
os_version: "11.0"
|
||||
- device: "Google Pixel 6"
|
||||
os_version: "12.0"
|
||||
- device: "Samsung Galaxy S23"
|
||||
os_version: "13.0"
|
||||
- device: "Google Pixel 8"
|
||||
os_version: "14.0"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run Example
|
||||
run: |
|
||||
cd .github/start-mobile-example
|
||||
npm install
|
||||
npm install -g @percy/cli@latest
|
||||
npx percy app:exec --parallel -- npm run mobile
|
||||
env:
|
||||
BROWSERSTACK_APP_ID: ${{ github.run_id }}
|
||||
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
|
||||
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
|
||||
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
||||
DEVICE: ${{ matrix.device }}
|
||||
OS_VERSION: ${{ matrix.os_version }}
|
||||
|
||||
- name: Save screenshots
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-${{ matrix.device }}-${{ matrix.os_version }}
|
||||
path: .github/start-mobile-example/*.png
|
||||
|
||||
check-result:
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: [run]
|
||||
steps:
|
||||
- name: Wait for screenshots comparison
|
||||
run: |
|
||||
npm install -g @percy/cli@latest
|
||||
npx percy build:wait --project dede4209/Bevy-Mobile-Example --commit ${{ github.sha }} --fail-on-changes --pass-if-approved
|
||||
env:
|
||||
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
|
20
.github/workflows/docs.yml
vendored
@ -58,22 +58,8 @@ jobs:
|
||||
- name: Build docs
|
||||
env:
|
||||
# needs to be in sync with [package.metadata.docs.rs]
|
||||
RUSTFLAGS: --cfg docsrs_dep
|
||||
RUSTDOCFLAGS: -Zunstable-options --cfg=docsrs --generate-link-to-definition --html-after-content docs-rs/trait-tags.html
|
||||
run: |
|
||||
cargo doc \
|
||||
-Zunstable-options \
|
||||
-Zrustdoc-scrape-examples \
|
||||
--all-features \
|
||||
--workspace \
|
||||
--no-deps \
|
||||
--document-private-items \
|
||||
--exclude ci \
|
||||
--exclude errors \
|
||||
--exclude bevy_mobile_example \
|
||||
--exclude build-wasm-example \
|
||||
--exclude build-templated-pages \
|
||||
--exclude example-showcase
|
||||
RUSTDOCFLAGS: -Zunstable-options --cfg=docsrs
|
||||
run: cargo doc --all-features --no-deps -p bevy -Zunstable-options -Zrustdoc-scrape-examples
|
||||
|
||||
# This adds the following:
|
||||
# - A top level redirect to the bevy crate documentation
|
||||
@ -83,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
echo "<meta http-equiv=\"refresh\" content=\"0; url=bevy/index.html\">" > target/doc/index.html
|
||||
echo "dev-docs.bevyengine.org" > target/doc/CNAME
|
||||
echo $'User-Agent: *\nDisallow: /' > target/doc/robots.txt
|
||||
echo "User-Agent: *\nDisallow: /" > target/doc/robots.txt
|
||||
rm target/doc/.lock
|
||||
|
||||
- name: Upload site artifact
|
||||
|
120
.github/workflows/example-run-report.yml
vendored
@ -1,120 +0,0 @@
|
||||
name: Example Run - PR Comments
|
||||
|
||||
# This workflow has write permissions on the repo
|
||||
# It must not checkout a PR and run untrusted code
|
||||
|
||||
# Also requesting write permissions on PR to be able to comment
|
||||
permissions:
|
||||
pull-requests: "write"
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Example Run"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
make-macos-screenshots-available:
|
||||
if: github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
outputs:
|
||||
branch-name: ${{ steps.branch-name.outputs.result }}
|
||||
pr-number: ${{ steps.pr-number.outputs.result }}
|
||||
steps:
|
||||
- name: "Download artifact"
|
||||
id: find-artifact
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "screenshots-macos"
|
||||
});
|
||||
if (matchArtifacts.length == 0) { return "false" }
|
||||
var matchArtifact = matchArtifacts[0];
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/screenshots-macos.zip', Buffer.from(download.data));
|
||||
return "true"
|
||||
- name: prepare artifact folder
|
||||
run: |
|
||||
unzip screenshots-macos.zip
|
||||
mkdir screenshots
|
||||
mv screenshots-* screenshots/
|
||||
- name: save screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-macos
|
||||
path: screenshots
|
||||
- name: branch name
|
||||
id: branch-name
|
||||
run: |
|
||||
echo "result=PR-$(cat PR)-${{ github.event.workflow_run.head_branch }}" >> $GITHUB_OUTPUT
|
||||
- name: PR number
|
||||
id: pr-number
|
||||
run: |
|
||||
echo "result=$(cat PR)" >> $GITHUB_OUTPUT
|
||||
|
||||
compare-macos-screenshots:
|
||||
name: Compare macOS screenshots
|
||||
needs: [make-macos-screenshots-available]
|
||||
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
|
||||
with:
|
||||
commit: ${{ github.event.workflow_run.head_sha }}
|
||||
branch: ${{ needs.make-macos-screenshots-available.outputs.branch-name }}
|
||||
artifact: screenshots-macos
|
||||
os: macos
|
||||
secrets: inherit
|
||||
|
||||
comment-on-pr:
|
||||
name: Comment on PR
|
||||
runs-on: ubuntu-latest
|
||||
needs: [make-macos-screenshots-available, compare-macos-screenshots]
|
||||
if: ${{ always() && needs.compare-macos-screenshots.result == 'failure' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Check if PR already has label"
|
||||
id: check-label
|
||||
env:
|
||||
PR: ${{ needs.make-macos-screenshots-available.outputs.pr-number }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [[ `gh api --jq '.labels.[].name' /repos/bevyengine/bevy/pulls/$PR` =~ "M-Deliberate-Rendering-Change" ]]
|
||||
then
|
||||
echo "result=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "result=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: "Check if last comment is already from actions"
|
||||
id: check-last-comment
|
||||
env:
|
||||
PR: ${{ needs.make-macos-screenshots-available.outputs.pr-number }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot' ]]
|
||||
then
|
||||
echo "result=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "result=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: "Comment on PR"
|
||||
if: ${{ steps.check-label.outputs.result == 'false' && steps.check-last-comment.outputs.result == 'false' }}
|
||||
env:
|
||||
PROJECT: B04F67C0-C054-4A6F-92EC-F599FEC2FD1D
|
||||
PR: ${{ needs.make-macos-screenshots-available.outputs.pr-number }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
LF=$'\n'
|
||||
COMMENT_BODY="Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke! ${LF}You can review it at https://pixel-eagle.com/project/$PROJECT?filter=PR-$PR ${LF} ${LF}If it's expected, please add the M-Deliberate-Rendering-Change label. ${LF} ${LF}If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it."
|
||||
gh issue comment $PR --body "$COMMENT_BODY"
|
187
.github/workflows/example-run.yml
vendored
@ -1,187 +0,0 @@
|
||||
name: Example Run
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
# also run when pushed to main to update reference screenshots
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
|
||||
jobs:
|
||||
run-examples-macos-metal:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Disable audio
|
||||
# Disable audio through a patch. on github m1 runners, audio timeouts after 15 minutes
|
||||
run: git apply --ignore-whitespace tools/example-showcase/disable-audio.patch
|
||||
- name: Run examples
|
||||
run: |
|
||||
for example in .github/example-run/*.ron; do
|
||||
example_name=`basename $example .ron`
|
||||
echo -n $example_name > last_example_run
|
||||
echo "running $example_name - "`date`
|
||||
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
|
||||
sleep 10
|
||||
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
|
||||
mkdir screenshots-$example_name
|
||||
mv screenshot-*.png screenshots-$example_name/
|
||||
fi
|
||||
done
|
||||
mkdir traces && mv trace*.json traces/
|
||||
mkdir screenshots && mv screenshots-* screenshots/
|
||||
- name: save traces
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: example-traces-macos
|
||||
path: traces
|
||||
- name: Save PR number
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo ${{ github.event.number }} > ./screenshots/PR
|
||||
- name: save screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-macos
|
||||
path: screenshots
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: example-run-macos
|
||||
path: example-run/
|
||||
|
||||
compare-macos-screenshots:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
name: Compare Macos screenshots
|
||||
needs: [run-examples-macos-metal]
|
||||
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
|
||||
with:
|
||||
commit: ${{ github.sha }}
|
||||
branch: ${{ github.ref_name }}
|
||||
artifact: screenshots-macos
|
||||
os: macos
|
||||
secrets: inherit
|
||||
|
||||
run-examples-linux-vulkan:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
# At some point this may be merged into `install-linux-deps`, but for now it is its own step.
|
||||
- name: Install additional Linux dependencies for Vulkan
|
||||
run: |
|
||||
sudo add-apt-repository ppa:kisak/turtle -y
|
||||
sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-run-examples-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run examples
|
||||
run: |
|
||||
for example in .github/example-run/*.ron; do
|
||||
example_name=`basename $example .ron`
|
||||
echo -n $example_name > last_example_run
|
||||
echo "running $example_name - "`date`
|
||||
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
|
||||
sleep 10
|
||||
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
|
||||
mkdir screenshots-$example_name
|
||||
mv screenshot-*.png screenshots-$example_name/
|
||||
fi
|
||||
done
|
||||
mkdir traces && mv trace*.json traces/
|
||||
mkdir screenshots && mv screenshots-* screenshots/
|
||||
- name: save traces
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: example-traces-linux
|
||||
path: traces
|
||||
- name: save screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-linux
|
||||
path: screenshots
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: example-run-linux
|
||||
path: example-run/
|
||||
|
||||
compare-linux-screenshots:
|
||||
name: Compare Linux screenshots
|
||||
needs: [run-examples-linux-vulkan]
|
||||
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
|
||||
with:
|
||||
commit: ${{ github.sha }}
|
||||
branch: ${{ github.ref_name }}
|
||||
artifact: screenshots-linux
|
||||
os: linux
|
||||
secrets: inherit
|
||||
|
||||
run-examples-on-windows-dx12:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Run examples
|
||||
shell: bash
|
||||
run: |
|
||||
for example in .github/example-run/*.ron; do
|
||||
example_name=`basename $example .ron`
|
||||
echo -n $example_name > last_example_run
|
||||
echo "running $example_name - "`date`
|
||||
time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "statically-linked-dxc,bevy_ci_testing,trace,trace_chrome"
|
||||
sleep 10
|
||||
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
|
||||
mkdir screenshots-$example_name
|
||||
mv screenshot-*.png screenshots-$example_name/
|
||||
fi
|
||||
done
|
||||
mkdir traces && mv trace*.json traces/
|
||||
mkdir screenshots && mv screenshots-* screenshots/
|
||||
- name: save traces
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: example-traces-windows
|
||||
path: traces
|
||||
- name: save screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-windows
|
||||
path: screenshots
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: example-run-windows
|
||||
path: example-run/
|
||||
|
||||
compare-windows-screenshots:
|
||||
name: Compare Windows screenshots
|
||||
needs: [run-examples-on-windows-dx12]
|
||||
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
|
||||
with:
|
||||
commit: ${{ github.sha }}
|
||||
branch: ${{ github.ref_name }}
|
||||
artifact: screenshots-windows
|
||||
os: windows
|
||||
secrets: inherit
|
13
.github/workflows/post-release.yml
vendored
@ -8,12 +8,9 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
ci:
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -30,9 +27,9 @@ jobs:
|
||||
# Read the current version from Cargo.toml
|
||||
current_version=$(cargo metadata --format-version 1 --no-deps | \
|
||||
jq --raw-output '.packages | .[] | select(.name == "bevy").version')
|
||||
# Sanity check: current version should be 0.X.Y-dev
|
||||
if ! grep -q '^0\.[0-9]\+\.[0-9]\+-dev$' <<< "${current_version}"; then
|
||||
echo "Invalid version (not in 0.X.Y-dev format): ${current_version}"
|
||||
# Sanity check: current version should be 0.X.Y
|
||||
if ! grep -q '^0\.[0-9]\+\.[0-9]\+$' <<< "${current_version}"; then
|
||||
echo "Invalid version (not in 0.X.Y format): ${current_version}"
|
||||
exit 1
|
||||
fi
|
||||
minor_version=$(sed 's/^0\.\([0-9]\+\).*/\1/' <<< "${current_version}")
|
||||
@ -52,7 +49,7 @@ jobs:
|
||||
--exclude build-wasm-example
|
||||
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
delete-branch: true
|
||||
base: "main"
|
||||
|
55
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: Release
|
||||
|
||||
# how to trigger: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install cargo-release
|
||||
run: cargo install cargo-release
|
||||
|
||||
- name: Setup release
|
||||
run: |
|
||||
# Set the commit author to the github-actions bot. See discussion here for more information:
|
||||
# https://github.com/actions/checkout/issues/13#issuecomment-724415212
|
||||
# https://github.community/t/github-actions-bot-email-address/17204/6
|
||||
git config user.name 'Bevy Auto Releaser'
|
||||
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
# release: remove the dev suffix, like going from 0.X.0-dev to 0.X.0
|
||||
# --workspace: updating all crates in the workspace
|
||||
# --no-publish: do not publish to crates.io
|
||||
# --execute: not a dry run
|
||||
# --no-tag: do not push tag for each new version
|
||||
# --no-push: do not push the update commits
|
||||
# --dependent-version upgrade: change 0.X.0-dev in internal dependencies to 0.X.0
|
||||
# --exclude: ignore those packages
|
||||
cargo release release \
|
||||
--workspace \
|
||||
--no-publish \
|
||||
--execute \
|
||||
--no-tag \
|
||||
--no-confirm \
|
||||
--no-push \
|
||||
--dependent-version upgrade \
|
||||
--exclude ci \
|
||||
--exclude errors \
|
||||
--exclude bevy_mobile_example \
|
||||
--exclude build-wasm-example
|
||||
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
delete-branch: true
|
||||
base: "main"
|
||||
title: "Preparing Next Release"
|
||||
body: |
|
||||
Preparing next release. This PR has been auto-generated.
|
109
.github/workflows/send-screenshots-to-pixeleagle.yml
vendored
@ -1,109 +0,0 @@
|
||||
name: Send Screenshots to Pixel Eagle
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
artifact:
|
||||
required: true
|
||||
type: string
|
||||
commit:
|
||||
required: true
|
||||
type: string
|
||||
branch:
|
||||
required: true
|
||||
type: string
|
||||
os:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
# Unfortunately, we can't check secrets in `if:` conditionals. However, Github's own documentation
|
||||
# suggests a workaround: Putting the secret in an environment variable, and checking that instead.
|
||||
PIXELEAGLE_TOKEN_EXISTS: ${{ secrets.PIXELEAGLE_TOKEN != '' }}
|
||||
|
||||
jobs:
|
||||
send-to-pixel-eagle:
|
||||
name: Send screenshots to Pixel Eagle
|
||||
runs-on: ubuntu-24.04
|
||||
# Pixel Eagle is irrelevant for most forks, even of those that allow workflows to run. Thus, we
|
||||
# disable this job for any forks. Any forks where Pixel Eagle is relevant can comment out the
|
||||
# `if:` conditional below.
|
||||
if: ${{ github.repository == 'bevyengine/bevy' }}
|
||||
steps:
|
||||
- name: Notify user on non-existent token
|
||||
if: ${{ ! fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }}
|
||||
run: |
|
||||
echo "The PIXELEAGLE_TOKEN secret does not exist, so uploading screenshots to Pixel Eagle was skipped." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Download artifact
|
||||
if: ${{ fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: ${{ inputs.artifact }}
|
||||
|
||||
- name: Send to Pixel Eagle
|
||||
if: ${{ fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }}
|
||||
env:
|
||||
project: B04F67C0-C054-4A6F-92EC-F599FEC2FD1D
|
||||
run: |
|
||||
# Create a new run with its associated metadata
|
||||
metadata='{"os":"${{ inputs.os }}", "commit": "${{ inputs.commit }}", "branch": "${{ inputs.branch }}"}'
|
||||
run=`curl https://pixel-eagle.com/$project/runs --json "$metadata" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} | jq '.id'`
|
||||
|
||||
SAVEIFS=$IFS
|
||||
|
||||
cd ${{ inputs.artifact }}
|
||||
|
||||
# Read the hashes of the screenshot for fast comparison when they are equal
|
||||
IFS=$'\n'
|
||||
# Build a json array of screenshots and their hashes
|
||||
hashes='[';
|
||||
for screenshot in $(find . -type f -name "*.png");
|
||||
do
|
||||
name=${screenshot:14}
|
||||
echo $name
|
||||
hash=`shasum -a 256 $screenshot | awk '{print $1}'`
|
||||
hashes="$hashes [\"$name\",\"$hash\"],"
|
||||
done
|
||||
hashes=`echo $hashes | rev | cut -c 2- | rev`
|
||||
hashes="$hashes]"
|
||||
|
||||
IFS=$SAVEIFS
|
||||
|
||||
# Upload screenshots with unknown hashes
|
||||
curl https://pixel-eagle.com/$project/runs/$run/hashes --json "$hashes" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} | jq '.[]|[.name] | @tsv' |
|
||||
while IFS=$'\t' read -r name; do
|
||||
name=`echo $name | tr -d '"'`
|
||||
echo "Uploading $name"
|
||||
curl https://pixel-eagle.com/$project/runs/$run/screenshots -F "data=@./screenshots-$name" -F "screenshot=$name" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }}
|
||||
echo
|
||||
done
|
||||
|
||||
IFS=$SAVEIFS
|
||||
|
||||
cd ..
|
||||
|
||||
# Trigger comparison with the main branch on the same os
|
||||
curl https://pixel-eagle.com/$project/runs/$run/compare/auto --json '{"os":"<equal>", "branch": "main"}' --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} > pixeleagle.json
|
||||
|
||||
# Log results
|
||||
compared_with=`cat pixeleagle.json | jq '.to'`
|
||||
|
||||
status=0
|
||||
missing=`cat pixeleagle.json | jq '.missing | length'`
|
||||
if [ ! $missing -eq 0 ]; then
|
||||
echo "There are $missing missing screenshots"
|
||||
echo "::warning title=$missing missing screenshots on ${{ inputs.os }}::https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with"
|
||||
status=1
|
||||
fi
|
||||
|
||||
diff=`cat pixeleagle.json | jq '.diff | length'`
|
||||
if [ ! $diff -eq 0 ]; then
|
||||
echo "There are $diff screenshots with a difference"
|
||||
echo "::warning title=$diff different screenshots on ${{ inputs.os }}::https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with"
|
||||
status=1
|
||||
fi
|
||||
|
||||
echo "created run $run: https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with"
|
||||
|
||||
exit $status
|
137
.github/workflows/validation-jobs.yml
vendored
@ -5,6 +5,7 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release-*
|
||||
|
||||
concurrency:
|
||||
@ -13,9 +14,6 @@ concurrency:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
# If nightly is breaking CI, modify this variable to target a specific nightly version.
|
||||
NIGHTLY_TOOLCHAIN: nightly
|
||||
|
||||
@ -33,7 +31,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
target
|
||||
key: ${{ runner.os }}-ios-install-${{ hashFiles('**/Cargo.lock') }}
|
||||
key: ${{ runner.os }}-ios-install-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
# TODO: remove x86 target once it always run on arm GitHub runners
|
||||
- name: Add iOS targets
|
||||
@ -51,12 +49,6 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
@ -68,16 +60,115 @@ jobs:
|
||||
key: ${{ runner.os }}-cargo-build-android-${{ hashFiles('**/Cargo.toml') }}
|
||||
|
||||
- name: Install Android targets
|
||||
run: rustup target add aarch64-linux-android
|
||||
run: rustup target add aarch64-linux-android armv7-linux-androideabi
|
||||
|
||||
- name: Install Cargo NDK
|
||||
run: cargo install --force cargo-ndk
|
||||
- name: Install Cargo APK
|
||||
run: cargo install --force cargo-apk
|
||||
|
||||
- name: Build .so file
|
||||
run: cargo ndk -t arm64-v8a -o android_example/app/src/main/jniLibs build --package bevy_mobile_example
|
||||
- name: Build APK
|
||||
run: ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME cargo apk build --package bevy_mobile_example
|
||||
|
||||
- name: Build app for Android
|
||||
run: cd examples/mobile/android_example && chmod +x gradlew && ./gradlew build
|
||||
run-examples-linux-vulkan:
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
# At some point this may be merged into `install-linux-deps`, but for now it is its own step.
|
||||
- name: Install additional Linux dependencies for Vulkan
|
||||
run: |
|
||||
sudo add-apt-repository ppa:kisak/turtle -y
|
||||
sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-run-examples-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Build bevy
|
||||
# this uses the same command as when running the example to ensure build is reused
|
||||
run: |
|
||||
TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome"
|
||||
- name: Run examples
|
||||
run: |
|
||||
for example in .github/example-run/*.ron; do
|
||||
example_name=`basename $example .ron`
|
||||
echo -n $example_name > last_example_run
|
||||
echo "running $example_name - "`date`
|
||||
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
|
||||
sleep 10
|
||||
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
|
||||
mkdir screenshots-$example_name
|
||||
mv screenshot-*.png screenshots-$example_name/
|
||||
fi
|
||||
done
|
||||
mkdir traces && mv trace*.json traces/
|
||||
mkdir screenshots && mv screenshots-* screenshots/
|
||||
- name: save traces
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: example-traces-linux
|
||||
path: traces
|
||||
- name: save screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-linux
|
||||
path: screenshots
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: example-run-linux
|
||||
path: example-run/
|
||||
|
||||
run-examples-on-windows-dx12:
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Build bevy
|
||||
shell: bash
|
||||
# this uses the same command as when running the example to ensure build is reused
|
||||
run: |
|
||||
WGPU_BACKEND=dx12 TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome"
|
||||
- name: Run examples
|
||||
shell: bash
|
||||
run: |
|
||||
for example in .github/example-run/*.ron; do
|
||||
example_name=`basename $example .ron`
|
||||
echo -n $example_name > last_example_run
|
||||
echo "running $example_name - "`date`
|
||||
time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
|
||||
sleep 10
|
||||
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
|
||||
mkdir screenshots-$example_name
|
||||
mv screenshot-*.png screenshots-$example_name/
|
||||
fi
|
||||
done
|
||||
mkdir traces && mv trace*.json traces/
|
||||
mkdir screenshots && mv screenshots-* screenshots/
|
||||
- name: save traces
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: example-traces-windows
|
||||
path: traces
|
||||
- name: save screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots-windows
|
||||
path: screenshots
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() && github.event_name == 'pull_request' }}
|
||||
with:
|
||||
name: example-run-windows
|
||||
path: example-run/
|
||||
|
||||
run-examples-on-wasm:
|
||||
if: ${{ github.event_name == 'merge_group' }}
|
||||
@ -101,6 +192,13 @@ jobs:
|
||||
target/
|
||||
key: ${{ runner.os }}-wasm-run-examples-${{ hashFiles('**/Cargo.toml') }}
|
||||
|
||||
- name: install xvfb, llvmpipe and lavapipe
|
||||
run: |
|
||||
sudo apt-get update -y -qq
|
||||
sudo add-apt-repository ppa:kisak/turtle -y
|
||||
sudo apt-get update
|
||||
sudo apt install -y xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
|
||||
|
||||
- name: Install wasm-bindgen
|
||||
run: cargo install --force wasm-bindgen-cli
|
||||
|
||||
@ -111,9 +209,9 @@ jobs:
|
||||
npx playwright install --with-deps
|
||||
cd ../..
|
||||
|
||||
- name: First Wasm build
|
||||
- name: First WASM build
|
||||
run: |
|
||||
cargo build --release --example testbed_ui --target wasm32-unknown-unknown
|
||||
cargo build --release --example ui --target wasm32-unknown-unknown
|
||||
|
||||
- name: Run examples
|
||||
shell: bash
|
||||
@ -145,6 +243,7 @@ jobs:
|
||||
- name: Build
|
||||
run: cargo build -p ${{ matrix.crate }} --no-default-features
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0 -D warnings"
|
||||
|
||||
build-without-default-features-status:
|
||||
|
76
.github/workflows/weekly.yml
vendored
@ -9,25 +9,6 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
ISSUE_TITLE: Main branch fails to compile on Rust beta.
|
||||
|
||||
# The jobs listed here are intentionally skipped when running on forks, for a number of reasons:
|
||||
#
|
||||
# * Scheduled workflows run on the base/default branch, with no way (currently) to change this. On
|
||||
# forks, the base/default branch is usually kept in sync with the main Bevy repository, meaning
|
||||
# that running this workflow on forks would just be a waste of resources.
|
||||
#
|
||||
# * Even if there was a way to change the branch that a scheduled workflow runs on, forks default
|
||||
# to not having an issue tracker.
|
||||
#
|
||||
# * Even in the event that a fork's issue tracker is enabled, most users probably don't want to
|
||||
# receive automated issues in the event of a compilation failure.
|
||||
#
|
||||
# Because of these reasons, this workflow is irrelevant for 99% of forks. Thus, the jobs here will
|
||||
# be skipped when running on any repository that isn't the main Bevy repository.
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@ -35,8 +16,6 @@ jobs:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
# Disable this job when running on a fork.
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -47,12 +26,11 @@ jobs:
|
||||
# See tools/ci/src/main.rs for the commands this runs
|
||||
run: cargo run -p ci -- test
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0 -D warnings"
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this job when running on a fork.
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -70,8 +48,6 @@ jobs:
|
||||
|
||||
check-compiles:
|
||||
runs-on: ubuntu-latest
|
||||
# Disable this job when running on a fork.
|
||||
if: github.repository == 'bevyengine/bevy'
|
||||
timeout-minutes: 30
|
||||
needs: test
|
||||
steps:
|
||||
@ -83,59 +59,53 @@ jobs:
|
||||
# See tools/ci/src/main.rs for the commands this runs
|
||||
run: cargo run -p ci -- compile
|
||||
|
||||
close-any-open-issues:
|
||||
check-doc:
|
||||
runs-on: ubuntu-latest
|
||||
needs: ['test', 'lint', 'check-compiles']
|
||||
permissions:
|
||||
issues: write
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Close issues
|
||||
run: |
|
||||
previous_issue_number=$(gh issue list \
|
||||
--search "$ISSUE_TITLE in:title" \
|
||||
--json number \
|
||||
--jq '.[0].number')
|
||||
if [[ -n $previous_issue_number ]]; then
|
||||
gh issue close $previous_issue_number \
|
||||
-r completed \
|
||||
-c $COMMENT
|
||||
fi
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@beta
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
with:
|
||||
wayland: true
|
||||
xkb: true
|
||||
- name: Build and check docs
|
||||
# See tools/ci/src/main.rs for the commands this runs
|
||||
run: cargo run -p ci -- doc
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
COMMENT: |
|
||||
[Last pipeline run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) successfully completed. Closing issue.
|
||||
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-C debuginfo=0"
|
||||
|
||||
open-issue:
|
||||
name: Warn that weekly CI fails
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, lint, check-compiles]
|
||||
needs: [test, lint, check-compiles, check-doc]
|
||||
permissions:
|
||||
issues: write
|
||||
# We disable this job on forks, because
|
||||
# Use always() so the job doesn't get canceled if any other jobs fail
|
||||
if: ${{ github.repository == 'bevyengine/bevy' && always() && contains(needs.*.result, 'failure') }}
|
||||
# Use always() so the job doesn't get canceled if any other jobs fail
|
||||
if: ${{ always() && contains(needs.*.result, 'failure') }}
|
||||
steps:
|
||||
- name: Create issue
|
||||
run: |
|
||||
previous_issue_number=$(gh issue list \
|
||||
--search "$ISSUE_TITLE in:title" \
|
||||
--search "$TITLE in:title" \
|
||||
--json number \
|
||||
--jq '.[0].number')
|
||||
if [[ -n $previous_issue_number ]]; then
|
||||
gh issue comment $previous_issue_number \
|
||||
--body "Weekly pipeline still fails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
--body "Weekly pipeline still fails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
else
|
||||
gh issue create \
|
||||
--title "$ISSUE_TITLE" \
|
||||
--title "$TITLE" \
|
||||
--label "$LABELS" \
|
||||
--body "$BODY"
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
TITLE: Main branch fails to compile on Rust beta.
|
||||
LABELS: C-Bug,S-Needs-Triage
|
||||
BODY: |
|
||||
BODY: |
|
||||
## Weekly CI run has failed.
|
||||
[The offending run.](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
|
4
.github/workflows/welcome.yml
vendored
@ -11,8 +11,6 @@ on:
|
||||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
@ -43,5 +41,5 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
body: `**Welcome**, new contributor!
|
||||
|
||||
Please make sure you've read our [contributing guide](https://bevyengine.org/learn/contribute/introduction) and we look forward to reviewing your pull request shortly ✨`
|
||||
Please make sure you've read our [contributing guide](https://github.com/bevyengine/bevy/blob/main/CONTRIBUTING.md) and we look forward to reviewing your pull request shortly ✨`
|
||||
})
|
||||
|
38
.gitignore
vendored
@ -1,34 +1,24 @@
|
||||
# If your IDE needs additional project specific files, configure git to ignore them:
|
||||
# https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer
|
||||
|
||||
# Rust build artifacts
|
||||
target
|
||||
crates/**/target
|
||||
benches/**/target
|
||||
tools/**/target
|
||||
/target
|
||||
crates/*/target
|
||||
**/*.rs.bk
|
||||
rustc-ice-*.txt
|
||||
|
||||
# DX12 wgpu backend
|
||||
dxcompiler.dll
|
||||
dxil.dll
|
||||
|
||||
# Cargo
|
||||
Cargo.lock
|
||||
.cargo/config
|
||||
.cargo/config.toml
|
||||
|
||||
# Bevy Assets
|
||||
assets/**/*.meta
|
||||
crates/bevy_asset/imported_assets
|
||||
imported_assets
|
||||
|
||||
# Bevy Examples
|
||||
example_showcase_config.ron
|
||||
example-showcase-reports/
|
||||
/.idea
|
||||
/.vscode
|
||||
/benches/target
|
||||
dxcompiler.dll
|
||||
dxil.dll
|
||||
|
||||
# Generated by "examples/scene/scene.rs"
|
||||
assets/scenes/load_scene_example-new.scn.ron
|
||||
|
||||
# Generated by "examples/window/screenshot.rs"
|
||||
**/screenshot-*.png
|
||||
|
||||
assets/**/*.meta
|
||||
crates/bevy_asset/imported_assets
|
||||
imported_assets
|
||||
|
||||
example_showcase_config.ron
|
||||
example-showcase-reports/
|
||||
|
6946
CHANGELOG.md
Normal file
466
CONTRIBUTING.md
@ -1,4 +1,466 @@
|
||||
# Contributing to Bevy
|
||||
|
||||
If you'd like to help build Bevy, start by reading this
|
||||
[introduction](https://bevyengine.org/learn/contribute/introduction). Thanks for contributing!
|
||||
Hey, so you're interested in contributing to Bevy!
|
||||
Feel free to pitch in on whatever interests you and we'll be happy to help you contribute.
|
||||
|
||||
Check out our community's [Code of Conduct](https://github.com/bevyengine/bevy/blob/main/CODE_OF_CONDUCT.md) and feel free to say hi on [Discord] if you'd like.
|
||||
It's a nice place to chat about Bevy development, ask questions, and get to know the other contributors and users in a less formal setting.
|
||||
|
||||
Read on if you're looking for:
|
||||
|
||||
* The high-level design goals of Bevy.
|
||||
* Conventions and informal practices we follow when developing Bevy.
|
||||
* General advice on good open source collaboration practices.
|
||||
* Concrete ways you can help us, no matter your background or skill level.
|
||||
|
||||
We're thrilled to have you along as we build!
|
||||
|
||||
## Getting oriented
|
||||
|
||||
Bevy, like any general-purpose game engine, is a large project!
|
||||
It can be a bit overwhelming to start, so here's the bird's-eye view.
|
||||
|
||||
The [Bevy Engine Organization](https://github.com/bevyengine) has 4 primary repos:
|
||||
|
||||
1. [**`bevy`**](https://github.com/bevyengine/bevy): This is where the engine itself lives. The bulk of development work occurs here.
|
||||
2. [**`bevy-website`**](https://github.com/bevyengine/bevy-website): Where the [official website](https://bevyengine.org/), release notes, Bevy Book, and Bevy Assets are hosted. It is created using the Zola static site generator.
|
||||
3. [**`bevy-assets`**](https://github.com/bevyengine/bevy-assets): A collection of community-made tutorials, plugins, crates, games, and tools! Make a PR if you want to showcase your projects there!
|
||||
4. [**`rfcs`**](https://github.com/bevyengine/rfcs): A place to collaboratively build and reach consensus on designs for large or controversial features.
|
||||
|
||||
The `bevy` repo itself contains many smaller subcrates. Most of them can be used by themselves and many of them can be modularly replaced. This enables developers to pick and choose the parts of Bevy that they want to use.
|
||||
|
||||
Some crates of interest:
|
||||
|
||||
* [**`bevy_ecs`**](./crates/bevy_ecs): The core data model for Bevy. Most Bevy features are implemented on top of it. It is also fully functional as a stand-alone ECS, which can be very valuable if you're looking to integrate it with other game engines or use it for non-game executables.
|
||||
* [**`bevy_app`**](./crates/bevy_app): The api used to define Bevy Plugins and compose them together into Bevy Apps.
|
||||
* [**`bevy_tasks`**](./crates/bevy_tasks): Our light-weight async executor. This drives most async and parallel code in Bevy.
|
||||
* [**`bevy_render`**](./crates/bevy_render): Our core renderer API. It handles interaction with the GPU, such as the creation of Meshes, Textures, and Shaders. It also exposes a modular Render Graph for composing render pipelines. All 2D and 3D render features are implemented on top of this crate.
|
||||
|
||||
## What we're trying to build
|
||||
|
||||
Bevy is a completely free and open source game engine built in Rust. It currently has the following design goals:
|
||||
|
||||
* **Capable**: Offer a complete 2D and 3D feature set.
|
||||
* **Simple**: Easy for newbies to pick up, but infinitely flexible for power users.
|
||||
* **Data Focused**: Data-oriented architecture using the Entity Component System paradigm.
|
||||
* **Modular**: Use only what you need. Replace what you don't like.
|
||||
* **Fast**: App logic should run quickly, and when possible, in parallel.
|
||||
* **Productive**: Changes should compile quickly ... waiting isn't fun.
|
||||
|
||||
Bevy also currently has the following "development process" goals:
|
||||
|
||||
* **Rapid experimentation over API stability**: We need the freedom to experiment and iterate in order to build the best engine we can. This will change over time as APIs prove their staying power.
|
||||
* **Consistent vision**: The engine needs to feel consistent and cohesive. This takes precedence over democratic and/or decentralized processes. See our [*Bevy Organization doc*](/docs/the_bevy_organization.md) for more details.
|
||||
* **Flexibility over bureaucracy**: Developers should feel productive and unencumbered by development processes.
|
||||
* **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 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:
|
||||
* 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).
|
||||
|
||||
## The Bevy Organization
|
||||
|
||||
The Bevy Organization is the group of people responsible for stewarding the Bevy project. It handles things like merging pull requests, choosing project direction, managing bugs / issues / feature requests, running the Bevy website, controlling access to secrets, defining and enforcing best practices, etc.
|
||||
|
||||
Note that you *do not* need to be a member of the Bevy Organization to contribute to Bevy. Community contributors (this means you) can freely open issues, submit pull requests, and review pull requests.
|
||||
|
||||
Check out our dedicated [Bevy Organization document](/docs/the_bevy_organization.md) to learn more about how we're organized.
|
||||
|
||||
### Classifying PRs
|
||||
|
||||
[Labels](https://github.com/bevyengine/bevy/labels) are our primary tool to organize work.
|
||||
Each label has a prefix denoting its category:
|
||||
|
||||
* **D:** Difficulty. In order, these are:
|
||||
* `D-Trivial`: typos, obviously incorrect one-line bug fixes, code reorganization, renames
|
||||
* `D-Straightforward`: simple bug fixes and API improvements, docs, test and examples
|
||||
* `D-Modest`: new features, refactors, challenging bug fixes
|
||||
* `D-Complex`: rewrites and unusually complex features
|
||||
* When applied to an issue, these labels reflect the estimated level of expertise (not time) required to fix the issue.
|
||||
* When applied to a PR, these labels reflect the estimated level of expertise required to *review* the PR.
|
||||
* The `D-Domain-Expert` and `D-Domain-Agnostic` labels are modifiers, which describe if unusually high or low degrees of domain-specific knowledge are required.
|
||||
* The `D-Unsafe` label is applied to any code that touches `unsafe` Rust, which requires special skills and scrutiny.
|
||||
* **X:** Controversiality. In order, these are:
|
||||
* `X-Uncontroversial`: everyone should agree that this is a good idea
|
||||
* `X-Contentious`: there's real design thought needed to ensure that this is the right path forward
|
||||
* `X-Controversial`: there's active disagreement and/or large-scale architectural implications involved
|
||||
* `X-Blessed`: work that was controversial, but whose controversial (but perhaps not technical) elements have been endorsed by the relevant decision makers.
|
||||
* **A:** Area (e.g. A-Animation, A-ECS, A-Rendering, ...).
|
||||
* **C:** Category (e.g. C-Breaking-Change, C-Code-Quality, C-Docs, ...).
|
||||
* **O:** Operating System (e.g. O-Linux, O-Web, O-Windows, ...).
|
||||
* **P:** Priority (e.g. P-Critical, P-High, ...)
|
||||
* Most work is not explicitly categorized by priority: volunteer work mostly occurs on an ad hoc basis depending on contributor interests
|
||||
* **S:** Status (e.g. S-Blocked, S-Needs-Review, S-Needs-Design, ...).
|
||||
|
||||
The rules for how PRs get merged depend on their classification by controversy and difficulty.
|
||||
More difficult PRs will require more careful review from experts,
|
||||
while more controversial PRs will require rewrites to reduce the costs involved and/or sign-off from Subject Matter Experts and Maintainers.
|
||||
|
||||
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.
|
||||
It is also a good idea to try and split out simple changes from more complex changes if it is not helpful for them to be reviewed together.
|
||||
|
||||
Some things that are reason to apply the [`S-Controversial`] label to a PR:
|
||||
|
||||
1. Changes to a project-wide workflow or style.
|
||||
2. New architecture for a large feature.
|
||||
3. Serious tradeoffs were made.
|
||||
4. Heavy user impact.
|
||||
5. New ways for users to make mistakes (footguns).
|
||||
6. Adding a dependency.
|
||||
7. Touching licensing information (due to level of precision required).
|
||||
8. Adding root-level files (due to the high level of visibility).
|
||||
|
||||
Some things that are reason to apply the [`D-Complex`] label to a PR:
|
||||
|
||||
1. Introduction or modification of soundness relevant code (for example `unsafe` code).
|
||||
2. High levels of technical complexity.
|
||||
3. Large-scale code reorganization.
|
||||
|
||||
Examples of PRs that are not [`S-Controversial`] or [`D-Complex`]:
|
||||
|
||||
* Fixing dead links.
|
||||
* Removing dead code or unused dependencies.
|
||||
* Typo and grammar fixes.
|
||||
* [Add `Mut::reborrow`](https://github.com/bevyengine/bevy/pull/7114).
|
||||
* [Add `Res::clone`](https://github.com/bevyengine/bevy/pull/4109).
|
||||
|
||||
Examples of PRs that are [`S-Controversial`] but not [`D-Complex`]:
|
||||
|
||||
* [Implement and require `#[derive(Component)]` on all component structs](https://github.com/bevyengine/bevy/pull/2254).
|
||||
* [Use default serde impls for Entity](https://github.com/bevyengine/bevy/pull/6194).
|
||||
|
||||
Examples of PRs that are not [`S-Controversial`] but are [`D-Complex`]:
|
||||
|
||||
* [Ensure `Ptr`/`PtrMut`/`OwningPtr` are aligned in debug builds](https://github.com/bevyengine/bevy/pull/7117).
|
||||
* [Replace `BlobVec`'s `swap_scratch` with a `swap_nonoverlapping`](https://github.com/bevyengine/bevy/pull/4853).
|
||||
|
||||
Examples of PRs that are both [`S-Controversial`] and [`D-Complex`]:
|
||||
|
||||
* [bevy_reflect: Binary formats](https://github.com/bevyengine/bevy/pull/6140).
|
||||
|
||||
Some useful pull request queries:
|
||||
|
||||
* [PRs which need reviews and are not `D-Complex`](https://github.com/bevyengine/bevy/pulls?q=is%3Apr+-label%3AD-Complex+-label%3AS-Ready-For-Final-Review+-label%3AS-Blocked++).
|
||||
* [`D-Complex` PRs which need reviews](https://github.com/bevyengine/bevy/pulls?q=is%3Apr+label%3AD-Complex+-label%3AS-Ready-For-Final-Review+-label%3AS-Blocked).
|
||||
|
||||
[`S-Controversial`]: https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+label%3AS-Controversial
|
||||
[`D-Complex`]: https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+label%3AD-Complex
|
||||
|
||||
### Prioritizing PRs and issues
|
||||
|
||||
We use [Milestones](https://github.com/bevyengine/bevy/milestones) to track issues and PRs that:
|
||||
|
||||
* Need to be merged/fixed before the next release. This is generally for extremely bad bugs i.e. UB or important functionality being broken.
|
||||
* Would have higher user impact and are almost ready to be merged/fixed.
|
||||
|
||||
There are also two priority labels: [`P-Critical`](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AP-Critical) and [`P-High`](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AP-High) that can be used to find issues and PRs that need to be resolved urgently.
|
||||
|
||||
### Closing PRs and Issues
|
||||
|
||||
From time to time, PRs are unsuitable to be merged in a way that cannot be readily fixed.
|
||||
Rather than leaving these PRs open in limbo indefinitely, they should simply be closed.
|
||||
|
||||
This might happen if:
|
||||
|
||||
1. The PR is spam or malicious.
|
||||
2. The work has already been done elsewhere or is otherwise fully obsolete.
|
||||
3. The PR was successfully adopted.
|
||||
4. The work is particularly low quality, and the author is resistant to coaching.
|
||||
5. The work adds features or abstraction of limited value, especially in a way that could easily be recreated outside of the engine.
|
||||
6. The work has been sitting in review for so long and accumulated so many conflicts that it would be simpler to redo it from scratch.
|
||||
7. The PR is pointlessly large, and should be broken into multiple smaller PRs for easier review.
|
||||
|
||||
PRs that are `S-Adopt-Me` should be left open, but only if they're genuinely more useful to rebase rather than simply use as a reference.
|
||||
|
||||
There are several paths for PRs to be closed:
|
||||
|
||||
1. Obviously, authors may close their own PRs for any reason at any time.
|
||||
2. If a PR is clearly spam or malicious, anyone with triage rights is encouraged to close out the PR and report it to Github.
|
||||
3. If the work has already been done elsewhere, adopted or otherwise obsoleted, anyone with triage rights is encouraged to close out the PR with an explanatory comment.
|
||||
4. Anyone may nominate a PR for closure, by bringing it to the attention of the author and / or one of the SMEs / maintainers. Let them press the button, but this is generally well-received and helpful.
|
||||
5. SMEs or maintainers may and are encouraged to unilaterally close PRs that fall into one or more of the remaining categories.
|
||||
6. In the case of PRs where some members of the community (other than the author) are in favor and some are opposed, any two relevant SMEs or maintainers may act in concert to close the PR.
|
||||
|
||||
When closing a PR, check if it has an issue linked.
|
||||
If it does not, you should strongly consider creating an issue and linking the now-closed PR to help make sure the previous work can be discovered and credited.
|
||||
|
||||
## Making changes to Bevy
|
||||
|
||||
Most changes don't require much "process". If your change is relatively straightforward, just do the following:
|
||||
|
||||
1. A community member (that's you!) creates one of the following:
|
||||
* [GitHub Discussions]: An informal discussion with the community. This is the place to start if you want to propose a feature or specific implementation.
|
||||
* [Issue](https://github.com/bevyengine/bevy/issues): A formal way for us to track a bug or feature. Please look for duplicates before opening a new issue and consider starting with a Discussion.
|
||||
* [Pull Request](https://github.com/bevyengine/bevy/pulls) (or PR for short): A request to merge code changes. This starts our "review process". You are welcome to start with a pull request, but consider starting with an Issue or Discussion for larger changes (or if you aren't certain about a design). We don't want anyone to waste their time on code that didn't have a chance to be merged! But conversely, sometimes PRs are the most efficient way to propose a change. Just use your own judgement here.
|
||||
2. Other community members review and comment in an ad-hoc fashion. Active subject matter experts may be pulled into a thread using `@mentions`. If your PR has been quiet for a while and is ready for review, feel free to leave a message to "bump" the thread, or bring it up on [Discord](https://discord.gg/bevy) in an appropriate engine development channel.
|
||||
3. Once they're content with the pull request (design, code quality, documentation, tests), individual reviewers leave "Approved" reviews.
|
||||
4. After consensus has been reached (typically two approvals from the community or one for extremely simple changes) and CI passes, the [S-Ready-For-Final-Review](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AS-Ready-For-Final-Review) label is added.
|
||||
5. When they find time, someone with merge rights performs a final code review and queue the PR for merging.
|
||||
|
||||
### Complex changes
|
||||
|
||||
Individual contributors often lead major new features and reworks. However these changes require more design work and scrutiny. Complex changes like this tend to go through the following lifecycle:
|
||||
|
||||
1. A need or opportunity is identified and an issue is made, laying out the general problem.
|
||||
2. As needed, this is discussed further on that issue thread, in cross-linked [GitHub Discussion] threads, or on [Discord] in the Engine Development channels.
|
||||
3. Either a Draft Pull Request or an RFC is made. As discussed in the [RFC repo](https://github.com/bevyengine/rfcs), complex features need RFCs, but these can be submitted before or after prototyping work has been started.
|
||||
4. If feasible, parts that work on their own (even if they're only useful once the full complex change is merged) get split out into individual PRs to make them easier to review.
|
||||
5. The community as a whole helps improve the Draft PR and/or RFC, leaving comments, making suggestions, and submitting pull requests to the original branch.
|
||||
6. Once the RFC is merged and/or the Draft Pull Request is transitioned out of draft mode, the [normal change process outlined in the previous section](#making-changes-to-bevy) can begin.
|
||||
|
||||
## How you can help
|
||||
|
||||
If you've made it to this page, you're probably already convinced that Bevy is a project you'd like to see thrive.
|
||||
But how can *you* help?
|
||||
|
||||
No matter your experience level with Bevy or Rust or your level of commitment, there are ways to meaningfully contribute.
|
||||
Take a look at the sections that follow to pick a route (or five) that appeal to you.
|
||||
|
||||
If you ever find yourself at a loss for what to do, or in need of mentorship or advice on how to contribute to Bevy, feel free to ask in [Discord] and one of our more experienced community members will be happy to help.
|
||||
|
||||
### Join a working group
|
||||
|
||||
Active initiatives in Bevy are organized into temporary working groups: choosing one of those and asking how to help can be a fantastic way to get up to speed and be immediately useful.
|
||||
|
||||
Working groups are public, open-membership groups that work together to tackle a broad-but-scoped initiative.
|
||||
The work that they do is coordinated in a forum-channel on [Discord](https://discord.gg/bevy), although they also create issues and may use project boards for tangible work that needs to be done.
|
||||
|
||||
There are no special requirements to be a member, and no formal membership list or leadership.
|
||||
Anyone can help, and you should expect to compromise and work together with others to bring a shared vision to life.
|
||||
Working groups are *spaces*, not clubs.
|
||||
|
||||
### Start a working group
|
||||
|
||||
When tackling a complex initiative, friends and allies can make things go much more smoothly.
|
||||
|
||||
To start a working group:
|
||||
|
||||
1. Decide what the working group is going to focus on. This should be tightly focused and achievable!
|
||||
2. Gather at least 3 people including yourself who are willing to be in the working group.
|
||||
3. Ping the `@Maintainer` role on Discord in [#engine-dev](https://discord.com/channels/691052431525675048/692572690833473578) announcing your mutual intent and a one or two sentence description of your plans.
|
||||
|
||||
The maintainers will briefly evaluate the proposal in consultation with the relevant SMEs and give you a thumbs up or down on whether this is something Bevy can and wants to explore right now.
|
||||
You don't need a concrete plan at this stage, just a sensible argument for both "why is this something that could be useful to Bevy" and "why there aren't any serious barriers in implementing this in the near future".
|
||||
If they're in favor, a maintainer will create a forum channel for you and you're off to the races.
|
||||
|
||||
Your initial task is writing up a design doc: laying out the scope of work and general implementation strategy.
|
||||
Here's a [solid example of a design doc](https://github.com/bevyengine/bevy/issues/12365), although feel free to use whatever format works best for your team.
|
||||
|
||||
Once that's ready, get a sign-off on the broad vision and goals from the appropriate SMEs and maintainers.
|
||||
This is the primary review step: maintainers and SMEs should be broadly patient and supportive even if they're skeptical until a proper design doc is in hand to evaluate.
|
||||
|
||||
With a sign-off in hand, post the design doc to [Github Discussions](https://github.com/bevyengine/bevy/discussions) with the [`C-Design-Doc` label](https://github.com/bevyengine/bevy/discussions?discussions_q=is%3Aopen+label%3A%22C-Design+Doc%22) for archival purposes and begin work on implementation.
|
||||
Post PRs that you need review on in your group's forum thread, ask for advice, and share the load.
|
||||
Controversial PRs are still `S-Controversial`, but with a sign-off-in-priniciple, things should go more smoothly.
|
||||
|
||||
If work peters out and the initiative dies, maintainers can wind down working groups (in consultation with SMEs and the working group itself).
|
||||
This is normal and expected: projects fail for all sorts of reasons!
|
||||
However, it's important to both keep the number of working groups relatively small and ensure they're active:
|
||||
they serve a vital role in onboarding new contributors.
|
||||
|
||||
Once your implementation work laid out in your initial design doc is complete, it's time to wind down the working group.
|
||||
Feel free to make another one though to tackle the next step in your grand vision!
|
||||
|
||||
### Battle-testing Bevy
|
||||
|
||||
Ultimately, Bevy is a tool that's designed to help people make cool games.
|
||||
By using Bevy, you can help us catch bugs, prioritize new features, polish off the rough edges, and promote the project.
|
||||
|
||||
If you need help, don't hesitate to ask for help on [GitHub Discussions], [Discord], or [reddit](https://www.reddit.com/r/bevy). Generally you should prefer asking questions as [GitHub Discussions] as they are more searchable.
|
||||
|
||||
When you think you've found a bug, missing documentation, or a feature that would help you make better games, please [file an issue](https://github.com/bevyengine/bevy/issues/new/choose) on the main `bevy` repo.
|
||||
|
||||
Do your best to search for duplicate issues, but if you're unsure, open a new issue and link to other related issues on the thread you make.
|
||||
|
||||
Once you've made something that you're proud of, feel free to drop a link, video, or screenshot in `#showcase` on [Discord]!
|
||||
If you release a game on [itch.io](https://itch.io/games/tag-bevy) we'd be thrilled if you tagged it with `bevy`.
|
||||
|
||||
### Teaching others
|
||||
|
||||
Bevy is still very young, and light on documentation, tutorials, and accumulated expertise.
|
||||
By helping others with their issues, and teaching them about Bevy, you will naturally learn the engine and codebase in greater depth while also making our community better!
|
||||
|
||||
Some of the best ways to do this are:
|
||||
|
||||
* Answering questions on [GitHub Discussions], [Discord], and [reddit](https://www.reddit.com/r/bevy).
|
||||
* Writing tutorials, guides, and other informal documentation and sharing them on [Bevy Assets](https://github.com/bevyengine/bevy-assets).
|
||||
* Streaming, writing blog posts about creating your game, and creating videos. Share these in the `#devlogs` channel on [Discord]!
|
||||
|
||||
### Writing plugins
|
||||
|
||||
You can improve Bevy's ecosystem by building your own Bevy Plugins and crates.
|
||||
|
||||
Non-trivial, reusable functionality that works well with itself is a good candidate for a plugin.
|
||||
If it's closer to a snippet or design pattern, you may want to share it with the community on [Discord], Reddit, or [GitHub Discussions] instead.
|
||||
|
||||
Check out our [plugin guidelines](https://bevyengine.org/learn/book/plugin-development/) for helpful tips and patterns.
|
||||
|
||||
### Fixing bugs
|
||||
|
||||
Bugs in Bevy (or the associated website / book) are filed on the issue tracker using the [`C-Bug`](https://github.com/bevyengine/bevy/issues?q=is%3Aissue+is%3Aopen+label%3AC-Bug) label.
|
||||
|
||||
If you're looking for an easy place to start, take a look at the [`D-Good-First-Issue`](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AD-Good-First-Issue) label, and feel free to ask questions on that issue's thread in question or on [Discord].
|
||||
You don't need anyone's permission to try fixing a bug or adding a simple feature, but stating that you'd like to tackle an issue can be helpful to avoid duplicated work.
|
||||
|
||||
When you make a pull request that fixes an issue, include a line that says `Fixes #X` (or "Closes"), where `X` is the issue number.
|
||||
This will cause the issue in question to be closed when your PR is merged.
|
||||
|
||||
General improvements to code quality are also welcome!
|
||||
Bevy can always be safer, better tested, and more idiomatic.
|
||||
|
||||
### Writing docs
|
||||
|
||||
Like every other large, rapidly developing open source library you've ever used, Bevy's documentation can always use improvement.
|
||||
This is incredibly valuable, easily distributed work, but requires a bit of guidance:
|
||||
|
||||
* Inaccurate documentation is worse than no documentation: prioritize fixing broken docs.
|
||||
* Bevy is remarkably unstable: before tackling a new major documentation project, check in with the community on Discord or GitHub (making an issue about specific missing docs is a great way to plan) about the stability of that feature and upcoming plans to save yourself heartache.
|
||||
* Code documentation (doc examples and in the examples folder) is easier to maintain because the compiler will tell us when it breaks.
|
||||
* Inline documentation should be technical and to the point. Link relevant examples or other explanations if broader context is useful.
|
||||
* The Bevy book is hosted on the `bevy-website` repo and targeted towards beginners who are just getting to know Bevy (and perhaps Rust!).
|
||||
* Accepted RFCs are not documentation: they serve only as a record of accepted decisions.
|
||||
|
||||
[docs.rs](https://docs.rs/bevy) is built from out of the last release's documentation, which is written right in-line directly above the code it documents.
|
||||
To view the current docs on `main` before you contribute, clone the `bevy` repo, and run `cargo doc --open` or go to [dev-docs.bevyengine.org](https://dev-docs.bevyengine.org/),
|
||||
which has the latest API reference built from the repo on every commit made to the `main` branch.
|
||||
|
||||
### Writing examples
|
||||
|
||||
Most [examples in Bevy](https://github.com/bevyengine/bevy/tree/main/examples) aim to clearly demonstrate a single feature, group of closely related small features, or show how to accomplish a particular task (such as asset loading, creating a custom shader or testing your app).
|
||||
In rare cases, creating new "game" examples is justified in order to demonstrate new features that open a complex class of functionality in a way that's hard to demonstrate in isolation or requires additional integration testing.
|
||||
|
||||
Examples in Bevy should be:
|
||||
|
||||
1. **Working:** They must compile and run, and any introduced errors in them should be obvious (through tests, simple results or clearly displayed behavior).
|
||||
2. **Clear:** They must use descriptive variable names, be formatted, and be appropriately commented. Try your best to showcase best practices when it doesn't obscure the point of the example.
|
||||
3. **Relevant:** They should explain, through comments or variable names, what they do and how this can be useful to a game developer.
|
||||
4. **Minimal:** They should be no larger or complex than is needed to meet the goals of the example.
|
||||
|
||||
When you add a new example, be sure to update `examples/README.md` with the new example and add it to the root `Cargo.toml` file.
|
||||
Run `cargo run -p build-templated-pages -- build-example-page` to do this automatically.
|
||||
Use a generous sprinkling of keywords in your description: these are commonly used to search for a specific example.
|
||||
See the [example style guide](.github/contributing/example_style_guide.md) to help make sure the style of your example matches what we're already using.
|
||||
|
||||
More complex demonstrations of functionality are also welcome, but these should be submitted to [bevy-assets](https://github.com/bevyengine/bevy-assets).
|
||||
|
||||
### Reviewing others' work
|
||||
|
||||
With the sheer volume of activity in Bevy's community, reviewing others work with the aim of improving it is one of the most valuable things you can do.
|
||||
You don't need to be an Elder Rustacean to be useful here: anyone can catch missing tests, unclear docs, logic errors, and so on.
|
||||
If you have specific skills (e.g. advanced familiarity with `unsafe` code, rendering knowledge or web development experience) or personal experience with a problem, try to prioritize those areas to ensure we can get appropriate expertise where we need it.
|
||||
|
||||
When you find (or make) a PR that you don't feel comfortable reviewing, but you *can* think of someone who does, consider using Github's "Request review" functionality (in the top-right of the PR screen) to bring the work to their attention.
|
||||
If they're not a Bevy Org member, you'll need to ping them in the thread directly: that's fine too!
|
||||
Almost everyone working on Bevy is a volunteer: this should be treated as a gentle nudge, rather than an assignment of work.
|
||||
Consider checking the Git history for appropriate reviewers, or ask on Discord for suggestions.
|
||||
|
||||
Focus on giving constructive, actionable feedback that results in real improvements to code quality or end-user experience.
|
||||
If you don't understand why an approach was taken, please ask!
|
||||
|
||||
Provide actual code suggestions when that is helpful. Small changes work well as comments or in-line suggestions on specific lines of codes.
|
||||
Larger changes deserve a comment in the main thread, or a pull request to the original author's branch (but please mention that you've made one).
|
||||
When in doubt about a matter of architectural philosophy, refer back to [*What we're trying to build*](#what-were-trying-to-build) for guidance.
|
||||
|
||||
Once you're happy with the work and feel you're reasonably qualified to assess quality in this particular area, leave your `Approved` review on the PR.
|
||||
If you're new to GitHub, check out the [Pull Request Review documentation](https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews).
|
||||
**Anyone** can and should leave reviews ... no special permissions are required!
|
||||
|
||||
It's okay to leave an approval even if you aren't 100% confident on all areas of the PR: just be sure to note your limitations.
|
||||
When maintainers are evaluating the PR to be merged, they'll make sure that there's good coverage on all of the critical areas.
|
||||
If you can only check that the math is correct, and another reviewer can check everything *but* the math, we're in good shape!
|
||||
|
||||
Similarly, if there are areas that would be *good* to fix but aren't severe, please consider leaving an approval.
|
||||
The author can address them immediately, or spin it out into follow-up issues or PRs.
|
||||
Large PRs are much more draining for both reviewers and authors, so try to push for a smaller scope with clearly tracked follow-ups.
|
||||
|
||||
There are three main places you can check for things to review:
|
||||
|
||||
1. Pull requests which are ready and in need of more reviews on [bevy](https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+-label%3AS-Ready-For-Final-Review+-draft%3A%3Atrue+-label%3AS-Needs-RFC+-reviewed-by%3A%40me+-author%3A%40me).
|
||||
2. Pull requests on [bevy](https://github.com/bevyengine/bevy/pulls) and the [bevy-website](https://github.com/bevyengine/bevy-website/pulls) repos.
|
||||
3. [RFCs](https://github.com/bevyengine/rfcs), which need extensive thoughtful community input on their design.
|
||||
|
||||
Not even our Project Leads and Maintainers are exempt from reviews and RFCs!
|
||||
By giving feedback on this work (and related supporting work), you can help us make sure our releases are both high-quality and timely.
|
||||
|
||||
Finally, if nothing brings you more satisfaction than seeing every last issue labeled and all resolved issues closed, feel free to message the Project Lead (currently @cart) for a Bevy org role to help us keep things tidy.
|
||||
As discussed in our [*Bevy Organization doc*](/docs/the_bevy_organization.md), this role only requires good faith and a basic understanding of our development process.
|
||||
|
||||
### How to adopt pull requests
|
||||
|
||||
Occasionally authors of pull requests get busy or become unresponsive, or project members fail to reply in a timely manner.
|
||||
This is a natural part of any open source project.
|
||||
To avoid blocking these efforts, these pull requests may be *adopted*, where another contributor creates a new pull request with the same content.
|
||||
If there is an old pull request that is without updates, comment to the organization whether it is appropriate to add the
|
||||
*[S-Adopt-Me](https://github.com/bevyengine/bevy/labels/S-Adopt-Me)* label, to indicate that it can be *adopted*.
|
||||
If you plan on adopting a PR yourself, you can also leave a comment on the PR asking the author if they plan on returning.
|
||||
If the author gives permission or simply doesn't respond after a few days, then it can be adopted.
|
||||
This may sometimes even skip the labeling process since at that point the PR has been adopted by you.
|
||||
|
||||
With this label added, it's best practice to fork the original author's branch.
|
||||
This ensures that they still get credit for working on it and that the commit history is retained.
|
||||
When the new pull request is ready, it should reference the original PR in the description.
|
||||
Then notify org members to close the original.
|
||||
|
||||
* For example, you can reference the original PR by adding the following to your PR description:
|
||||
|
||||
`Adopted #number-original-pull-request`
|
||||
|
||||
### Contributing code
|
||||
|
||||
Bevy is actively open to code contributions from community members.
|
||||
If you're new to Bevy, here's the workflow we use:
|
||||
|
||||
1. Fork the `bevyengine/bevy` repository on GitHub. You'll need to create a GitHub account if you don't have one already.
|
||||
2. Make your changes in a local clone of your fork, typically in its own new branch.
|
||||
1. Try to split your work into separate commits, each with a distinct purpose. Be particularly mindful of this when responding to reviews so it's easy to see what's changed.
|
||||
2. Tip: [You can set up a global `.gitignore` file](https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer) to exclude your operating system/text editor's special/temporary files. (e.g. `.DS_Store`, `thumbs.db`, `*~`, `*.swp` or `*.swo`) This allows us to keep the `.gitignore` file in the repo uncluttered.
|
||||
3. To test CI validations locally, run the `cargo run -p ci` command. This will run most checks that happen in CI, but can take some time. You can also run sub-commands to iterate faster depending on what you're contributing:
|
||||
* `cargo run -p ci -- lints` - to run formatting and clippy.
|
||||
* `cargo run -p ci -- test` - to run tests.
|
||||
* `cargo run -p ci -- doc` - to run doc tests and doc checks.
|
||||
* `cargo run -p ci -- compile` - to check that everything that must compile still does (examples and benches), and that some that shouldn't still don't ([`crates/bevy_ecs_compile_fail_tests`](./crates/bevy_ecs_compile_fail_tests)).
|
||||
* to get more information on commands available and what is run, check the [tools/ci crate](./tools/ci).
|
||||
4. When working with Markdown (`.md`) files, Bevy's CI will check markdown files (like this one) using [markdownlint](https://github.com/DavidAnson/markdownlint).
|
||||
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. When working with Toml (`.toml`) files, Bevy's CI will check toml files using [taplo](https://taplo.tamasfe.dev/): `taplo fmt --check --diff`
|
||||
1. If you use VSCode, install [Even better toml](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) and format your files.
|
||||
2. If you want to use the cli tool, install [taplo-cli](https://taplo.tamasfe.dev/cli/installation/cargo.html) and run `taplo fmt --check --diff` to check for the formatting. Fix any issues by running `taplo fmt` in the root directory of the Bevy project.
|
||||
6. Check for typos. Bevy's CI will check for them using [typos](https://github.com/crate-ci/typos).
|
||||
1. If you use VSCode, install [Typos Spell Checker](https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode).
|
||||
2. You can also use the cli tool. Install [typos-cli](https://github.com/crate-ci/typos?tab=readme-ov-file#install) and run `typos` to check for typos, and fix them by running `typos -w`.
|
||||
7. Push your changes to your fork on Github and open a Pull Request.
|
||||
8. 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 a Maintainer or Project Lead's final judgement.
|
||||
9. When your PR is ready to merge, a Maintainer or Project Lead will review it and suggest final changes. If those changes are minimal they may even apply them directly to speed up merging.
|
||||
|
||||
If you end up adding a new official Bevy crate to the `bevy` repo:
|
||||
|
||||
1. Add the new crate to the [./tools/publish.sh](./tools/publish.sh) file.
|
||||
2. Check if a new cargo feature was added, update [cargo_features.md](https://github.com/bevyengine/bevy/blob/main/docs/cargo_features.md) as needed.
|
||||
|
||||
When contributing, please:
|
||||
|
||||
* Try to loosely follow the workflow in [*Making changes to Bevy*](#making-changes-to-bevy).
|
||||
* Consult the [style guide](.github/contributing/engine_style_guide.md) to help keep our code base tidy.
|
||||
* Explain what you're doing and why.
|
||||
* Document new code with doc comments.
|
||||
* Include clear, simple tests.
|
||||
* Add or improve the examples when adding new user-facing functionality.
|
||||
* Break work into digestible chunks.
|
||||
* Ask for any help that you need!
|
||||
|
||||
Your first PR will be merged in no time!
|
||||
|
||||
No matter how you're helping: thanks for contributing to Bevy!
|
||||
|
||||
[GitHub Discussions]: https://github.com/bevyengine/bevy/discussions "GitHub Discussions"
|
||||
[Discord]: https://discord.gg/bevy "Discord"
|
||||
|
@ -20,8 +20,8 @@
|
||||
* 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 [Kenney's Simple Space Kit](https://www.kenney.nl/assets/simple-space) (CC0 1.0 Universal)
|
||||
* UI borders from [Kenney's Fantasy UI Borders Kit](https://kenney.nl/assets/fantasy-ui-borders) (CC0 1.0 Universal)
|
||||
* Space ships from [Kenny's Simple Space Kit](https://www.kenney.nl/assets/simple-space) (CC0 1.0 Universal)
|
||||
* UI borders from [Kenny's Fantasy UI Borders Kit](https://kenney.nl/assets/fantasy-ui-borders) (CC0 1.0 Universal)
|
||||
* glTF animated fox from [glTF Sample Models][fox]
|
||||
* Low poly fox [by PixelMannen] (CC0 1.0 Universal)
|
||||
* Rigging and animation [by @tomkranis on Sketchfab] ([CC-BY 4.0])
|
||||
@ -32,7 +32,7 @@
|
||||
* Epic orchestra music sample, modified to loop, from [Migfus20](https://freesound.org/people/Migfus20/sounds/560449/) ([CC BY 4.0 DEED](https://creativecommons.org/licenses/by/4.0/))
|
||||
|
||||
[MorphStressTest]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/MorphStressTest
|
||||
[fox]: https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/Fox
|
||||
[fox]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox
|
||||
[by PixelMannen]: https://opengameart.org/content/fox-and-shiba
|
||||
[by @tomkranis on Sketchfab]: https://sketchfab.com/models/371dea88d7e04a76af5763f2a36866bc
|
||||
[CC-BY 4.0]: https://creativecommons.org/licenses/by/4.0/
|
||||
|
1314
Cargo.toml
@ -50,7 +50,7 @@ Before contributing or participating in discussions with the community, you shou
|
||||
|
||||
### Contributing
|
||||
|
||||
If you'd like to help build Bevy, check out the **[Contributor's Guide](https://bevyengine.org/learn/contribute/introduction)**.
|
||||
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](https://github.com/bevyengine/bevy/issues) or
|
||||
[PR](https://github.com/bevyengine/bevy/pulls) and tackle it yourself!
|
||||
|
||||
|
@ -2,28 +2,23 @@
|
||||
graph: (
|
||||
nodes: [
|
||||
(
|
||||
node_type: Blend,
|
||||
mask: 0,
|
||||
clip: None,
|
||||
weight: 1.0,
|
||||
),
|
||||
(
|
||||
node_type: Blend,
|
||||
mask: 0,
|
||||
clip: None,
|
||||
weight: 0.5,
|
||||
),
|
||||
(
|
||||
clip: Some(AssetPath("models/animated/Fox.glb#Animation0")),
|
||||
weight: 1.0,
|
||||
),
|
||||
(
|
||||
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation0")),
|
||||
mask: 0,
|
||||
clip: Some(AssetPath("models/animated/Fox.glb#Animation1")),
|
||||
weight: 1.0,
|
||||
),
|
||||
(
|
||||
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation1")),
|
||||
mask: 0,
|
||||
weight: 1.0,
|
||||
),
|
||||
(
|
||||
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation2")),
|
||||
mask: 0,
|
||||
clip: Some(AssetPath("models/animated/Fox.glb#Animation2")),
|
||||
weight: 1.0,
|
||||
),
|
||||
],
|
||||
@ -37,5 +32,4 @@
|
||||
],
|
||||
),
|
||||
root: 0,
|
||||
mask_groups: {},
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
|
||||
|
||||
Crosshair Pack
|
||||
|
||||
by Kenney Vleugels (Kenney.nl)
|
||||
|
||||
------------------------------
|
||||
|
||||
License (Creative Commons Zero, CC0)
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
You may use these assets in personal and commercial projects.
|
||||
Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
|
||||
|
||||
------------------------------
|
||||
|
||||
Donate: http://support.kenney.nl
|
||||
|
||||
Follow on Twitter for updates: @KenneyNL (www.twitter.com/kenneynl)
|
Before Width: | Height: | Size: 34 KiB |
2
assets/external/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
*
|
||||
!.gitignore
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 540 KiB |
@ -1,26 +1,37 @@
|
||||
(
|
||||
resources: {
|
||||
"scene::ResourceA": (
|
||||
score: 1,
|
||||
score: 2,
|
||||
),
|
||||
},
|
||||
entities: {
|
||||
4294967296: (
|
||||
components: {
|
||||
"bevy_ecs::name::Name": "joe",
|
||||
"bevy_transform::components::global_transform::GlobalTransform": ((1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)),
|
||||
"bevy_transform::components::transform::Transform": (
|
||||
translation: (0.0, 0.0, 0.0),
|
||||
rotation: (0.0, 0.0, 0.0, 1.0),
|
||||
scale: (1.0, 1.0, 1.0),
|
||||
translation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0
|
||||
),
|
||||
rotation: (
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
w: 1.0,
|
||||
),
|
||||
scale: (
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0
|
||||
),
|
||||
),
|
||||
"scene::ComponentB": (
|
||||
value: "hello",
|
||||
),
|
||||
"scene::ComponentA": (
|
||||
x: 1.0,
|
||||
y: 2.0,
|
||||
),
|
||||
"scene::ComponentB": (
|
||||
value: "hello",
|
||||
),
|
||||
},
|
||||
),
|
||||
4294967297: (
|
||||
@ -31,5 +42,5 @@
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
|
@ -1,43 +0,0 @@
|
||||
#import bevy_pbr::{
|
||||
mesh_functions,
|
||||
view_transformations::position_world_to_clip
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var texture: texture_2d<f32>;
|
||||
@group(2) @binding(1) var texture_sampler: sampler;
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
@location(0) position: vec3<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) world_position: vec4<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
// Lookup the tag for the given mesh
|
||||
let tag = mesh_functions::get_tag(vertex.instance_index);
|
||||
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
|
||||
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
|
||||
out.clip_position = position_world_to_clip(out.world_position.xyz);
|
||||
|
||||
let tex_dim = textureDimensions(texture);
|
||||
// Find the texel coordinate as derived from the tag
|
||||
let texel_coord = vec2<u32>(tag % tex_dim.x, tag / tex_dim.x);
|
||||
|
||||
out.color = textureLoad(texture, texel_coord, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
mesh: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return mesh.color;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
#import bevy_pbr::mesh_bindings::mesh
|
||||
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
|
||||
|
||||
struct Color {
|
||||
base_color: vec4<f32>,
|
||||
}
|
||||
|
||||
// This structure is a mapping from bindless index to the index in the
|
||||
// appropriate slab
|
||||
struct MaterialBindings {
|
||||
material: u32, // 0
|
||||
color_texture: u32, // 1
|
||||
color_texture_sampler: u32, // 2
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(0) var<storage> materials: array<MaterialBindings>;
|
||||
@group(2) @binding(10) var<storage> material_color: binding_array<Color>;
|
||||
#else // BINDLESS
|
||||
@group(2) @binding(0) var<uniform> material_color: Color;
|
||||
@group(2) @binding(1) var material_color_texture: texture_2d<f32>;
|
||||
@group(2) @binding(2) var material_color_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
#ifdef BINDLESS
|
||||
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
||||
let base_color = material_color[materials[slot].material].base_color;
|
||||
#else // BINDLESS
|
||||
let base_color = material_color.base_color;
|
||||
#endif // BINDLESS
|
||||
|
||||
return base_color * textureSampleLevel(
|
||||
#ifdef BINDLESS
|
||||
bindless_textures_2d[materials[slot].color_texture],
|
||||
bindless_samplers_filtering[materials[slot].color_texture_sampler],
|
||||
#else // BINDLESS
|
||||
material_color_texture,
|
||||
material_color_sampler,
|
||||
#endif // BINDLESS
|
||||
in.uv,
|
||||
0.0
|
||||
);
|
||||
}
|
20
assets/shaders/circle_shader.wgsl
Normal file
@ -0,0 +1,20 @@
|
||||
// This shader draws a circle with a given input color
|
||||
#import bevy_ui::ui_vertex_output::UiVertexOutput
|
||||
|
||||
struct CustomUiMaterial {
|
||||
@location(0) color: vec4<f32>
|
||||
}
|
||||
|
||||
@group(1) @binding(0)
|
||||
var<uniform> input: CustomUiMaterial;
|
||||
|
||||
@fragment
|
||||
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
|
||||
// the UVs are now adjusted around the middle of the rect.
|
||||
let uv = in.uv * 2.0 - 1.0;
|
||||
|
||||
// circle alpha, the higher the power the harsher the falloff.
|
||||
let alpha = 1.0 - pow(sqrt(dot(uv, uv)), 100.0);
|
||||
|
||||
return vec4<f32>(input.color.rgb, alpha);
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
// This shader, a part of the `clustered_decals` example, shows how to use the
|
||||
// decal `tag` field to apply arbitrary decal effects.
|
||||
|
||||
#import bevy_pbr::{
|
||||
clustered_forward,
|
||||
decal::clustered,
|
||||
forward_io::{VertexOutput, FragmentOutput},
|
||||
mesh_view_bindings,
|
||||
pbr_fragment::pbr_input_from_standard_material,
|
||||
pbr_functions::{alpha_discard, apply_pbr_lighting, main_pass_post_lighting_processing},
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
in: VertexOutput,
|
||||
@builtin(front_facing) is_front: bool,
|
||||
) -> FragmentOutput {
|
||||
// Generate a `PbrInput` struct from the `StandardMaterial` bindings.
|
||||
var pbr_input = pbr_input_from_standard_material(in, is_front);
|
||||
|
||||
// Alpha discard.
|
||||
pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
|
||||
|
||||
// Apply the normal decals.
|
||||
pbr_input.material.base_color = clustered::apply_decal_base_color(
|
||||
in.world_position.xyz,
|
||||
in.position.xy,
|
||||
pbr_input.material.base_color
|
||||
);
|
||||
|
||||
// Here we tint the color based on the tag of the decal.
|
||||
// We could optionally do other things, such as adjust the normal based on a normal map.
|
||||
let view_z = clustered::get_view_z(in.world_position.xyz);
|
||||
let is_orthographic = clustered::view_is_orthographic();
|
||||
let cluster_index =
|
||||
clustered_forward::fragment_cluster_index(in.position.xy, view_z, is_orthographic);
|
||||
var clusterable_object_index_ranges =
|
||||
clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
|
||||
var decal_iterator = clustered::clustered_decal_iterator_new(
|
||||
in.world_position.xyz,
|
||||
&clusterable_object_index_ranges
|
||||
);
|
||||
while (clustered::clustered_decal_iterator_next(&decal_iterator)) {
|
||||
var decal_base_color = textureSampleLevel(
|
||||
mesh_view_bindings::clustered_decal_textures[decal_iterator.texture_index],
|
||||
mesh_view_bindings::clustered_decal_sampler,
|
||||
decal_iterator.uv,
|
||||
0.0
|
||||
);
|
||||
|
||||
switch (decal_iterator.tag) {
|
||||
case 1u: {
|
||||
// Tint with red.
|
||||
decal_base_color = vec4(
|
||||
mix(pbr_input.material.base_color.rgb, vec3(1.0, 0.0, 0.0), 0.5),
|
||||
decal_base_color.a,
|
||||
);
|
||||
}
|
||||
case 2u: {
|
||||
// Tint with blue.
|
||||
decal_base_color = vec4(
|
||||
mix(pbr_input.material.base_color.rgb, vec3(0.0, 0.0, 1.0), 0.5),
|
||||
decal_base_color.a,
|
||||
);
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
|
||||
pbr_input.material.base_color = vec4(
|
||||
mix(pbr_input.material.base_color.rgb, decal_base_color.rgb, decal_base_color.a),
|
||||
pbr_input.material.base_color.a + decal_base_color.a
|
||||
);
|
||||
}
|
||||
|
||||
// Apply lighting.
|
||||
var out: FragmentOutput;
|
||||
out.color = apply_pbr_lighting(pbr_input);
|
||||
|
||||
// Apply in-shader post processing (fog, alpha-premultiply, and also
|
||||
// tonemapping, debanding if the camera is non-HDR). Note this does not
|
||||
// include fullscreen postprocessing effects like bloom.
|
||||
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
@ -7,16 +7,14 @@ layout(location = 2) in vec2 Vertex_Uv;
|
||||
layout(location = 0) out vec2 v_Uv;
|
||||
|
||||
layout(set = 0, binding = 0) uniform CameraViewProj {
|
||||
mat4 clip_from_world;
|
||||
// Other attributes exist that can be described here.
|
||||
// See full definition in: crates/bevy_render/src/view/view.wgsl
|
||||
// Attributes added here must be in the same order as they are defined
|
||||
// in view.wgsl, and they must be contiguous starting from the top to
|
||||
// ensure they have the same layout.
|
||||
//
|
||||
// Needing to maintain this mapping yourself is one of the harder parts of using
|
||||
// GLSL with Bevy. WGSL provides a much better user experience!
|
||||
} camera_view;
|
||||
mat4 ViewProj;
|
||||
mat4 View;
|
||||
mat4 InverseView;
|
||||
mat4 Projection;
|
||||
vec3 WorldPosition;
|
||||
float width;
|
||||
float height;
|
||||
};
|
||||
|
||||
struct Mesh {
|
||||
mat3x4 Model;
|
||||
@ -43,7 +41,7 @@ mat4 affine_to_square(mat3x4 affine) {
|
||||
|
||||
void main() {
|
||||
v_Uv = Vertex_Uv;
|
||||
gl_Position = camera_view.clip_from_world
|
||||
gl_Position = ViewProj
|
||||
* affine_to_square(Meshes[gl_InstanceIndex].Model)
|
||||
* vec4(Vertex_Position, 1.0);
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
import super::util::make_polka_dots;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(2) uv: vec2<f32>,
|
||||
}
|
||||
|
||||
struct CustomMaterial {
|
||||
// Needed for 16-bit alignment on WebGL2
|
||||
time: vec4<f32>,
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: CustomMaterial;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
mesh: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return make_polka_dots(mesh.uv, material.time.x);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
//! A shader showing how to use the vertex position data to output the
|
||||
//! stencil in the right position
|
||||
|
||||
// First we import everything we need from bevy_pbr
|
||||
// A 2d shader would be vevry similar but import from bevy_sprite instead
|
||||
#import bevy_pbr::{
|
||||
mesh_functions,
|
||||
view_transformations::position_world_to_clip
|
||||
}
|
||||
|
||||
struct Vertex {
|
||||
// This is needed if you are using batching and/or gpu preprocessing
|
||||
// It's a built in so you don't need to define it in the vertex layout
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
// Like we defined for the vertex layout
|
||||
// position is at location 0
|
||||
@location(0) position: vec3<f32>,
|
||||
};
|
||||
|
||||
// This is the output of the vertex shader and we also use it as the input for the fragment shader
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) world_position: vec4<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
// This is how bevy computes the world position
|
||||
// The vertex.instance_index is very important. Especially if you are using batching and gpu preprocessing
|
||||
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
|
||||
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
|
||||
out.clip_position = position_world_to_clip(out.world_position.xyz);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Output a red color to represent the stencil of the mesh
|
||||
return vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
// Draws a progress bar with properties defined in CustomUiMaterial
|
||||
#import bevy_ui::ui_vertex_output::UiVertexOutput
|
||||
|
||||
@group(1) @binding(0) var<uniform> color: vec4<f32>;
|
||||
@group(1) @binding(1) var<uniform> slider: vec4<f32>;
|
||||
@group(1) @binding(2) var material_color_texture: texture_2d<f32>;
|
||||
@group(1) @binding(3) var material_color_sampler: sampler;
|
||||
@group(1) @binding(4) var<uniform> border_color: vec4<f32>;
|
||||
|
||||
|
||||
@fragment
|
||||
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
|
||||
let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color;
|
||||
|
||||
// half size of the UI node
|
||||
let half_size = 0.5 * in.size;
|
||||
|
||||
// position relative to the center of the UI node
|
||||
let p = in.uv * in.size - half_size;
|
||||
|
||||
// thickness of the border closest to the current position
|
||||
let b = vec2(
|
||||
select(in.border_widths.x, in.border_widths.z, 0. < p.x),
|
||||
select(in.border_widths.y, in.border_widths.w, 0. < p.y)
|
||||
);
|
||||
|
||||
// select radius for the nearest corner
|
||||
let rs = select(in.border_radius.xy, in.border_radius.wz, 0.0 < p.y);
|
||||
let radius = select(rs.x, rs.y, 0.0 < p.x);
|
||||
|
||||
// distance along each axis from the corner
|
||||
let d = half_size - abs(p);
|
||||
|
||||
// if the distance to the edge from the current position on any axis
|
||||
// is less than the border width on that axis then the position is within
|
||||
// the border and we return the border color
|
||||
if d.x < b.x || d.y < b.y {
|
||||
// select radius for the nearest corner
|
||||
let rs = select(in.border_radius.xy, in.border_radius.wz, 0.0 < p.y);
|
||||
let radius = select(rs.x, rs.y, 0.0 < p.x);
|
||||
|
||||
// determine if the point is inside the curved corner and return the corresponding color
|
||||
let q = radius - d;
|
||||
if radius < min(max(q.x, q.y), 0.0) + length(vec2(max(q.x, 0.0), max(q.y, 0.0))) {
|
||||
return vec4(0.0);
|
||||
} else {
|
||||
return border_color;
|
||||
}
|
||||
}
|
||||
|
||||
// sample the texture at this position if it's to the left of the slider value
|
||||
// otherwise return a fully transparent color
|
||||
if in.uv.x < slider.x {
|
||||
let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color;
|
||||
return output_color;
|
||||
} else {
|
||||
return vec4(0.0);
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// The shader that goes with `extended_material_bindless.rs`.
|
||||
//
|
||||
// This code demonstrates how to write shaders that are compatible with both
|
||||
// bindless and non-bindless mode. See the `#ifdef BINDLESS` blocks.
|
||||
|
||||
#import bevy_pbr::{
|
||||
forward_io::{FragmentOutput, VertexOutput},
|
||||
mesh_bindings::mesh,
|
||||
pbr_fragment::pbr_input_from_standard_material,
|
||||
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
|
||||
}
|
||||
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
|
||||
|
||||
#ifdef BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::{material_array, material_indices}
|
||||
#else // BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::material
|
||||
#endif // BINDLESS
|
||||
|
||||
// Stores the indices of the bindless resources in the bindless resource arrays,
|
||||
// for the `ExampleBindlessExtension` fields.
|
||||
struct ExampleBindlessExtendedMaterialIndices {
|
||||
// The index of the `ExampleBindlessExtendedMaterial` data in
|
||||
// `example_extended_material`.
|
||||
material: u32,
|
||||
// The index of the texture we're going to modulate the base color with in
|
||||
// the `bindless_textures_2d` array.
|
||||
modulate_texture: u32,
|
||||
// The index of the sampler we're going to sample the modulated texture with
|
||||
// in the `bindless_samplers_filtering` array.
|
||||
modulate_texture_sampler: u32,
|
||||
}
|
||||
|
||||
// Plain data associated with this example material.
|
||||
struct ExampleBindlessExtendedMaterial {
|
||||
// The color that we multiply the base color, base color texture, and
|
||||
// modulated texture with.
|
||||
modulate_color: vec4<f32>,
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
|
||||
// The indices of the bindless resources in the bindless resource arrays, for
|
||||
// the `ExampleBindlessExtension` fields.
|
||||
@group(2) @binding(100) var<storage> example_extended_material_indices:
|
||||
array<ExampleBindlessExtendedMaterialIndices>;
|
||||
// An array that holds the `ExampleBindlessExtendedMaterial` plain old data,
|
||||
// indexed by `ExampleBindlessExtendedMaterialIndices.material`.
|
||||
@group(2) @binding(101) var<storage> example_extended_material:
|
||||
array<ExampleBindlessExtendedMaterial>;
|
||||
|
||||
#else // BINDLESS
|
||||
|
||||
// In non-bindless mode, we simply use a uniform for the plain old data.
|
||||
@group(2) @binding(50) var<uniform> example_extended_material: ExampleBindlessExtendedMaterial;
|
||||
@group(2) @binding(51) var modulate_texture: texture_2d<f32>;
|
||||
@group(2) @binding(52) var modulate_sampler: sampler;
|
||||
|
||||
#endif // BINDLESS
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
in: VertexOutput,
|
||||
@builtin(front_facing) is_front: bool,
|
||||
) -> FragmentOutput {
|
||||
#ifdef BINDLESS
|
||||
// Fetch the material slot. We'll use this in turn to fetch the bindless
|
||||
// indices from `example_extended_material_indices`.
|
||||
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
||||
#endif // BINDLESS
|
||||
|
||||
// Generate a `PbrInput` struct from the `StandardMaterial` bindings.
|
||||
var pbr_input = pbr_input_from_standard_material(in, is_front);
|
||||
|
||||
// Calculate the UV for the texture we're about to sample.
|
||||
#ifdef BINDLESS
|
||||
let uv_transform = material_array[material_indices[slot].material].uv_transform;
|
||||
#else // BINDLESS
|
||||
let uv_transform = material.uv_transform;
|
||||
#endif // BINDLESS
|
||||
let uv = (uv_transform * vec3(in.uv, 1.0)).xy;
|
||||
|
||||
// Multiply the base color by the `modulate_texture` and `modulate_color`.
|
||||
#ifdef BINDLESS
|
||||
// Notice how we fetch the texture, sampler, and plain extended material
|
||||
// data from the appropriate arrays.
|
||||
pbr_input.material.base_color *= textureSample(
|
||||
bindless_textures_2d[example_extended_material_indices[slot].modulate_texture],
|
||||
bindless_samplers_filtering[
|
||||
example_extended_material_indices[slot].modulate_texture_sampler
|
||||
],
|
||||
uv
|
||||
) * example_extended_material[example_extended_material_indices[slot].material].modulate_color;
|
||||
#else // BINDLESS
|
||||
pbr_input.material.base_color *= textureSample(modulate_texture, modulate_sampler, uv) *
|
||||
example_extended_material.modulate_color;
|
||||
#endif // BINDLESS
|
||||
|
||||
var out: FragmentOutput;
|
||||
// Apply lighting.
|
||||
out.color = apply_pbr_lighting(pbr_input);
|
||||
// Apply in-shader post processing (fog, alpha-premultiply, and also
|
||||
// tonemapping, debanding if the camera is non-HDR). Note this does not
|
||||
// include fullscreen postprocessing effects like bloom.
|
||||
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
|
||||
return out;
|
||||
}
|
@ -3,13 +3,10 @@
|
||||
|
||||
// This is the data that lives in the gpu only buffer
|
||||
@group(0) @binding(0) var<storage, read_write> data: array<u32>;
|
||||
@group(0) @binding(1) var texture: texture_storage_2d<r32uint, write>;
|
||||
|
||||
@compute @workgroup_size(1)
|
||||
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||
// We use the global_id to index the array to make sure we don't
|
||||
// access data used in another workgroup
|
||||
data[global_id.x] += 1u;
|
||||
// Write the same data to the texture
|
||||
textureStore(texture, vec2<i32>(i32(global_id.x), 0), vec4<u32>(data[global_id.x], 0, 0, 0));
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
#import bevy_pbr::irradiance_volume
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
#import bevy_pbr::clustered_forward
|
||||
|
||||
struct VoxelVisualizationIrradianceVolumeInfo {
|
||||
world_from_voxel: mat4x4<f32>,
|
||||
@ -26,24 +25,11 @@ fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let stp_rounded = round(stp - 0.5f) + 0.5f;
|
||||
let rounded_world_pos = (irradiance_volume_info.world_from_voxel * vec4(stp_rounded, 1.0f)).xyz;
|
||||
|
||||
// Look up the irradiance volume range in the cluster list.
|
||||
let view_z = dot(vec4<f32>(
|
||||
mesh_view_bindings::view.view_from_world[0].z,
|
||||
mesh_view_bindings::view.view_from_world[1].z,
|
||||
mesh_view_bindings::view.view_from_world[2].z,
|
||||
mesh_view_bindings::view.view_from_world[3].z
|
||||
), mesh.world_position);
|
||||
let cluster_index = clustered_forward::fragment_cluster_index(mesh.position.xy, view_z, false);
|
||||
var clusterable_object_index_ranges =
|
||||
clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
|
||||
|
||||
// `irradiance_volume_light()` multiplies by intensity, so cancel it out.
|
||||
// If we take intensity into account, the cubes will be way too bright.
|
||||
let rgb = irradiance_volume::irradiance_volume_light(
|
||||
mesh.world_position.xyz,
|
||||
mesh.world_normal,
|
||||
&clusterable_object_index_ranges,
|
||||
) / irradiance_volume_info.intensity;
|
||||
mesh.world_normal) / irradiance_volume_info.intensity;
|
||||
|
||||
return vec4<f32>(rgb, 1.0f);
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
//! Very simple shader used to demonstrate how to get the world position and pass data
|
||||
//! between the vertex and fragment shader. Also shows the custom vertex layout.
|
||||
|
||||
// First we import everything we need from bevy_pbr
|
||||
// A 2D shader would be very similar but import from bevy_sprite instead
|
||||
#import bevy_pbr::{
|
||||
mesh_functions,
|
||||
view_transformations::position_world_to_clip
|
||||
}
|
||||
|
||||
struct Vertex {
|
||||
// This is needed if you are using batching and/or gpu preprocessing
|
||||
// It's a built in so you don't need to define it in the vertex layout
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
// Like we defined for the vertex layout
|
||||
// position is at location 0
|
||||
@location(0) position: vec3<f32>,
|
||||
// and color at location 1
|
||||
@location(1) color: vec4<f32>,
|
||||
};
|
||||
|
||||
// This is the output of the vertex shader and we also use it as the input for the fragment shader
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) world_position: vec4<f32>,
|
||||
@location(1) color: vec3<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
// This is how bevy computes the world position
|
||||
// The vertex.instance_index is very important. Especially if you are using batching and gpu preprocessing
|
||||
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
|
||||
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
|
||||
out.clip_position = position_world_to_clip(out.world_position.xyz);
|
||||
|
||||
// We just use the raw vertex color
|
||||
out.color = vertex.color.rgb;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// output the color directly
|
||||
return vec4(in.color, 1.0);
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
#import bevy_pbr::{
|
||||
mesh_functions,
|
||||
view_transformations::position_world_to_clip
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
@location(0) position: vec3<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) world_position: vec4<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
let tag = mesh_functions::get_tag(vertex.instance_index);
|
||||
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
|
||||
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
|
||||
out.clip_position = position_world_to_clip(out.world_position.xyz);
|
||||
|
||||
out.color = colors[tag];
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
mesh: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return mesh.color;
|
||||
}
|
@ -9,19 +9,19 @@
|
||||
#import bevy_core_pipeline::tonemapping::tone_mapping
|
||||
#endif
|
||||
|
||||
// Sweep across hues on y axis with value from 0.0 to +15EV across x axis
|
||||
// Sweep across hues on y axis with value from 0.0 to +15EV across x axis
|
||||
// quantized into 24 steps for both axis.
|
||||
fn color_sweep(uv_input: vec2<f32>) -> vec3<f32> {
|
||||
var uv = uv_input;
|
||||
let steps = 24.0;
|
||||
uv.y = uv.y * (1.0 + 1.0 / steps);
|
||||
let ratio = 2.0;
|
||||
|
||||
|
||||
let h = PI * 2.0 * floor(1.0 + steps * uv.y) / steps;
|
||||
let L = floor(uv.x * steps * ratio) / (steps * ratio) - 0.5;
|
||||
|
||||
|
||||
var color = vec3(0.0);
|
||||
if uv.y < 1.0 {
|
||||
if uv.y < 1.0 {
|
||||
color = cos(h + vec3(0.0, 1.0, 2.0) * PI * 2.0 / 3.0);
|
||||
let maxRGB = max(color.r, max(color.g, color.b));
|
||||
let minRGB = min(color.r, min(color.g, color.b));
|
||||
|
@ -1,44 +0,0 @@
|
||||
fn make_polka_dots(pos: vec2<f32>, time: f32) -> vec4<f32> {
|
||||
let scaled_pos = pos * 6.0;
|
||||
let cell = vec2<f32>(fract(scaled_pos.x), fract(scaled_pos.y));
|
||||
var dist_from_center = distance(cell, vec2<f32>(0.5));
|
||||
|
||||
let is_even = (floor(scaled_pos.x) + floor(scaled_pos.y)) % 2.0;
|
||||
|
||||
var dot_color = vec3<f32>(0.0);
|
||||
var is_dot = 0.0;
|
||||
|
||||
@if(!PARTY_MODE) {
|
||||
let color1 = vec3<f32>(1.0, 0.4, 0.8); // pink
|
||||
let color2 = vec3<f32>(0.6, 0.2, 1.0); // purple
|
||||
dot_color = mix(color1, color2, is_even);
|
||||
is_dot = step(dist_from_center, 0.3);
|
||||
} @else {
|
||||
let grid_x = floor(scaled_pos.x);
|
||||
let grid_y = floor(scaled_pos.y);
|
||||
let wave_speed = 3.0;
|
||||
let wave_phase = time * wave_speed;
|
||||
|
||||
let diagonal_pos = (grid_x + grid_y) * 0.5;
|
||||
let wave_value = sin(diagonal_pos + wave_phase);
|
||||
|
||||
let wave_normalized = (wave_value + 1.0) * 0.5;
|
||||
|
||||
let color1 = vec3<f32>(1.0, 0.3, 0.7);
|
||||
let color2 = vec3<f32>(0.5, 0.1, 1.0);
|
||||
let intense_color1 = vec3<f32>(1.0, 0.1, 0.9);
|
||||
let intense_color2 = vec3<f32>(0.8, 0.0, 1.0);
|
||||
|
||||
let animated_color1 = mix(color1, intense_color1, wave_normalized);
|
||||
let animated_color2 = mix(color2, intense_color2, wave_normalized);
|
||||
|
||||
dot_color = mix(animated_color1, animated_color2, is_even);
|
||||
|
||||
let size_mod = 0.15 * wave_value;
|
||||
dist_from_center = dist_from_center * (1.0 - size_mod);
|
||||
// Animate whether something is a dot by position but also time
|
||||
is_dot = step(dist_from_center, 0.3 + wave_normalized * 0.2);
|
||||
}
|
||||
|
||||
return vec4<f32>(dot_color * is_dot, 1.0);
|
||||
}
|
Before Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 83 KiB |
@ -1,105 +1,73 @@
|
||||
[package]
|
||||
name = "benches"
|
||||
edition = "2024"
|
||||
edition = "2021"
|
||||
description = "Benchmarks that test Bevy's performance"
|
||||
publish = false
|
||||
license = "MIT OR Apache-2.0"
|
||||
# Do not automatically discover benchmarks, we specify them manually instead.
|
||||
autobenches = false
|
||||
|
||||
[dependencies]
|
||||
# The primary crate that runs and analyzes our benchmarks. This is a regular dependency because the
|
||||
# `bench!` macro refers to it in its documentation.
|
||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# Bevy crates
|
||||
bevy_app = { path = "../crates/bevy_app" }
|
||||
bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] }
|
||||
bevy_math = { path = "../crates/bevy_math" }
|
||||
bevy_picking = { path = "../crates/bevy_picking", features = [
|
||||
"bevy_mesh_picking_backend",
|
||||
] }
|
||||
bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] }
|
||||
bevy_render = { path = "../crates/bevy_render" }
|
||||
bevy_tasks = { path = "../crates/bevy_tasks" }
|
||||
bevy_utils = { path = "../crates/bevy_utils" }
|
||||
bevy_platform = { path = "../crates/bevy_platform", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
# Other crates
|
||||
glam = "0.29"
|
||||
glam = "0.27"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
bevy_app = { path = "../crates/bevy_app" }
|
||||
bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] }
|
||||
bevy_reflect = { path = "../crates/bevy_reflect" }
|
||||
bevy_tasks = { path = "../crates/bevy_tasks" }
|
||||
bevy_utils = { path = "../crates/bevy_utils" }
|
||||
bevy_math = { path = "../crates/bevy_math" }
|
||||
bevy_render = { path = "../crates/bevy_render" }
|
||||
|
||||
# Make `bevy_render` compile on Linux with x11 windowing. x11 vs. Wayland does not matter here
|
||||
# because the benches do not actually open any windows.
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
bevy_winit = { path = "../crates/bevy_winit", features = ["x11"] }
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
|
||||
[lints.clippy]
|
||||
doc_markdown = "warn"
|
||||
manual_let_else = "warn"
|
||||
match_same_arms = "warn"
|
||||
redundant_closure_for_method_calls = "warn"
|
||||
redundant_else = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
type_complexity = "allow"
|
||||
undocumented_unsafe_blocks = "warn"
|
||||
unwrap_or_default = "warn"
|
||||
needless_lifetimes = "allow"
|
||||
too_many_arguments = "allow"
|
||||
nonstandard_macro_braces = "warn"
|
||||
|
||||
ptr_as_ptr = "warn"
|
||||
ptr_cast_constness = "warn"
|
||||
ref_as_ptr = "warn"
|
||||
|
||||
# see: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366966219
|
||||
too_long_first_doc_paragraph = "allow"
|
||||
|
||||
allow_attributes = "warn"
|
||||
allow_attributes_without_reason = "warn"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
|
||||
unsafe_op_in_unsafe_fn = "warn"
|
||||
unused_qualifications = "warn"
|
||||
|
||||
[lib]
|
||||
# This fixes the "Unrecognized Option" error when running commands like
|
||||
# `cargo bench -- --save-baseline before` by disabling the default benchmark harness.
|
||||
# See <https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options>
|
||||
# for more information.
|
||||
bench = false
|
||||
[[bench]]
|
||||
name = "change_detection"
|
||||
path = "benches/bevy_ecs/change_detection.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "ecs"
|
||||
path = "benches/bevy_ecs/main.rs"
|
||||
path = "benches/bevy_ecs/benches.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "math"
|
||||
path = "benches/bevy_math/main.rs"
|
||||
name = "reflect_list"
|
||||
path = "benches/bevy_reflect/list.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "picking"
|
||||
path = "benches/bevy_picking/main.rs"
|
||||
name = "reflect_map"
|
||||
path = "benches/bevy_reflect/map.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "reflect"
|
||||
path = "benches/bevy_reflect/main.rs"
|
||||
name = "reflect_struct"
|
||||
path = "benches/bevy_reflect/struct.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "render"
|
||||
path = "benches/bevy_render/main.rs"
|
||||
name = "parse_reflect_path"
|
||||
path = "benches/bevy_reflect/path.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "tasks"
|
||||
path = "benches/bevy_tasks/main.rs"
|
||||
name = "iter"
|
||||
path = "benches/bevy_tasks/iter.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "bezier"
|
||||
path = "benches/bevy_math/bezier.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "torus"
|
||||
path = "benches/bevy_render/torus.rs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "entity_hash"
|
||||
path = "benches/bevy_ecs/world/entity_hash.rs"
|
||||
harness = false
|
||||
|
@ -1,35 +1,27 @@
|
||||
# Bevy Benchmarks
|
||||
|
||||
This is a crate with a collection of benchmarks for Bevy.
|
||||
This is a crate with a collection of benchmarks for Bevy, separate from the rest of the Bevy crates.
|
||||
|
||||
## Running benchmarks
|
||||
## Running the benchmarks
|
||||
|
||||
Benchmarks can be run through Cargo:
|
||||
1. Setup everything you need for Bevy with the [setup guide](https://bevyengine.org/learn/book/getting-started/setup/).
|
||||
2. Move into the `benches` directory (where this README is located).
|
||||
|
||||
```sh
|
||||
# Run all benchmarks. (This will take a while!)
|
||||
cargo bench -p benches
|
||||
```sh
|
||||
bevy $ cd benches
|
||||
```
|
||||
|
||||
# Just compile the benchmarks, do not run them.
|
||||
cargo bench -p benches --no-run
|
||||
3. Run the benchmarks with cargo (This will take a while)
|
||||
|
||||
# Run the benchmarks for a specific crate. (See `Cargo.toml` for a complete list of crates
|
||||
# tracked.)
|
||||
cargo bench -p benches --bench ecs
|
||||
```sh
|
||||
bevy/benches $ cargo bench
|
||||
```
|
||||
|
||||
# Filter which benchmarks are run based on the name. This will only run benchmarks whose name
|
||||
# contains "name_fragment".
|
||||
cargo bench -p benches -- name_fragment
|
||||
If you'd like to only compile the benchmarks (without running them), you can do that like this:
|
||||
|
||||
# List all available benchmarks.
|
||||
cargo bench -p benches -- --list
|
||||
|
||||
# Save a baseline to be compared against later.
|
||||
cargo bench -p benches --save-baseline before
|
||||
|
||||
# Compare the current benchmarks against a baseline to find performance gains and regressions.
|
||||
cargo bench -p benches --baseline before
|
||||
```
|
||||
```sh
|
||||
bevy/benches $ cargo bench --no-run
|
||||
```
|
||||
|
||||
## Criterion
|
||||
|
||||
|
15
benches/benches/bevy_ecs/benches.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use criterion::criterion_main;
|
||||
|
||||
mod components;
|
||||
mod events;
|
||||
mod iteration;
|
||||
mod scheduling;
|
||||
mod world;
|
||||
|
||||
criterion_main!(
|
||||
components::components_benches,
|
||||
events::event_benches,
|
||||
iteration::iterations_benches,
|
||||
scheduling::scheduling_benches,
|
||||
world::world_benches,
|
||||
);
|
@ -1,13 +1,10 @@
|
||||
use core::hint::black_box;
|
||||
|
||||
use bevy_ecs::{
|
||||
component::{Component, Mutable},
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
prelude::{Added, Changed, EntityWorldMut, QueryState},
|
||||
query::QueryFilter,
|
||||
prelude::{Added, Changed},
|
||||
world::World,
|
||||
};
|
||||
use criterion::{criterion_group, Criterion};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use rand::{prelude::SliceRandom, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
@ -17,27 +14,15 @@ criterion_group!(
|
||||
all_changed_detection,
|
||||
few_changed_detection,
|
||||
none_changed_detection,
|
||||
multiple_archetype_none_changed_detection
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
macro_rules! modify {
|
||||
($components:ident;$($index:tt),*) => {
|
||||
$(
|
||||
$components.$index.map(|mut v| {
|
||||
v.0+=1.
|
||||
});
|
||||
)*
|
||||
};
|
||||
}
|
||||
#[derive(Component, Default)]
|
||||
#[component(storage = "Table")]
|
||||
struct Table(f32);
|
||||
#[derive(Component, Default)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct Sparse(f32);
|
||||
#[derive(Component, Default)]
|
||||
#[component(storage = "Table")]
|
||||
struct Data<const X: u16>(f32);
|
||||
|
||||
trait BenchModify {
|
||||
fn bench_modify(&mut self) -> f32;
|
||||
@ -56,7 +41,7 @@ impl BenchModify for Sparse {
|
||||
}
|
||||
}
|
||||
|
||||
const ENTITIES_TO_BENCH_COUNT: &[u32] = &[5000, 50000];
|
||||
const RANGE_ENTITIES_TO_BENCH_COUNT: std::ops::Range<u32> = 5..7;
|
||||
|
||||
type BenchGroup<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::WallTime>;
|
||||
|
||||
@ -70,11 +55,6 @@ fn setup<T: Component + Default>(entity_count: u32) -> World {
|
||||
black_box(world)
|
||||
}
|
||||
|
||||
// create a cached query in setup to avoid extra costs in each iter
|
||||
fn generic_filter_query<F: QueryFilter>(world: &mut World) -> QueryState<Entity, F> {
|
||||
world.query_filtered::<Entity, F>()
|
||||
}
|
||||
|
||||
fn generic_bench<P: Copy>(
|
||||
bench_group: &mut BenchGroup,
|
||||
mut benches: Vec<Box<dyn FnMut(&mut BenchGroup, P)>>,
|
||||
@ -87,17 +67,14 @@ fn generic_bench<P: Copy>(
|
||||
|
||||
fn all_added_detection_generic<T: Component + Default>(group: &mut BenchGroup, entity_count: u32) {
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = setup::<T>(entity_count);
|
||||
let query = generic_filter_query::<Added<T>>(&mut world);
|
||||
(world, query)
|
||||
},
|
||||
|(world, query)| {
|
||||
bencher.iter_batched(
|
||||
|| setup::<T>(entity_count),
|
||||
|mut world| {
|
||||
let mut count = 0;
|
||||
for entity in query.iter(world) {
|
||||
let mut query = world.query_filtered::<Entity, Added<T>>();
|
||||
for entity in query.iter(&world) {
|
||||
black_box(entity);
|
||||
count += 1;
|
||||
}
|
||||
@ -111,9 +88,9 @@ fn all_added_detection_generic<T: Component + Default>(group: &mut BenchGroup, e
|
||||
|
||||
fn all_added_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("all_added_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
@ -125,14 +102,14 @@ fn all_added_detection(criterion: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
fn all_changed_detection_generic<T: Component<Mutability = Mutable> + Default + BenchModify>(
|
||||
fn all_changed_detection_generic<T: Component + Default + BenchModify>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
bencher.iter_batched(
|
||||
|| {
|
||||
let mut world = setup::<T>(entity_count);
|
||||
world.clear_trackers();
|
||||
@ -140,12 +117,12 @@ fn all_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
|
||||
for mut component in query.iter_mut(&mut world) {
|
||||
black_box(component.bench_modify());
|
||||
}
|
||||
let query = generic_filter_query::<Changed<T>>(&mut world);
|
||||
(world, query)
|
||||
world
|
||||
},
|
||||
|(world, query)| {
|
||||
|mut world| {
|
||||
let mut count = 0;
|
||||
for entity in query.iter(world) {
|
||||
let mut query = world.query_filtered::<Entity, Changed<T>>();
|
||||
for entity in query.iter(&world) {
|
||||
black_box(entity);
|
||||
count += 1;
|
||||
}
|
||||
@ -159,9 +136,9 @@ fn all_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
|
||||
|
||||
fn all_changed_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("all_changed_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
@ -173,16 +150,16 @@ fn all_changed_detection(criterion: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
fn few_changed_detection_generic<T: Component<Mutability = Mutable> + Default + BenchModify>(
|
||||
fn few_changed_detection_generic<T: Component + Default + BenchModify>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
let ratio_to_modify = 0.1;
|
||||
let amount_to_modify = (entity_count as f32 * ratio_to_modify) as usize;
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
bencher.iter_batched(
|
||||
|| {
|
||||
let mut world = setup::<T>(entity_count);
|
||||
world.clear_trackers();
|
||||
@ -193,11 +170,11 @@ fn few_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
|
||||
for component in to_modify[0..amount_to_modify].iter_mut() {
|
||||
black_box(component.bench_modify());
|
||||
}
|
||||
let query = generic_filter_query::<Changed<T>>(&mut world);
|
||||
(world, query)
|
||||
world
|
||||
},
|
||||
|(world, query)| {
|
||||
for entity in query.iter(world) {
|
||||
|mut world| {
|
||||
let mut query = world.query_filtered::<Entity, Changed<T>>();
|
||||
for entity in query.iter(&world) {
|
||||
black_box(entity);
|
||||
}
|
||||
},
|
||||
@ -209,9 +186,9 @@ fn few_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
|
||||
|
||||
fn few_changed_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("few_changed_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
@ -223,23 +200,23 @@ fn few_changed_detection(criterion: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
fn none_changed_detection_generic<T: Component<Mutability = Mutable> + Default>(
|
||||
fn none_changed_detection_generic<T: Component + Default>(
|
||||
group: &mut BenchGroup,
|
||||
entity_count: u32,
|
||||
) {
|
||||
group.bench_function(
|
||||
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
|
||||
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
bencher.iter_batched(
|
||||
|| {
|
||||
let mut world = setup::<T>(entity_count);
|
||||
world.clear_trackers();
|
||||
let query = generic_filter_query::<Changed<T>>(&mut world);
|
||||
(world, query)
|
||||
world
|
||||
},
|
||||
|(world, query)| {
|
||||
|mut world| {
|
||||
let mut count = 0;
|
||||
for entity in query.iter(world) {
|
||||
let mut query = world.query_filtered::<Entity, Changed<T>>();
|
||||
for entity in query.iter(&world) {
|
||||
black_box(entity);
|
||||
count += 1;
|
||||
}
|
||||
@ -253,9 +230,9 @@ fn none_changed_detection_generic<T: Component<Mutability = Mutable> + Default>(
|
||||
|
||||
fn none_changed_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("none_changed_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for &entity_count in ENTITIES_TO_BENCH_COUNT {
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
|
||||
generic_bench(
|
||||
&mut group,
|
||||
vec![
|
||||
@ -266,113 +243,3 @@ fn none_changed_detection(criterion: &mut Criterion) {
|
||||
);
|
||||
}
|
||||
}
|
||||
fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {
|
||||
if i & (1 << B) != 0 {
|
||||
entity.insert(Data::<B>(1.0));
|
||||
}
|
||||
}
|
||||
|
||||
fn add_archetypes_entities<T: Component<Mutability = Mutable> + Default>(
|
||||
world: &mut World,
|
||||
archetype_count: u16,
|
||||
entity_count: u32,
|
||||
) {
|
||||
for i in 0..archetype_count {
|
||||
for _j in 0..entity_count {
|
||||
let mut e = world.spawn(T::default());
|
||||
insert_if_bit_enabled::<0>(&mut e, i);
|
||||
insert_if_bit_enabled::<1>(&mut e, i);
|
||||
insert_if_bit_enabled::<2>(&mut e, i);
|
||||
insert_if_bit_enabled::<3>(&mut e, i);
|
||||
insert_if_bit_enabled::<4>(&mut e, i);
|
||||
insert_if_bit_enabled::<5>(&mut e, i);
|
||||
insert_if_bit_enabled::<6>(&mut e, i);
|
||||
insert_if_bit_enabled::<7>(&mut e, i);
|
||||
insert_if_bit_enabled::<8>(&mut e, i);
|
||||
insert_if_bit_enabled::<9>(&mut e, i);
|
||||
insert_if_bit_enabled::<10>(&mut e, i);
|
||||
insert_if_bit_enabled::<11>(&mut e, i);
|
||||
insert_if_bit_enabled::<12>(&mut e, i);
|
||||
insert_if_bit_enabled::<13>(&mut e, i);
|
||||
insert_if_bit_enabled::<14>(&mut e, i);
|
||||
insert_if_bit_enabled::<15>(&mut e, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn multiple_archetype_none_changed_detection_generic<
|
||||
T: Component<Mutability = Mutable> + Default + BenchModify,
|
||||
>(
|
||||
group: &mut BenchGroup,
|
||||
archetype_count: u16,
|
||||
entity_count: u32,
|
||||
) {
|
||||
group.bench_function(
|
||||
format!(
|
||||
"{}_archetypes_{}_entities_{}",
|
||||
archetype_count,
|
||||
entity_count,
|
||||
core::any::type_name::<T>()
|
||||
),
|
||||
|bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = World::new();
|
||||
add_archetypes_entities::<T>(&mut world, archetype_count, entity_count);
|
||||
world.clear_trackers();
|
||||
let mut query = world.query::<(
|
||||
Option<&mut Data<0>>,
|
||||
Option<&mut Data<1>>,
|
||||
Option<&mut Data<2>>,
|
||||
Option<&mut Data<3>>,
|
||||
Option<&mut Data<4>>,
|
||||
Option<&mut Data<5>>,
|
||||
Option<&mut Data<6>>,
|
||||
Option<&mut Data<7>>,
|
||||
Option<&mut Data<8>>,
|
||||
Option<&mut Data<9>>,
|
||||
Option<&mut Data<10>>,
|
||||
Option<&mut Data<11>>,
|
||||
Option<&mut Data<12>>,
|
||||
Option<&mut Data<13>>,
|
||||
Option<&mut Data<14>>,
|
||||
)>();
|
||||
for components in query.iter_mut(&mut world) {
|
||||
// change Data<X> while keeping T unchanged
|
||||
modify!(components;0,1,2,3,4,5,6,7,8,9,10,11,12,13,14);
|
||||
}
|
||||
let query = generic_filter_query::<Changed<T>>(&mut world);
|
||||
(world, query)
|
||||
},
|
||||
|(world, query)| {
|
||||
let mut count = 0;
|
||||
for entity in query.iter(world) {
|
||||
black_box(entity);
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(0, count);
|
||||
},
|
||||
criterion::BatchSize::LargeInput,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn multiple_archetype_none_changed_detection(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("multiple_archetypes_none_changed_detection");
|
||||
group.warm_up_time(core::time::Duration::from_millis(800));
|
||||
group.measurement_time(core::time::Duration::from_secs(8));
|
||||
for archetype_count in [5, 20, 100] {
|
||||
for entity_count in [10, 100, 1000, 10000] {
|
||||
multiple_archetype_none_changed_detection_generic::<Table>(
|
||||
&mut group,
|
||||
archetype_count,
|
||||
entity_count,
|
||||
);
|
||||
multiple_archetype_none_changed_detection_generic::<Sparse>(
|
||||
&mut group,
|
||||
archetype_count,
|
||||
entity_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Component, Clone)]
|
||||
#[derive(Component)]
|
||||
struct A(f32);
|
||||
#[derive(Component)]
|
||||
struct B(f32);
|
||||
@ -12,18 +12,19 @@ impl Benchmark {
|
||||
let mut world = World::default();
|
||||
|
||||
let entities = world
|
||||
.spawn_batch(core::iter::repeat_n(A(0.), 10_000))
|
||||
.collect();
|
||||
.spawn_batch((0..10000).map(|_| A(0.0)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Self(world, entities)
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
for entity in &self.1 {
|
||||
self.0.entity_mut(*entity).insert(B(0.));
|
||||
self.0.insert_one(*entity, B(0.0)).unwrap();
|
||||
}
|
||||
|
||||
for entity in &self.1 {
|
||||
self.0.entity_mut(*entity).remove::<B>();
|
||||
self.0.remove_one::<B>(*entity).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,114 +0,0 @@
|
||||
#![expect(
|
||||
dead_code,
|
||||
reason = "The `Mat4`s in the structs are used to bloat the size of the structs for benchmarking purposes."
|
||||
)]
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use glam::*;
|
||||
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct A<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct B<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct C<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct D<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct E<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct F<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct Z<const N: usize>;
|
||||
|
||||
pub struct Benchmark(World, Vec<Entity>);
|
||||
|
||||
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((
|
||||
(
|
||||
A::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
A::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
),
|
||||
(
|
||||
A::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
A::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
),
|
||||
(
|
||||
A::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
A::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
),
|
||||
(
|
||||
A::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
Z::<1>,
|
||||
Z::<2>,
|
||||
Z::<3>,
|
||||
Z::<4>,
|
||||
Z::<5>,
|
||||
Z::<6>,
|
||||
Z::<7>,
|
||||
),
|
||||
))
|
||||
.id(),
|
||||
);
|
||||
}
|
||||
|
||||
Self(world, entities)
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
for entity in &self.1 {
|
||||
self.0.entity_mut(*entity).insert((
|
||||
F::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
));
|
||||
}
|
||||
|
||||
for entity in &self.1 {
|
||||
self.0
|
||||
.entity_mut(*entity)
|
||||
.remove::<(F<1>, F<2>, F<3>, F<4>, F<5>, F<6>, F<7>)>();
|
||||
self.0
|
||||
.entity_mut(*entity)
|
||||
.remove::<(Z<1>, Z<2>, Z<3>, Z<4>, Z<5>, Z<6>, Z<7>)>();
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ fn setup(system_count: usize) -> (World, Schedule) {
|
||||
}
|
||||
|
||||
fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {
|
||||
if i & (1 << B) != 0 {
|
||||
if i & 1 << B != 0 {
|
||||
entity.insert(A::<B>(1.0));
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,19 @@
|
||||
mod add_remove;
|
||||
use criterion::*;
|
||||
|
||||
mod add_remove_big_sparse_set;
|
||||
mod add_remove_big_table;
|
||||
mod add_remove_sparse_set;
|
||||
mod add_remove_table;
|
||||
mod add_remove_very_big_table;
|
||||
mod archetype_updates;
|
||||
mod insert_simple;
|
||||
mod insert_simple_unbatched;
|
||||
|
||||
use archetype_updates::*;
|
||||
use criterion::{criterion_group, Criterion};
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
components_benches,
|
||||
add_remove,
|
||||
add_remove_big,
|
||||
add_remove_very_big,
|
||||
insert_simple,
|
||||
no_archetypes,
|
||||
added_archetypes,
|
||||
@ -23,8 +21,8 @@ criterion_group!(
|
||||
|
||||
fn add_remove(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("add_remove");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
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());
|
||||
@ -38,8 +36,8 @@ fn add_remove(c: &mut Criterion) {
|
||||
|
||||
fn add_remove_big(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("add_remove_big");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
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());
|
||||
@ -51,21 +49,10 @@ fn add_remove_big(c: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn add_remove_very_big(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("add_remove_very_big");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
group.bench_function("table", |b| {
|
||||
let mut bench = add_remove_very_big_table::Benchmark::new();
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn insert_simple(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("insert_simple");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
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 = insert_simple::Benchmark::new();
|
||||
b.iter(move || bench.run());
|
||||
|
@ -1,9 +1,9 @@
|
||||
use core::hint::black_box;
|
||||
|
||||
use bevy_ecs::{component::Component, prelude::*, schedule::ExecutorKind, world::World};
|
||||
use criterion::{criterion_group, BenchmarkId, Criterion};
|
||||
use bevy_ecs::{component::Component, prelude::*, world::World};
|
||||
use bevy_tasks::{ComputeTaskPool, TaskPool};
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
|
||||
criterion_group!(benches, empty_archetypes);
|
||||
criterion_main!(benches);
|
||||
|
||||
#[derive(Component)]
|
||||
struct A<const N: u16>(f32);
|
||||
@ -47,12 +47,13 @@ fn for_each(
|
||||
&A<12>,
|
||||
)>,
|
||||
) {
|
||||
query.iter().for_each(|comp| {
|
||||
query.for_each(|comp| {
|
||||
black_box(comp);
|
||||
});
|
||||
}
|
||||
|
||||
fn par_for_each(
|
||||
task_pool: Res<ComputeTaskPool>,
|
||||
query: Query<(
|
||||
&A<0>,
|
||||
&A<1>,
|
||||
@ -69,29 +70,25 @@ fn par_for_each(
|
||||
&A<12>,
|
||||
)>,
|
||||
) {
|
||||
query.par_iter().for_each(|comp| {
|
||||
query.par_for_each(&*task_pool, 64, |comp| {
|
||||
black_box(comp);
|
||||
});
|
||||
}
|
||||
|
||||
fn setup(parallel: bool, setup: impl FnOnce(&mut Schedule)) -> (World, Schedule) {
|
||||
let world = World::new();
|
||||
let mut world = World::new();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
schedule.set_executor_kind(match parallel {
|
||||
true => ExecutorKind::MultiThreaded,
|
||||
false => ExecutorKind::SingleThreaded,
|
||||
});
|
||||
|
||||
if parallel {
|
||||
world.insert_resource(ComputeTaskPool(TaskPool::default()));
|
||||
}
|
||||
setup(&mut schedule);
|
||||
|
||||
(world, schedule)
|
||||
}
|
||||
|
||||
/// create `count` entities with distinct archetypes
|
||||
fn add_archetypes(world: &mut World, count: u16) {
|
||||
for i in 0..count {
|
||||
let mut e = world.spawn_empty();
|
||||
let mut e = world.spawn();
|
||||
e.insert(A::<0>(1.0));
|
||||
e.insert(A::<1>(1.0));
|
||||
e.insert(A::<2>(1.0));
|
||||
@ -105,49 +102,49 @@ fn add_archetypes(world: &mut World, count: u16) {
|
||||
e.insert(A::<10>(1.0));
|
||||
e.insert(A::<11>(1.0));
|
||||
e.insert(A::<12>(1.0));
|
||||
if i & (1 << 1) != 0 {
|
||||
if i & 1 << 1 != 0 {
|
||||
e.insert(A::<13>(1.0));
|
||||
}
|
||||
if i & (1 << 2) != 0 {
|
||||
if i & 1 << 2 != 0 {
|
||||
e.insert(A::<14>(1.0));
|
||||
}
|
||||
if i & (1 << 3) != 0 {
|
||||
if i & 1 << 3 != 0 {
|
||||
e.insert(A::<15>(1.0));
|
||||
}
|
||||
if i & (1 << 4) != 0 {
|
||||
if i & 1 << 4 != 0 {
|
||||
e.insert(A::<16>(1.0));
|
||||
}
|
||||
if i & (1 << 5) != 0 {
|
||||
if i & 1 << 5 != 0 {
|
||||
e.insert(A::<18>(1.0));
|
||||
}
|
||||
if i & (1 << 6) != 0 {
|
||||
if i & 1 << 6 != 0 {
|
||||
e.insert(A::<19>(1.0));
|
||||
}
|
||||
if i & (1 << 7) != 0 {
|
||||
if i & 1 << 7 != 0 {
|
||||
e.insert(A::<20>(1.0));
|
||||
}
|
||||
if i & (1 << 8) != 0 {
|
||||
if i & 1 << 8 != 0 {
|
||||
e.insert(A::<21>(1.0));
|
||||
}
|
||||
if i & (1 << 9) != 0 {
|
||||
if i & 1 << 9 != 0 {
|
||||
e.insert(A::<22>(1.0));
|
||||
}
|
||||
if i & (1 << 10) != 0 {
|
||||
if i & 1 << 10 != 0 {
|
||||
e.insert(A::<23>(1.0));
|
||||
}
|
||||
if i & (1 << 11) != 0 {
|
||||
if i & 1 << 11 != 0 {
|
||||
e.insert(A::<24>(1.0));
|
||||
}
|
||||
if i & (1 << 12) != 0 {
|
||||
if i & 1 << 12 != 0 {
|
||||
e.insert(A::<25>(1.0));
|
||||
}
|
||||
if i & (1 << 13) != 0 {
|
||||
if i & 1 << 13 != 0 {
|
||||
e.insert(A::<26>(1.0));
|
||||
}
|
||||
if i & (1 << 14) != 0 {
|
||||
if i & 1 << 14 != 0 {
|
||||
e.insert(A::<27>(1.0));
|
||||
}
|
||||
if i & (1 << 15) != 0 {
|
||||
if i & 1 << 15 != 0 {
|
||||
e.insert(A::<28>(1.0));
|
||||
}
|
||||
}
|
||||
@ -161,7 +158,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
});
|
||||
add_archetypes(&mut world, archetype_count);
|
||||
world.clear_entities();
|
||||
let mut e = world.spawn_empty();
|
||||
let mut e = world.spawn();
|
||||
e.insert(A::<0>(1.0));
|
||||
e.insert(A::<1>(1.0));
|
||||
e.insert(A::<2>(1.0));
|
||||
@ -182,7 +179,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
|bencher, &_| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -192,7 +189,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
});
|
||||
add_archetypes(&mut world, archetype_count);
|
||||
world.clear_entities();
|
||||
let mut e = world.spawn_empty();
|
||||
let mut e = world.spawn();
|
||||
e.insert(A::<0>(1.0));
|
||||
e.insert(A::<1>(1.0));
|
||||
e.insert(A::<2>(1.0));
|
||||
@ -213,7 +210,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
|bencher, &_| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -223,7 +220,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
});
|
||||
add_archetypes(&mut world, archetype_count);
|
||||
world.clear_entities();
|
||||
let mut e = world.spawn_empty();
|
||||
let mut e = world.spawn();
|
||||
e.insert(A::<0>(1.0));
|
||||
e.insert(A::<1>(1.0));
|
||||
e.insert(A::<2>(1.0));
|
||||
@ -244,7 +241,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
|bencher, &_| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -1,238 +0,0 @@
|
||||
use core::hint::black_box;
|
||||
|
||||
use benches::bench;
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_ecs::component::ComponentCloneBehavior;
|
||||
use bevy_ecs::entity::EntityCloner;
|
||||
use bevy_ecs::hierarchy::ChildOf;
|
||||
use bevy_ecs::reflect::AppTypeRegistry;
|
||||
use bevy_ecs::{component::Component, world::World};
|
||||
use bevy_math::Mat4;
|
||||
use bevy_reflect::{GetTypeRegistration, Reflect};
|
||||
use criterion::{criterion_group, Bencher, Criterion, Throughput};
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
single,
|
||||
hierarchy_tall,
|
||||
hierarchy_wide,
|
||||
hierarchy_many,
|
||||
);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C1(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C2(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C3(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C4(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C5(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C6(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C7(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C8(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C9(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C10(Mat4);
|
||||
|
||||
type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10);
|
||||
|
||||
/// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to
|
||||
/// use the [`Reflect`] trait instead of [`Clone`].
|
||||
fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
||||
world: &mut World,
|
||||
linked_cloning: bool,
|
||||
) -> EntityCloner {
|
||||
// Get mutable access to the type registry, creating it if it does not exist yet.
|
||||
let registry = world.get_resource_or_init::<AppTypeRegistry>();
|
||||
|
||||
// Recursively register all components in the bundle to the reflection type registry.
|
||||
{
|
||||
let mut r = registry.write();
|
||||
r.register::<B>();
|
||||
}
|
||||
|
||||
// Recursively register all components in the bundle, then save the component IDs to a list.
|
||||
// This uses `contributed_components()`, meaning both explicit and required component IDs in
|
||||
// this bundle are saved.
|
||||
let component_ids: Vec<_> = world.register_bundle::<B>().contributed_components().into();
|
||||
|
||||
let mut builder = EntityCloner::build(world);
|
||||
|
||||
// Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`.
|
||||
for component in component_ids {
|
||||
builder.override_clone_behavior_with_id(component, ComponentCloneBehavior::reflect());
|
||||
}
|
||||
builder.linked_cloning(linked_cloning);
|
||||
|
||||
builder.finish()
|
||||
}
|
||||
|
||||
/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
|
||||
/// bundle `B`.
|
||||
///
|
||||
/// The bundle must implement [`Default`], which is used to create the first entity that gets cloned
|
||||
/// in the benchmark.
|
||||
///
|
||||
/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneHandler`] for all
|
||||
/// components (which is usually [`ComponentCloneHandler::clone_handler()`]). If `clone_via_reflect`
|
||||
/// is true, it will overwrite the handler for all components in the bundle to be
|
||||
/// [`ComponentCloneHandler::reflect_handler()`].
|
||||
fn bench_clone<B: Bundle + Default + GetTypeRegistration>(
|
||||
b: &mut Bencher,
|
||||
clone_via_reflect: bool,
|
||||
) {
|
||||
let mut world = World::default();
|
||||
|
||||
let mut cloner = if clone_via_reflect {
|
||||
reflection_cloner::<B>(&mut world, false)
|
||||
} else {
|
||||
EntityCloner::default()
|
||||
};
|
||||
|
||||
// Spawn the first entity, which will be cloned in the benchmark routine.
|
||||
let id = world.spawn(B::default()).id();
|
||||
|
||||
b.iter(|| {
|
||||
// clones the given entity
|
||||
cloner.spawn_clone(&mut world, black_box(id));
|
||||
world.flush();
|
||||
});
|
||||
}
|
||||
|
||||
/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
|
||||
/// bundle `B`.
|
||||
///
|
||||
/// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several
|
||||
/// children. It does so by setting up an entity tree with a given `height` where each entity has a
|
||||
/// specified number of `children`.
|
||||
///
|
||||
/// For example, setting `height` to 5 and `children` to 1 creates a single chain of entities with
|
||||
/// no siblings. Alternatively, setting `height` to 1 and `children` to 5 will spawn 5 direct
|
||||
/// children of the root entity.
|
||||
fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
|
||||
b: &mut Bencher,
|
||||
height: usize,
|
||||
children: usize,
|
||||
clone_via_reflect: bool,
|
||||
) {
|
||||
let mut world = World::default();
|
||||
|
||||
let mut cloner = if clone_via_reflect {
|
||||
reflection_cloner::<B>(&mut world, true)
|
||||
} else {
|
||||
let mut builder = EntityCloner::build(&mut world);
|
||||
builder.linked_cloning(true);
|
||||
builder.finish()
|
||||
};
|
||||
|
||||
// Make the clone command recursive, so children are cloned as well.
|
||||
|
||||
// Spawn the first entity, which will be cloned in the benchmark routine.
|
||||
let id = world.spawn(B::default()).id();
|
||||
|
||||
let mut hierarchy_level = vec![id];
|
||||
|
||||
// Set up the hierarchy tree by spawning all children.
|
||||
for _ in 0..height {
|
||||
let current_hierarchy_level = hierarchy_level.clone();
|
||||
|
||||
hierarchy_level.clear();
|
||||
|
||||
for parent in current_hierarchy_level {
|
||||
for _ in 0..children {
|
||||
let child_id = world.spawn((B::default(), ChildOf(parent))).id();
|
||||
hierarchy_level.push(child_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
cloner.spawn_clone(&mut world, black_box(id));
|
||||
world.flush();
|
||||
});
|
||||
}
|
||||
|
||||
// Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This
|
||||
// constant represents this as an easy array that can be used in a `for` loop.
|
||||
const SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)];
|
||||
|
||||
/// Benchmarks cloning a single entity with 10 components and no children.
|
||||
fn single(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group(bench!("single"));
|
||||
|
||||
// We're cloning 1 entity.
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone::<ComplexBundle>(b, clone_via_reflect);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmarks cloning an an entity and its 50 descendents, each with only 1 component.
|
||||
fn hierarchy_tall(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group(bench!("hierarchy_tall"));
|
||||
|
||||
// We're cloning both the root entity and its 50 descendents.
|
||||
group.throughput(Throughput::Elements(51));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone_hierarchy::<C1>(b, 50, 1, clone_via_reflect);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmarks cloning an an entity and its 50 direct children, each with only 1 component.
|
||||
fn hierarchy_wide(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group(bench!("hierarchy_wide"));
|
||||
|
||||
// We're cloning both the root entity and its 50 direct children.
|
||||
group.throughput(Throughput::Elements(51));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone_hierarchy::<C1>(b, 1, 50, clone_via_reflect);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmarks cloning a large hierarchy of entities with several children each. Each entity has 10
|
||||
/// components.
|
||||
fn hierarchy_many(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group(bench!("hierarchy_many"));
|
||||
|
||||
// We're cloning 364 entities total. This number was calculated by manually counting the number
|
||||
// of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :)
|
||||
group.throughput(Throughput::Elements(364));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone_hierarchy::<ComplexBundle>(b, 5, 3, clone_via_reflect);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
@ -17,9 +17,9 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let mut reader = self.0.get_cursor();
|
||||
let mut reader = self.0.get_reader();
|
||||
for evt in reader.read(&self.0) {
|
||||
core::hint::black_box(evt);
|
||||
std::hint::black_box(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
use criterion::*;
|
||||
|
||||
mod iter;
|
||||
mod send;
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
|
||||
criterion_group!(benches, send, iter);
|
||||
criterion_group!(event_benches, send, iter);
|
||||
|
||||
fn send(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("events_send");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
for count in [100, 1000, 10000, 50000] {
|
||||
group.bench_function(format!("size_4_events_{}", count), |b| {
|
||||
let mut bench = send::Benchmark::<4>::new(count);
|
||||
@ -32,8 +32,8 @@ fn send(c: &mut Criterion) {
|
||||
|
||||
fn iter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("events_iter");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
for count in [100, 1000, 10000, 50000] {
|
||||
group.bench_function(format!("size_4_events_{}", count), |b| {
|
||||
let mut bench = iter::Benchmark::<4>::new(count);
|
||||
|
@ -32,7 +32,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
pub fn run(&mut self) {
|
||||
for _ in 0..self.count {
|
||||
self.events
|
||||
.send(core::hint::black_box(BenchEvent([0u8; SIZE])));
|
||||
.send(std::hint::black_box(BenchEvent([0u8; SIZE])));
|
||||
}
|
||||
self.events.update();
|
||||
}
|
||||
|
@ -1,99 +0,0 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::system::SystemState;
|
||||
use core::hint::black_box;
|
||||
use criterion::*;
|
||||
use glam::*;
|
||||
|
||||
criterion_group!(benches, iter_frag_empty);
|
||||
|
||||
#[derive(Component, Default)]
|
||||
struct Table<const X: usize = 0>(usize);
|
||||
#[derive(Component, Default)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct Sparse<const X: usize = 0>(usize);
|
||||
|
||||
fn flip_coin() -> bool {
|
||||
rand::random::<bool>()
|
||||
}
|
||||
fn iter_frag_empty(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("iter_fragmented(4096)_empty");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
group.bench_function("foreach_table", |b| {
|
||||
let mut world = World::new();
|
||||
spawn_empty_frag_archetype::<Table>(&mut world);
|
||||
let mut q: SystemState<Query<(Entity, &Table)>> =
|
||||
SystemState::<Query<(Entity, &Table<0>)>>::new(&mut world);
|
||||
let query = q.get(&world);
|
||||
b.iter(move || {
|
||||
let mut res = 0;
|
||||
query.iter().for_each(|(e, t)| {
|
||||
res += e.to_bits();
|
||||
black_box(t);
|
||||
});
|
||||
});
|
||||
});
|
||||
group.bench_function("foreach_sparse", |b| {
|
||||
let mut world = World::new();
|
||||
spawn_empty_frag_archetype::<Sparse>(&mut world);
|
||||
let mut q: SystemState<Query<(Entity, &Sparse)>> =
|
||||
SystemState::<Query<(Entity, &Sparse<0>)>>::new(&mut world);
|
||||
let query = q.get(&world);
|
||||
b.iter(move || {
|
||||
let mut res = 0;
|
||||
query.iter().for_each(|(e, t)| {
|
||||
res += e.to_bits();
|
||||
black_box(t);
|
||||
});
|
||||
});
|
||||
});
|
||||
group.finish();
|
||||
|
||||
fn spawn_empty_frag_archetype<T: Component + Default>(world: &mut World) {
|
||||
for i in 0..65536 {
|
||||
let mut e = world.spawn_empty();
|
||||
if flip_coin() {
|
||||
e.insert(Table::<1>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<2>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<3>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<4>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<5>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<6>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<7>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<8>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<9>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<10>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<11>(0));
|
||||
}
|
||||
if flip_coin() {
|
||||
e.insert(Table::<12>(0));
|
||||
}
|
||||
e.insert(T::default());
|
||||
|
||||
if i != 0 {
|
||||
e.despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,8 +17,8 @@ pub fn heavy_compute(c: &mut Criterion) {
|
||||
struct Transform(Mat4);
|
||||
|
||||
let mut group = c.benchmark_group("heavy_compute");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
group.bench_function("base", |b| {
|
||||
ComputeTaskPool::get_or_init(TaskPool::default);
|
||||
|
||||
|
@ -19,15 +19,15 @@ impl<'w> Benchmark<'w> {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
|
||||
world.spawn_batch(core::iter::repeat_n(
|
||||
(
|
||||
world.spawn_batch(
|
||||
std::iter::repeat((
|
||||
Transform(Mat4::from_scale(Vec3::ONE)),
|
||||
Position(Vec3::X),
|
||||
Rotation(Vec3::X),
|
||||
Velocity(Vec3::X),
|
||||
),
|
||||
10_000,
|
||||
));
|
||||
))
|
||||
.take(10_000),
|
||||
);
|
||||
|
||||
let query = world.query::<(&Velocity, &mut Position)>();
|
||||
Self(world, query)
|
||||
|
@ -19,15 +19,15 @@ impl<'w> Benchmark<'w> {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
|
||||
world.spawn_batch(core::iter::repeat_n(
|
||||
(
|
||||
world.spawn_batch(
|
||||
std::iter::repeat((
|
||||
Transform(Mat4::from_scale(Vec3::ONE)),
|
||||
Position(Vec3::X),
|
||||
Rotation(Vec3::X),
|
||||
Velocity(Vec3::X),
|
||||
),
|
||||
10_000,
|
||||
));
|
||||
))
|
||||
.take(10_000),
|
||||
);
|
||||
|
||||
let query = world.query::<(&Velocity, &mut Position)>();
|
||||
Self(world, query)
|
||||
|
@ -1,43 +0,0 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use rand::{prelude::SliceRandom, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct TableData(f32);
|
||||
|
||||
#[derive(Component, Copy, Clone)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct SparseData(f32);
|
||||
|
||||
fn deterministic_rand() -> ChaCha8Rng {
|
||||
ChaCha8Rng::seed_from_u64(42)
|
||||
}
|
||||
pub struct Benchmark<'w>(World, QueryState<(&'w mut TableData, &'w SparseData)>);
|
||||
|
||||
impl<'w> Benchmark<'w> {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut v = vec![];
|
||||
for _ in 0..10000 {
|
||||
world.spawn((TableData(0.0), SparseData(0.0)));
|
||||
v.push(world.spawn(TableData(0.)).id());
|
||||
}
|
||||
|
||||
// by shuffling ,randomize the archetype iteration order to significantly deviate from the table order. This maximizes the loss of cache locality during archetype-based iteration.
|
||||
v.shuffle(&mut deterministic_rand());
|
||||
for e in v.into_iter() {
|
||||
world.entity_mut(e).despawn();
|
||||
}
|
||||
|
||||
let query = world.query::<(&mut TableData, &SparseData)>();
|
||||
Self(world, query)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn run(&mut self) {
|
||||
self.1
|
||||
.iter_mut(&mut self.0)
|
||||
.for_each(|(mut v1, v2)| v1.0 += v2.0);
|
||||
}
|
||||
}
|
@ -21,15 +21,15 @@ impl<'w> Benchmark<'w> {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
|
||||
world.spawn_batch(core::iter::repeat_n(
|
||||
(
|
||||
world.spawn_batch(
|
||||
std::iter::repeat((
|
||||
Transform(Mat4::from_scale(Vec3::ONE)),
|
||||
Position(Vec3::X),
|
||||
Rotation(Vec3::X),
|
||||
Velocity(Vec3::X),
|
||||
),
|
||||
10_000,
|
||||
));
|
||||
))
|
||||
.take(10_000),
|
||||
);
|
||||
|
||||
let query = world.query::<(&Velocity, &mut Position)>();
|
||||
Self(world, query)
|
||||
|
@ -33,8 +33,8 @@ impl<'w> Benchmark<'w> {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
|
||||
world.spawn_batch(core::iter::repeat_n(
|
||||
(
|
||||
world.spawn_batch(
|
||||
std::iter::repeat((
|
||||
Transform(Mat4::from_scale(Vec3::ONE)),
|
||||
Rotation(Vec3::X),
|
||||
Position::<0>(Vec3::X),
|
||||
@ -47,9 +47,9 @@ impl<'w> Benchmark<'w> {
|
||||
Velocity::<3>(Vec3::X),
|
||||
Position::<4>(Vec3::X),
|
||||
Velocity::<4>(Vec3::X),
|
||||
),
|
||||
10_000,
|
||||
));
|
||||
))
|
||||
.take(10_000),
|
||||
);
|
||||
|
||||
let query = world.query();
|
||||
Self(world, query)
|
||||
|