2022-05-16 13:53:20 +00:00
|
|
|
//! This example provides a 2D benchmark.
|
|
|
|
//!
|
|
|
|
//! Usage: spawn more entities by clicking on the screen.
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
use argh::FromArgs;
|
2020-11-13 02:03:57 +00:00
|
|
|
use bevy::{
|
Allow systems using Diagnostics to run in parallel (#8677)
# Objective
I was trying to add some `Diagnostics` to have a better break down of
performance but I noticed that the current implementation uses a
`ResMut` which forces the functions to all run sequentially whereas
before they could run in parallel. This created too great a performance
penalty to be usable.
## Solution
This PR reworks how the diagnostics work with a couple of breaking
changes. The idea is to change how `Diagnostics` works by changing it to
a `SystemParam`. This allows us to hold a `Deferred` buffer of
measurements that can be applied later, avoiding the need for multiple
mutable references to the hashmap. This means we can run systems that
write diagnostic measurements in parallel.
Firstly, we rename the old `Diagnostics` to `DiagnosticsStore`. This
clears up the original name for the new interface while allowing us to
preserve more closely the original API.
Then we create a new `Diagnostics` struct which implements `SystemParam`
and contains a deferred `SystemBuffer`. This can be used very similar to
the old `Diagnostics` for writing new measurements.
```rust
fn system(diagnostics: ResMut<Diagnostics>) { diagnostics.new_measurement(ID, || 10.0)}
// changes to
fn system(mut diagnostics: Diagnostics) { diagnostics.new_measurement(ID, || 10.0)}
```
For reading the diagnostics, the user needs to change from `Diagnostics`
to `DiagnosticsStore` but otherwise the function calls are the same.
Finally, we add a new method to the `App` for registering diagnostics.
This replaces the old method of creating a startup system and adding it
manually.
Testing it, this PR does indeed allow Diagnostic systems to be run in
parallel.
## Changelog
- Change `Diagnostics` to implement `SystemParam` which allows
diagnostic systems to run in parallel.
## Migration Guide
- Register `Diagnostic`'s using the new
`app.register_diagnostic(Diagnostic::new(DIAGNOSTIC_ID,
"diagnostic_name", 10));`
- In systems for writing new measurements, change `mut diagnostics:
ResMut<Diagnostics>` to `mut diagnostics: Diagnostics` to allow the
systems to run in parallel.
- In systems for reading measurements, change `diagnostics:
Res<Diagnostics>` to `diagnostics: Res<DiagnosticsStore>`.
2023-06-05 20:51:22 +00:00
|
|
|
diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
2020-11-13 02:03:57 +00:00
|
|
|
prelude::*,
|
2024-01-03 03:31:04 +00:00
|
|
|
render::{
|
RenderAssetPersistencePolicy → RenderAssetUsages (#11399)
# Objective
Right now, all assets in the main world get extracted and prepared in
the render world (if the asset's using the RenderAssetPlugin). This is
unfortunate for two cases:
1. **TextureAtlas** / **FontAtlas**: This one's huge. The individual
`Image` assets that make up the atlas are cloned and prepared
individually when there's no reason for them to be. The atlas textures
are built on the CPU in the main world. *There can be hundreds of images
that get prepared for rendering only not to be used.*
2. If one loads an Image and needs to transform it in a system before
rendering it, kind of like the [decompression
example](https://github.com/bevyengine/bevy/blob/main/examples/asset/asset_decompression.rs#L120),
there's a price paid for extracting & preparing the asset that's not
intended to be rendered yet.
------
* References #10520
* References #1782
## Solution
This changes the `RenderAssetPersistencePolicy` enum to bitflags. I felt
that the objective with the parameter is so similar in nature to wgpu's
[`TextureUsages`](https://docs.rs/wgpu/latest/wgpu/struct.TextureUsages.html)
and
[`BufferUsages`](https://docs.rs/wgpu/latest/wgpu/struct.BufferUsages.html),
that it may as well be just like that.
```rust
// This asset only needs to be in the main world. Don't extract and prepare it.
RenderAssetUsages::MAIN_WORLD
// Keep this asset in the main world and
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
// This asset is only needed in the render world. Remove it from the asset server once extracted.
RenderAssetUsages::RENDER_WORLD
```
### Alternate Solution
I considered introducing a third field to `RenderAssetPersistencePolicy`
enum:
```rust
enum RenderAssetPersistencePolicy {
/// Keep the asset in the main world after extracting to the render world.
Keep,
/// Remove the asset from the main world after extracting to the render world.
Unload,
/// This doesn't need to be in the render world at all.
NoExtract, // <-----
}
```
Functional, but this seemed like shoehorning. Another option is renaming
the enum to something like:
```rust
enum RenderAssetExtractionPolicy {
/// Extract the asset and keep it in the main world.
Extract,
/// Remove the asset from the main world after extracting to the render world.
ExtractAndUnload,
/// This doesn't need to be in the render world at all.
NoExtract,
}
```
I think this last one could be a good option if the bitflags are too
clunky.
## Migration Guide
* `RenderAssetPersistencePolicy::Keep` → `RenderAssetUsage::MAIN_WORLD |
RenderAssetUsage::RENDER_WORLD` (or `RenderAssetUsage::default()`)
* `RenderAssetPersistencePolicy::Unload` →
`RenderAssetUsage::RENDER_WORLD`
* For types implementing the `RenderAsset` trait, change `fn
persistence_policy(&self) -> RenderAssetPersistencePolicy` to `fn
asset_usage(&self) -> RenderAssetUsages`.
* Change any references to `cpu_persistent_access`
(`RenderAssetPersistencePolicy`) to `asset_usage` (`RenderAssetUsage`).
This applies to `Image`, `Mesh`, and a few other types.
2024-01-30 13:22:10 +00:00
|
|
|
render_asset::RenderAssetUsages,
|
2024-01-03 03:31:04 +00:00
|
|
|
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
|
|
|
},
|
2023-09-02 19:16:44 +00:00
|
|
|
sprite::{MaterialMesh2dBundle, Mesh2dHandle},
|
Unify `FixedTime` and `Time` while fixing several problems (#8964)
# Objective
Current `FixedTime` and `Time` have several problems. This pull aims to
fix many of them at once.
- If there is a longer pause between app updates, time will jump forward
a lot at once and fixed time will iterate on `FixedUpdate` for a large
number of steps. If the pause is merely seconds, then this will just
mean jerkiness and possible unexpected behaviour in gameplay. If the
pause is hours/days as with OS suspend, the game will appear to freeze
until it has caught up with real time.
- If calculating a fixed step takes longer than specified fixed step
period, the game will enter a death spiral where rendering each frame
takes longer and longer due to more and more fixed step updates being
run per frame and the game appears to freeze.
- There is no way to see current fixed step elapsed time inside fixed
steps. In order to track this, the game designer needs to add a custom
system inside `FixedUpdate` that calculates elapsed or step count in a
resource.
- Access to delta time inside fixed step is `FixedStep::period` rather
than `Time::delta`. This, coupled with the issue that `Time::elapsed`
isn't available at all for fixed steps, makes it that time requiring
systems are either implemented to be run in `FixedUpdate` or `Update`,
but rarely work in both.
- Fixes #8800
- Fixes #8543
- Fixes #7439
- Fixes #5692
## Solution
- Create a generic `Time<T>` clock that has no processing logic but
which can be instantiated for multiple usages. This is also exposed for
users to add custom clocks.
- Create three standard clocks, `Time<Real>`, `Time<Virtual>` and
`Time<Fixed>`, all of which contain their individual logic.
- Create one "default" clock, which is just `Time` (or `Time<()>`),
which will be overwritten from `Time<Virtual>` on each update, and
`Time<Fixed>` inside `FixedUpdate` schedule. This way systems that do
not care specifically which time they track can work both in `Update`
and `FixedUpdate` without changes and the behaviour is intuitive.
- Add `max_delta` to virtual time update, which limits how much can be
added to virtual time by a single update. This fixes both the behaviour
after a long freeze, and also the death spiral by limiting how many
fixed timestep iterations there can be per update. Possible future work
could be adding `max_accumulator` to add a sort of "leaky bucket" time
processing to possibly smooth out jumps in time while keeping frame rate
stable.
- Many minor tweaks and clarifications to the time functions and their
documentation.
## Changelog
- `Time::raw_delta()`, `Time::raw_elapsed()` and related methods are
moved to `Time<Real>::delta()` and `Time<Real>::elapsed()` and now match
`Time` API
- `FixedTime` is now `Time<Fixed>` and matches `Time` API.
- `Time<Fixed>` default timestep is now 64 Hz, or 15625 microseconds.
- `Time` inside `FixedUpdate` now reflects fixed timestep time, making
systems portable between `Update ` and `FixedUpdate`.
- `Time::pause()`, `Time::set_relative_speed()` and related methods must
now be called as `Time<Virtual>::pause()` etc.
- There is a new `max_delta` setting in `Time<Virtual>` that limits how
much the clock can jump by a single update. The default value is 0.25
seconds.
- Removed `on_fixed_timer()` condition as `on_timer()` does the right
thing inside `FixedUpdate` now.
## Migration Guide
- Change all `Res<Time>` instances that access `raw_delta()`,
`raw_elapsed()` and related methods to `Res<Time<Real>>` and `delta()`,
`elapsed()`, etc.
- Change access to `period` from `Res<FixedTime>` to `Res<Time<Fixed>>`
and use `delta()`.
- The default timestep has been changed from 60 Hz to 64 Hz. If you wish
to restore the old behaviour, use
`app.insert_resource(Time::<Fixed>::from_hz(60.0))`.
- Change `app.insert_resource(FixedTime::new(duration))` to
`app.insert_resource(Time::<Fixed>::from_duration(duration))`
- Change `app.insert_resource(FixedTime::new_from_secs(secs))` to
`app.insert_resource(Time::<Fixed>::from_seconds(secs))`
- Change `system.on_fixed_timer(duration)` to
`system.on_timer(duration)`. Timers in systems placed in `FixedUpdate`
schedule automatically use the fixed time clock.
- Change `ResMut<Time>` calls to `pause()`, `is_paused()`,
`set_relative_speed()` and related methods to `ResMut<Time<Virtual>>`
calls. The API is the same, with the exception that `relative_speed()`
will return the actual last ste relative speed, while
`effective_relative_speed()` returns 0.0 if the time is paused and
corresponds to the speed that was set when the update for the current
frame started.
## Todo
- [x] Update pull name and description
- [x] Top level documentation on usage
- [x] Fix examples
- [x] Decide on default `max_delta` value
- [x] Decide naming of the three clocks: is `Real`, `Virtual`, `Fixed`
good?
- [x] Decide if the three clock inner structures should be in prelude
- [x] Decide on best way to configure values at startup: is manually
inserting a new clock instance okay, or should there be config struct
separately?
- [x] Fix links in docs
- [x] Decide what should be public and what not
- [x] Decide how `wrap_period` should be handled when it is changed
- [x] ~~Add toggles to disable setting the clock as default?~~ No,
separate pull if needed.
- [x] Add tests
- [x] Reformat, ensure adheres to conventions etc.
- [x] Build documentation and see that it looks correct
## Contributors
Huge thanks to @alice-i-cecile and @maniwani while building this pull.
It was a shared effort!
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com>
Co-authored-by: Jerome Humbert <djeedai@gmail.com>
2023-10-16 01:57:55 +00:00
|
|
|
utils::Duration,
|
2023-01-19 00:38:28 +00:00
|
|
|
window::{PresentMode, WindowResolution},
|
2024-02-01 19:22:47 +00:00
|
|
|
winit::{UpdateMode, WinitSettings},
|
2020-11-13 02:03:57 +00:00
|
|
|
};
|
2023-09-02 19:16:44 +00:00
|
|
|
use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng};
|
2020-11-13 02:03:57 +00:00
|
|
|
|
2021-12-14 03:58:23 +00:00
|
|
|
const BIRDS_PER_SECOND: u32 = 10000;
|
2020-11-13 02:03:57 +00:00
|
|
|
const GRAVITY: f32 = -9.8 * 100.0;
|
|
|
|
const MAX_VELOCITY: f32 = 750.;
|
|
|
|
const BIRD_SCALE: f32 = 0.15;
|
2023-09-02 19:16:44 +00:00
|
|
|
const BIRD_TEXTURE_SIZE: usize = 256;
|
|
|
|
const HALF_BIRD_SIZE: f32 = BIRD_TEXTURE_SIZE as f32 * BIRD_SCALE * 0.5;
|
2021-01-31 21:41:13 +00:00
|
|
|
|
Make `Resource` trait opt-in, requiring `#[derive(Resource)]` V2 (#5577)
*This PR description is an edited copy of #5007, written by @alice-i-cecile.*
# Objective
Follow-up to https://github.com/bevyengine/bevy/pull/2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds.
While ergonomic, this results in several drawbacks:
* it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource
* it is challenging to discover if a type is intended to be used as a resource
* we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component).
* dependencies can use the same Rust type as a resource in invisibly conflicting ways
* raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values
* we cannot capture a definitive list of possible resources to display to users in an editor
## Notes to reviewers
* Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits.
*ira: My commits are not as well organized :')*
* I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does.
* I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with https://github.com/bevyengine/bevy/issues/4981.
## Changelog
`Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro.
## Migration Guide
Add `#[derive(Resource)]` to all types you are using as a resource.
If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics.
`ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing.
Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead.
Co-authored-by: Alice <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: devil-ira <justthecooldude@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-08-08 21:36:35 +00:00
|
|
|
#[derive(Resource)]
|
2020-11-13 02:03:57 +00:00
|
|
|
struct BevyCounter {
|
2022-02-28 01:27:38 +00:00
|
|
|
pub count: usize,
|
2021-12-14 03:58:23 +00:00
|
|
|
pub color: Color,
|
2020-11-13 02:03:57 +00:00
|
|
|
}
|
|
|
|
|
2021-10-03 19:23:44 +00:00
|
|
|
#[derive(Component)]
|
2020-11-13 02:03:57 +00:00
|
|
|
struct Bird {
|
|
|
|
velocity: Vec3,
|
|
|
|
}
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
#[derive(FromArgs, Resource)]
|
|
|
|
/// `bevymark` sprite / 2D mesh stress test
|
|
|
|
struct Args {
|
|
|
|
/// whether to use sprite or mesh2d
|
|
|
|
#[argh(option, default = "Mode::Sprite")]
|
|
|
|
mode: Mode,
|
|
|
|
|
|
|
|
/// whether to step animations by a fixed amount such that each frame is the same across runs.
|
|
|
|
/// If spawning waves, all are spawned up-front to immediately start rendering at the heaviest
|
|
|
|
/// load.
|
|
|
|
#[argh(switch)]
|
|
|
|
benchmark: bool,
|
|
|
|
|
|
|
|
/// how many birds to spawn per wave.
|
|
|
|
#[argh(option, default = "0")]
|
|
|
|
per_wave: usize,
|
|
|
|
|
|
|
|
/// the number of waves to spawn.
|
|
|
|
#[argh(option, default = "0")]
|
|
|
|
waves: usize,
|
|
|
|
|
|
|
|
/// whether to vary the material data in each instance.
|
|
|
|
#[argh(switch)]
|
|
|
|
vary_per_instance: bool,
|
|
|
|
|
|
|
|
/// the number of different textures from which to randomly select the material color. 0 means no textures.
|
|
|
|
#[argh(option, default = "1")]
|
|
|
|
material_texture_count: usize,
|
2023-09-21 17:53:20 +00:00
|
|
|
|
|
|
|
/// generate z values in increasing order rather than randomly
|
|
|
|
#[argh(switch)]
|
|
|
|
ordered_z: bool,
|
2023-09-02 19:16:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Clone)]
|
|
|
|
enum Mode {
|
|
|
|
#[default]
|
|
|
|
Sprite,
|
|
|
|
Mesh2d,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for Mode {
|
|
|
|
type Err = String;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
match s {
|
|
|
|
"sprite" => Ok(Self::Sprite),
|
|
|
|
"mesh2d" => Ok(Self::Mesh2d),
|
|
|
|
_ => Err(format!(
|
|
|
|
"Unknown mode: '{s}', valid modes: 'sprite', 'mesh2d'"
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const FIXED_TIMESTEP: f32 = 0.2;
|
|
|
|
|
2020-11-13 02:03:57 +00:00
|
|
|
fn main() {
|
2024-01-24 21:16:10 +00:00
|
|
|
// `from_env` panics on the web
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2023-09-02 19:16:44 +00:00
|
|
|
let args: Args = argh::from_env();
|
2024-01-24 21:16:10 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
let args = Args::from_args(&[], &[]).unwrap();
|
2023-09-02 19:16:44 +00:00
|
|
|
|
2021-07-27 20:21:06 +00:00
|
|
|
App::new()
|
2023-06-21 20:51:03 +00:00
|
|
|
.add_plugins((
|
|
|
|
DefaultPlugins.set(WindowPlugin {
|
|
|
|
primary_window: Some(Window {
|
|
|
|
title: "BevyMark".into(),
|
Use EntityHashMap<Entity, T> for render world entity storage for better performance (#9903)
# Objective
- Improve rendering performance, particularly by avoiding the large
system commands costs of using the ECS in the way that the render world
does.
## Solution
- Define `EntityHasher` that calculates a hash from the
`Entity.to_bits()` by `i | (i.wrapping_mul(0x517cc1b727220a95) << 32)`.
`0x517cc1b727220a95` is something like `u64::MAX / N` for N that gives a
value close to π and that works well for hashing. Thanks for @SkiFire13
for the suggestion and to @nicopap for alternative suggestions and
discussion. This approach comes from `rustc-hash` (a.k.a. `FxHasher`)
with some tweaks for the case of hashing an `Entity`. `FxHasher` and
`SeaHasher` were also tested but were significantly slower.
- Define `EntityHashMap` type that uses the `EntityHashser`
- Use `EntityHashMap<Entity, T>` for render world entity storage,
including:
- `RenderMaterialInstances` - contains the `AssetId<M>` of the material
associated with the entity. Also for 2D.
- `RenderMeshInstances` - contains mesh transforms, flags and properties
about mesh entities. Also for 2D.
- `SkinIndices` and `MorphIndices` - contains the skin and morph index
for an entity, respectively
- `ExtractedSprites`
- `ExtractedUiNodes`
## Benchmarks
All benchmarks have been conducted on an M1 Max connected to AC power.
The tests are run for 1500 frames. The 1000th frame is captured for
comparison to check for visual regressions. There were none.
### 2D Meshes
`bevymark --benchmark --waves 160 --per-wave 1000 --mode mesh2d`
#### `--ordered-z`
This test spawns the 2D meshes with z incrementing back to front, which
is the ideal arrangement allocation order as it matches the sorted
render order which means lookups have a high cache hit rate.
<img width="1112" alt="Screenshot 2023-09-27 at 07 50 45"
src="https://github.com/bevyengine/bevy/assets/302146/e140bc98-7091-4a3b-8ae1-ab75d16d2ccb">
-39.1% median frame time.
#### Random
This test spawns the 2D meshes with random z. This not only makes the
batching and transparent 2D pass lookups get a lot of cache misses, it
also currently means that the meshes are almost certain to not be
batchable.
<img width="1108" alt="Screenshot 2023-09-27 at 07 51 28"
src="https://github.com/bevyengine/bevy/assets/302146/29c2e813-645a-43ce-982a-55df4bf7d8c4">
-7.2% median frame time.
### 3D Meshes
`many_cubes --benchmark`
<img width="1112" alt="Screenshot 2023-09-27 at 07 51 57"
src="https://github.com/bevyengine/bevy/assets/302146/1a729673-3254-4e2a-9072-55e27c69f0fc">
-7.7% median frame time.
### Sprites
**NOTE: On `main` sprites are using `SparseSet<Entity, T>`!**
`bevymark --benchmark --waves 160 --per-wave 1000 --mode sprite`
#### `--ordered-z`
This test spawns the sprites with z incrementing back to front, which is
the ideal arrangement allocation order as it matches the sorted render
order which means lookups have a high cache hit rate.
<img width="1116" alt="Screenshot 2023-09-27 at 07 52 31"
src="https://github.com/bevyengine/bevy/assets/302146/bc8eab90-e375-4d31-b5cd-f55f6f59ab67">
+13.0% median frame time.
#### Random
This test spawns the sprites with random z. This makes the batching and
transparent 2D pass lookups get a lot of cache misses.
<img width="1109" alt="Screenshot 2023-09-27 at 07 53 01"
src="https://github.com/bevyengine/bevy/assets/302146/22073f5d-99a7-49b0-9584-d3ac3eac3033">
+0.6% median frame time.
### UI
**NOTE: On `main` UI is using `SparseSet<Entity, T>`!**
`many_buttons`
<img width="1111" alt="Screenshot 2023-09-27 at 07 53 26"
src="https://github.com/bevyengine/bevy/assets/302146/66afd56d-cbe4-49e7-8b64-2f28f6043d85">
+15.1% median frame time.
## Alternatives
- Cart originally suggested trying out `SparseSet<Entity, T>` and indeed
that is slightly faster under ideal conditions. However,
`PassHashMap<Entity, T>` has better worst case performance when data is
randomly distributed, rather than in sorted render order, and does not
have the worst case memory usage that `SparseSet`'s dense `Vec<usize>`
that maps from the `Entity` index to sparse index into `Vec<T>`. This
dense `Vec` has to be as large as the largest Entity index used with the
`SparseSet`.
- I also tested `PassHashMap<u32, T>`, intending to use `Entity.index()`
as the key, but this proved to sometimes be slower and mostly no
different.
- The only outstanding approach that has not been implemented and tested
is to _not_ clear the render world of its entities each frame. That has
its own problems, though they could perhaps be solved.
- Performance-wise, if the entities and their component data were not
cleared, then they would incur table moves on spawn, and should not
thereafter, rather just their component data would be overwritten.
Ideally we would have a neat way of either updating data in-place via
`&mut T` queries, or inserting components if not present. This would
likely be quite cumbersome to have to remember to do everywhere, but
perhaps it only needs to be done in the more performance-sensitive
systems.
- The main problem to solve however is that we want to both maintain a
mapping between main world entities and render world entities, be able
to run the render app and world in parallel with the main app and world
for pipelined rendering, and at the same time be able to spawn entities
in the render world in such a way that those Entity ids do not collide
with those spawned in the main world. This is potentially quite
solvable, but could well be a lot of ECS work to do it in a way that
makes sense.
---
## Changelog
- Changed: Component data for entities to be drawn are no longer stored
on entities in the render world. Instead, data is stored in a
`EntityHashMap<Entity, T>` in various resources. This brings significant
performance benefits due to the way the render app clears entities every
frame. Resources of most interest are `RenderMeshInstances` and
`RenderMaterialInstances`, and their 2D counterparts.
## Migration Guide
Previously the render app extracted mesh entities and their component
data from the main world and stored them as entities and components in
the render world. Now they are extracted into essentially
`EntityHashMap<Entity, T>` where `T` are structs containing an
appropriate group of data. This means that while extract set systems
will continue to run extract queries against the main world they will
store their data in hash maps. Also, systems in later sets will either
need to look up entities in the available resources such as
`RenderMeshInstances`, or maintain their own `EntityHashMap<Entity, T>`
for their own data.
Before:
```rust
fn queue_custom(
material_meshes: Query<(Entity, &MeshTransforms, &Handle<Mesh>), With<InstanceMaterialData>>,
) {
...
for (entity, mesh_transforms, mesh_handle) in &material_meshes {
...
}
}
```
After:
```rust
fn queue_custom(
render_mesh_instances: Res<RenderMeshInstances>,
instance_entities: Query<Entity, With<InstanceMaterialData>>,
) {
...
for entity in &instance_entities {
let Some(mesh_instance) = render_mesh_instances.get(&entity) else { continue; };
// The mesh handle in `AssetId<Mesh>` form, and the `MeshTransforms` can now
// be found in `mesh_instance` which is a `RenderMeshInstance`
...
}
}
```
---------
Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com>
2023-09-27 08:28:28 +00:00
|
|
|
resolution: WindowResolution::new(1920.0, 1080.0)
|
|
|
|
.with_scale_factor_override(1.0),
|
2023-06-21 20:51:03 +00:00
|
|
|
present_mode: PresentMode::AutoNoVsync,
|
|
|
|
..default()
|
|
|
|
}),
|
Plugins own their settings. Rework PluginGroup trait. (#6336)
# Objective
Fixes #5884 #2879
Alternative to #2988 #5885 #2886
"Immutable" Plugin settings are currently represented as normal ECS resources, which are read as part of plugin init. This presents a number of problems:
1. If a user inserts the plugin settings resource after the plugin is initialized, it will be silently ignored (and use the defaults instead)
2. Users can modify the plugin settings resource after the plugin has been initialized. This creates a false sense of control over settings that can no longer be changed.
(1) and (2) are especially problematic and confusing for the `WindowDescriptor` resource, but this is a general problem.
## Solution
Immutable Plugin settings now live on each Plugin struct (ex: `WindowPlugin`). PluginGroups have been reworked to support overriding plugin values. This also removes the need for the `add_plugins_with` api, as the `add_plugins` api can use the builder pattern directly. Settings that can be used at runtime continue to be represented as ECS resources.
Plugins are now configured like this:
```rust
app.add_plugin(AssetPlugin {
watch_for_changes: true,
..default()
})
```
PluginGroups are now configured like this:
```rust
app.add_plugins(DefaultPlugins
.set(AssetPlugin {
watch_for_changes: true,
..default()
})
)
```
This is an alternative to #2988, which is similar. But I personally prefer this solution for a couple of reasons:
* ~~#2988 doesn't solve (1)~~ #2988 does solve (1) and will panic in that case. I was wrong!
* This PR directly ties plugin settings to Plugin types in a 1:1 relationship, rather than a loose "setup resource" <-> plugin coupling (where the setup resource is consumed by the first plugin that uses it).
* I'm not a huge fan of overloading the ECS resource concept and implementation for something that has very different use cases and constraints.
## Changelog
- PluginGroups can now be configured directly using the builder pattern. Individual plugin values can be overridden by using `plugin_group.set(SomePlugin {})`, which enables overriding default plugin values.
- `WindowDescriptor` plugin settings have been moved to `WindowPlugin` and `AssetServerSettings` have been moved to `AssetPlugin`
- `app.add_plugins_with` has been replaced by using `add_plugins` with the builder pattern.
## Migration Guide
The `WindowDescriptor` settings have been moved from a resource to `WindowPlugin::window`:
```rust
// Old (Bevy 0.8)
app
.insert_resource(WindowDescriptor {
width: 400.0,
..default()
})
.add_plugins(DefaultPlugins)
// New (Bevy 0.9)
app.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
width: 400.0,
..default()
},
..default()
}))
```
The `AssetServerSettings` resource has been removed in favor of direct `AssetPlugin` configuration:
```rust
// Old (Bevy 0.8)
app
.insert_resource(AssetServerSettings {
watch_for_changes: true,
..default()
})
.add_plugins(DefaultPlugins)
// New (Bevy 0.9)
app.add_plugins(DefaultPlugins.set(AssetPlugin {
watch_for_changes: true,
..default()
}))
```
`add_plugins_with` has been replaced by `add_plugins` in combination with the builder pattern:
```rust
// Old (Bevy 0.8)
app.add_plugins_with(DefaultPlugins, |group| group.disable::<AssetPlugin>());
// New (Bevy 0.9)
app.add_plugins(DefaultPlugins.build().disable::<AssetPlugin>());
```
2022-10-24 21:20:33 +00:00
|
|
|
..default()
|
2023-01-19 00:38:28 +00:00
|
|
|
}),
|
2023-06-21 20:51:03 +00:00
|
|
|
FrameTimeDiagnosticsPlugin,
|
|
|
|
LogDiagnosticsPlugin::default(),
|
|
|
|
))
|
2024-02-01 19:22:47 +00:00
|
|
|
.insert_resource(WinitSettings {
|
|
|
|
focused_mode: UpdateMode::Continuous,
|
|
|
|
unfocused_mode: UpdateMode::Continuous,
|
|
|
|
})
|
2023-09-02 19:16:44 +00:00
|
|
|
.insert_resource(args)
|
2021-12-14 03:58:23 +00:00
|
|
|
.insert_resource(BevyCounter {
|
|
|
|
count: 0,
|
|
|
|
color: Color::WHITE,
|
|
|
|
})
|
2023-03-18 01:45:34 +00:00
|
|
|
.add_systems(Startup, setup)
|
|
|
|
.add_systems(FixedUpdate, scheduled_spawner)
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
(
|
|
|
|
mouse_handler,
|
|
|
|
movement_system,
|
|
|
|
collision_system,
|
|
|
|
counter_system,
|
|
|
|
),
|
|
|
|
)
|
Unify `FixedTime` and `Time` while fixing several problems (#8964)
# Objective
Current `FixedTime` and `Time` have several problems. This pull aims to
fix many of them at once.
- If there is a longer pause between app updates, time will jump forward
a lot at once and fixed time will iterate on `FixedUpdate` for a large
number of steps. If the pause is merely seconds, then this will just
mean jerkiness and possible unexpected behaviour in gameplay. If the
pause is hours/days as with OS suspend, the game will appear to freeze
until it has caught up with real time.
- If calculating a fixed step takes longer than specified fixed step
period, the game will enter a death spiral where rendering each frame
takes longer and longer due to more and more fixed step updates being
run per frame and the game appears to freeze.
- There is no way to see current fixed step elapsed time inside fixed
steps. In order to track this, the game designer needs to add a custom
system inside `FixedUpdate` that calculates elapsed or step count in a
resource.
- Access to delta time inside fixed step is `FixedStep::period` rather
than `Time::delta`. This, coupled with the issue that `Time::elapsed`
isn't available at all for fixed steps, makes it that time requiring
systems are either implemented to be run in `FixedUpdate` or `Update`,
but rarely work in both.
- Fixes #8800
- Fixes #8543
- Fixes #7439
- Fixes #5692
## Solution
- Create a generic `Time<T>` clock that has no processing logic but
which can be instantiated for multiple usages. This is also exposed for
users to add custom clocks.
- Create three standard clocks, `Time<Real>`, `Time<Virtual>` and
`Time<Fixed>`, all of which contain their individual logic.
- Create one "default" clock, which is just `Time` (or `Time<()>`),
which will be overwritten from `Time<Virtual>` on each update, and
`Time<Fixed>` inside `FixedUpdate` schedule. This way systems that do
not care specifically which time they track can work both in `Update`
and `FixedUpdate` without changes and the behaviour is intuitive.
- Add `max_delta` to virtual time update, which limits how much can be
added to virtual time by a single update. This fixes both the behaviour
after a long freeze, and also the death spiral by limiting how many
fixed timestep iterations there can be per update. Possible future work
could be adding `max_accumulator` to add a sort of "leaky bucket" time
processing to possibly smooth out jumps in time while keeping frame rate
stable.
- Many minor tweaks and clarifications to the time functions and their
documentation.
## Changelog
- `Time::raw_delta()`, `Time::raw_elapsed()` and related methods are
moved to `Time<Real>::delta()` and `Time<Real>::elapsed()` and now match
`Time` API
- `FixedTime` is now `Time<Fixed>` and matches `Time` API.
- `Time<Fixed>` default timestep is now 64 Hz, or 15625 microseconds.
- `Time` inside `FixedUpdate` now reflects fixed timestep time, making
systems portable between `Update ` and `FixedUpdate`.
- `Time::pause()`, `Time::set_relative_speed()` and related methods must
now be called as `Time<Virtual>::pause()` etc.
- There is a new `max_delta` setting in `Time<Virtual>` that limits how
much the clock can jump by a single update. The default value is 0.25
seconds.
- Removed `on_fixed_timer()` condition as `on_timer()` does the right
thing inside `FixedUpdate` now.
## Migration Guide
- Change all `Res<Time>` instances that access `raw_delta()`,
`raw_elapsed()` and related methods to `Res<Time<Real>>` and `delta()`,
`elapsed()`, etc.
- Change access to `period` from `Res<FixedTime>` to `Res<Time<Fixed>>`
and use `delta()`.
- The default timestep has been changed from 60 Hz to 64 Hz. If you wish
to restore the old behaviour, use
`app.insert_resource(Time::<Fixed>::from_hz(60.0))`.
- Change `app.insert_resource(FixedTime::new(duration))` to
`app.insert_resource(Time::<Fixed>::from_duration(duration))`
- Change `app.insert_resource(FixedTime::new_from_secs(secs))` to
`app.insert_resource(Time::<Fixed>::from_seconds(secs))`
- Change `system.on_fixed_timer(duration)` to
`system.on_timer(duration)`. Timers in systems placed in `FixedUpdate`
schedule automatically use the fixed time clock.
- Change `ResMut<Time>` calls to `pause()`, `is_paused()`,
`set_relative_speed()` and related methods to `ResMut<Time<Virtual>>`
calls. The API is the same, with the exception that `relative_speed()`
will return the actual last ste relative speed, while
`effective_relative_speed()` returns 0.0 if the time is paused and
corresponds to the speed that was set when the update for the current
frame started.
## Todo
- [x] Update pull name and description
- [x] Top level documentation on usage
- [x] Fix examples
- [x] Decide on default `max_delta` value
- [x] Decide naming of the three clocks: is `Real`, `Virtual`, `Fixed`
good?
- [x] Decide if the three clock inner structures should be in prelude
- [x] Decide on best way to configure values at startup: is manually
inserting a new clock instance okay, or should there be config struct
separately?
- [x] Fix links in docs
- [x] Decide what should be public and what not
- [x] Decide how `wrap_period` should be handled when it is changed
- [x] ~~Add toggles to disable setting the clock as default?~~ No,
separate pull if needed.
- [x] Add tests
- [x] Reformat, ensure adheres to conventions etc.
- [x] Build documentation and see that it looks correct
## Contributors
Huge thanks to @alice-i-cecile and @maniwani while building this pull.
It was a shared effort!
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com>
Co-authored-by: Jerome Humbert <djeedai@gmail.com>
2023-10-16 01:57:55 +00:00
|
|
|
.insert_resource(Time::<Fixed>::from_duration(Duration::from_secs_f32(
|
|
|
|
FIXED_TIMESTEP,
|
|
|
|
)))
|
2020-11-13 02:03:57 +00:00
|
|
|
.run();
|
|
|
|
}
|
|
|
|
|
Make `Resource` trait opt-in, requiring `#[derive(Resource)]` V2 (#5577)
*This PR description is an edited copy of #5007, written by @alice-i-cecile.*
# Objective
Follow-up to https://github.com/bevyengine/bevy/pull/2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds.
While ergonomic, this results in several drawbacks:
* it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource
* it is challenging to discover if a type is intended to be used as a resource
* we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component).
* dependencies can use the same Rust type as a resource in invisibly conflicting ways
* raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values
* we cannot capture a definitive list of possible resources to display to users in an editor
## Notes to reviewers
* Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits.
*ira: My commits are not as well organized :')*
* I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does.
* I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with https://github.com/bevyengine/bevy/issues/4981.
## Changelog
`Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro.
## Migration Guide
Add `#[derive(Resource)]` to all types you are using as a resource.
If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics.
`ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing.
Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead.
Co-authored-by: Alice <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: devil-ira <justthecooldude@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-08-08 21:36:35 +00:00
|
|
|
#[derive(Resource)]
|
2021-12-18 20:13:59 +00:00
|
|
|
struct BirdScheduled {
|
2023-09-02 19:16:44 +00:00
|
|
|
waves: usize,
|
2022-02-28 01:27:38 +00:00
|
|
|
per_wave: usize,
|
2021-12-18 20:13:59 +00:00
|
|
|
}
|
2021-12-14 03:58:23 +00:00
|
|
|
|
2021-12-18 20:13:59 +00:00
|
|
|
fn scheduled_spawner(
|
2021-12-14 03:58:23 +00:00
|
|
|
mut commands: Commands,
|
2023-09-02 19:16:44 +00:00
|
|
|
args: Res<Args>,
|
2023-01-19 00:38:28 +00:00
|
|
|
windows: Query<&Window>,
|
2021-12-18 20:13:59 +00:00
|
|
|
mut scheduled: ResMut<BirdScheduled>,
|
2021-12-14 03:58:23 +00:00
|
|
|
mut counter: ResMut<BevyCounter>,
|
2023-09-02 19:16:44 +00:00
|
|
|
bird_resources: ResMut<BirdResources>,
|
2021-12-14 03:58:23 +00:00
|
|
|
) {
|
2023-01-19 00:38:28 +00:00
|
|
|
let window = windows.single();
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
if scheduled.waves > 0 {
|
|
|
|
let bird_resources = bird_resources.into_inner();
|
2021-12-14 03:58:23 +00:00
|
|
|
spawn_birds(
|
|
|
|
&mut commands,
|
2023-09-02 19:16:44 +00:00
|
|
|
args.into_inner(),
|
2023-01-19 00:38:28 +00:00
|
|
|
&window.resolution,
|
2021-12-14 03:58:23 +00:00
|
|
|
&mut counter,
|
2021-12-18 20:13:59 +00:00
|
|
|
scheduled.per_wave,
|
2023-09-02 19:16:44 +00:00
|
|
|
bird_resources,
|
|
|
|
None,
|
|
|
|
scheduled.waves - 1,
|
2021-12-14 03:58:23 +00:00
|
|
|
);
|
2022-02-28 01:27:38 +00:00
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
scheduled.waves -= 1;
|
2021-12-14 03:58:23 +00:00
|
|
|
}
|
2021-12-18 20:13:59 +00:00
|
|
|
}
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
#[derive(Resource)]
|
|
|
|
struct BirdResources {
|
|
|
|
textures: Vec<Handle<Image>>,
|
|
|
|
materials: Vec<Handle<ColorMaterial>>,
|
|
|
|
quad: Mesh2dHandle,
|
|
|
|
color_rng: StdRng,
|
|
|
|
material_rng: StdRng,
|
|
|
|
velocity_rng: StdRng,
|
2023-09-21 17:53:20 +00:00
|
|
|
transform_rng: StdRng,
|
2023-09-02 19:16:44 +00:00
|
|
|
}
|
2021-12-18 20:13:59 +00:00
|
|
|
|
2022-02-28 01:27:38 +00:00
|
|
|
#[derive(Component)]
|
|
|
|
struct StatsText;
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn setup(
|
|
|
|
mut commands: Commands,
|
|
|
|
args: Res<Args>,
|
|
|
|
asset_server: Res<AssetServer>,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
material_assets: ResMut<Assets<ColorMaterial>>,
|
|
|
|
images: ResMut<Assets<Image>>,
|
|
|
|
windows: Query<&Window>,
|
|
|
|
counter: ResMut<BevyCounter>,
|
|
|
|
) {
|
2022-07-13 19:13:46 +00:00
|
|
|
warn!(include_str!("warning_string.txt"));
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
let args = args.into_inner();
|
|
|
|
let images = images.into_inner();
|
|
|
|
|
|
|
|
let mut textures = Vec::with_capacity(args.material_texture_count.max(1));
|
|
|
|
if matches!(args.mode, Mode::Sprite) || args.material_texture_count > 0 {
|
|
|
|
textures.push(asset_server.load("branding/icon.png"));
|
|
|
|
}
|
|
|
|
init_textures(&mut textures, args, images);
|
|
|
|
|
|
|
|
let material_assets = material_assets.into_inner();
|
|
|
|
let materials = init_materials(args, &textures, material_assets);
|
|
|
|
|
|
|
|
let mut bird_resources = BirdResources {
|
|
|
|
textures,
|
|
|
|
materials,
|
|
|
|
quad: meshes
|
Use `impl Into<A>` for `Assets::add` (#10878)
# Motivation
When spawning entities into a scene, it is very common to create assets
like meshes and materials and to add them via asset handles. A common
setup might look like this:
```rust
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(StandardMaterial::from(Color::RED)),
..default()
});
}
```
Let's take a closer look at the part that adds the assets using `add`.
```rust
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(StandardMaterial::from(Color::RED)),
```
Here, "mesh" and "material" are both repeated three times. It's very
explicit, but I find it to be a bit verbose. In addition to being more
code to read and write, the extra characters can sometimes also lead to
the code being formatted to span multiple lines even though the core
task, adding e.g. a primitive mesh, is extremely simple.
A way to address this is by using `.into()`:
```rust
mesh: meshes.add(shape::Cube { size: 1.0 }.into()),
material: materials.add(Color::RED.into()),
```
This is fine, but from the names and the type of `meshes`, we already
know what the type should be. It's very clear that `Cube` should be
turned into a `Mesh` because of the context it's used in. `.into()` is
just seven characters, but it's so common that it quickly adds up and
gets annoying.
It would be nice if you could skip all of the conversion and let Bevy
handle it for you:
```rust
mesh: meshes.add(shape::Cube { size: 1.0 }),
material: materials.add(Color::RED),
```
# Objective
Make adding assets more ergonomic by making `Assets::add` take an `impl
Into<A>` instead of `A`.
## Solution
`Assets::add` now takes an `impl Into<A>` instead of `A`, so e.g. this
works:
```rust
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Cube { size: 1.0 }),
material: materials.add(Color::RED),
..default()
});
```
I also changed all examples to use this API, which increases consistency
as well because `Mesh::from` and `into` were being used arbitrarily even
in the same file. This also gets rid of some lines of code because
formatting is nicer.
---
## Changelog
- `Assets::add` now takes an `impl Into<A>` instead of `A`
- Examples don't use `T::from(K)` or `K.into()` when adding assets
## Migration Guide
Some `into` calls that worked previously might now be broken because of
the new trait bounds. You need to either remove `into` or perform the
conversion explicitly with `from`:
```rust
// Doesn't compile
let mesh_handle = meshes.add(shape::Cube { size: 1.0 }.into()),
// These compile
let mesh_handle = meshes.add(shape::Cube { size: 1.0 }),
let mesh_handle = meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
```
## Concerns
I believe the primary concerns might be:
1. Is this too implicit?
2. Does this increase codegen bloat?
Previously, the two APIs were using `into` or `from`, and now it's
"nothing" or `from`. You could argue that `into` is slightly more
explicit than "nothing" in cases like the earlier examples where a
`Color` gets converted to e.g. a `StandardMaterial`, but I personally
don't think `into` adds much value even in this case, and you could
still see the actual type from the asset type.
As for codegen bloat, I doubt it adds that much, but I'm not very
familiar with the details of codegen. I personally value the user-facing
code reduction and ergonomics improvements that these changes would
provide, but it might be worth checking the other effects in more
detail.
Another slight concern is migration pain; apps might have a ton of
`into` calls that would need to be removed, and it did take me a while
to do so for Bevy itself (maybe around 20-40 minutes). However, I think
the fact that there *are* so many `into` calls just highlights that the
API could be made nicer, and I'd gladly migrate my own projects for it.
2024-01-08 22:14:43 +00:00
|
|
|
.add(shape::Quad::new(Vec2::splat(BIRD_TEXTURE_SIZE as f32)))
|
2023-09-02 19:16:44 +00:00
|
|
|
.into(),
|
|
|
|
color_rng: StdRng::seed_from_u64(42),
|
|
|
|
material_rng: StdRng::seed_from_u64(42),
|
|
|
|
velocity_rng: StdRng::seed_from_u64(42),
|
2023-09-21 17:53:20 +00:00
|
|
|
transform_rng: StdRng::seed_from_u64(42),
|
2023-09-02 19:16:44 +00:00
|
|
|
};
|
2021-12-18 20:13:59 +00:00
|
|
|
|
2022-10-24 13:46:37 +00:00
|
|
|
let text_section = move |color, value: &str| {
|
|
|
|
TextSection::new(
|
|
|
|
value,
|
|
|
|
TextStyle {
|
|
|
|
font_size: 40.0,
|
|
|
|
color,
|
2023-04-21 22:30:18 +00:00
|
|
|
..default()
|
2022-10-24 13:46:37 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
Spawn now takes a Bundle (#6054)
# Objective
Now that we can consolidate Bundles and Components under a single insert (thanks to #2975 and #6039), almost 100% of world spawns now look like `world.spawn().insert((Some, Tuple, Here))`. Spawning an entity without any components is an extremely uncommon pattern, so it makes sense to give spawn the "first class" ergonomic api. This consolidated api should be made consistent across all spawn apis (such as World and Commands).
## Solution
All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input:
```rust
// before:
commands
.spawn()
.insert((A, B, C));
world
.spawn()
.insert((A, B, C);
// after
commands.spawn((A, B, C));
world.spawn((A, B, C));
```
All existing instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api. A new `spawn_empty` has been added, replacing the old `spawn` api.
By allowing `world.spawn(some_bundle)` to replace `world.spawn().insert(some_bundle)`, this opened the door to removing the initial entity allocation in the "empty" archetype / table done in `spawn()` (and subsequent move to the actual archetype in `.insert(some_bundle)`).
This improves spawn performance by over 10%:
![image](https://user-images.githubusercontent.com/2694663/191627587-4ab2f949-4ccd-4231-80eb-80dd4d9ad6b9.png)
To take this measurement, I added a new `world_spawn` benchmark.
Unfortunately, optimizing `Commands::spawn` is slightly less trivial, as Commands expose the Entity id of spawned entities prior to actually spawning. Doing the optimization would (naively) require assurances that the `spawn(some_bundle)` command is applied before all other commands involving the entity (which would not necessarily be true, if memory serves). Optimizing `Commands::spawn` this way does feel possible, but it will require careful thought (and maybe some additional checks), which deserves its own PR. For now, it has the same performance characteristics of the current `Commands::spawn_bundle` on main.
**Note that 99% of this PR is simple renames and refactors. The only code that needs careful scrutiny is the new `World::spawn()` impl, which is relatively straightforward, but it has some new unsafe code (which re-uses battle tested BundlerSpawner code path).**
---
## Changelog
- All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input
- All instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api
- World and Commands now have `spawn_empty()`, which is equivalent to the old `spawn()` behavior.
## Migration Guide
```rust
// Old (0.8):
commands
.spawn()
.insert_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
commands.spawn_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
let entity = commands.spawn().id();
// New (0.9)
let entity = commands.spawn_empty().id();
// Old (0.8)
let entity = world.spawn().id();
// New (0.9)
let entity = world.spawn_empty();
```
2022-09-23 19:55:54 +00:00
|
|
|
commands.spawn(Camera2dBundle::default());
|
2023-09-02 19:16:44 +00:00
|
|
|
commands
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
style: Style {
|
|
|
|
position_type: PositionType::Absolute,
|
|
|
|
padding: UiRect::all(Val::Px(5.0)),
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
z_index: ZIndex::Global(i32::MAX),
|
|
|
|
background_color: Color::BLACK.with_a(0.75).into(),
|
Spawn now takes a Bundle (#6054)
# Objective
Now that we can consolidate Bundles and Components under a single insert (thanks to #2975 and #6039), almost 100% of world spawns now look like `world.spawn().insert((Some, Tuple, Here))`. Spawning an entity without any components is an extremely uncommon pattern, so it makes sense to give spawn the "first class" ergonomic api. This consolidated api should be made consistent across all spawn apis (such as World and Commands).
## Solution
All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input:
```rust
// before:
commands
.spawn()
.insert((A, B, C));
world
.spawn()
.insert((A, B, C);
// after
commands.spawn((A, B, C));
world.spawn((A, B, C));
```
All existing instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api. A new `spawn_empty` has been added, replacing the old `spawn` api.
By allowing `world.spawn(some_bundle)` to replace `world.spawn().insert(some_bundle)`, this opened the door to removing the initial entity allocation in the "empty" archetype / table done in `spawn()` (and subsequent move to the actual archetype in `.insert(some_bundle)`).
This improves spawn performance by over 10%:
![image](https://user-images.githubusercontent.com/2694663/191627587-4ab2f949-4ccd-4231-80eb-80dd4d9ad6b9.png)
To take this measurement, I added a new `world_spawn` benchmark.
Unfortunately, optimizing `Commands::spawn` is slightly less trivial, as Commands expose the Entity id of spawned entities prior to actually spawning. Doing the optimization would (naively) require assurances that the `spawn(some_bundle)` command is applied before all other commands involving the entity (which would not necessarily be true, if memory serves). Optimizing `Commands::spawn` this way does feel possible, but it will require careful thought (and maybe some additional checks), which deserves its own PR. For now, it has the same performance characteristics of the current `Commands::spawn_bundle` on main.
**Note that 99% of this PR is simple renames and refactors. The only code that needs careful scrutiny is the new `World::spawn()` impl, which is relatively straightforward, but it has some new unsafe code (which re-uses battle tested BundlerSpawner code path).**
---
## Changelog
- All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input
- All instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api
- World and Commands now have `spawn_empty()`, which is equivalent to the old `spawn()` behavior.
## Migration Guide
```rust
// Old (0.8):
commands
.spawn()
.insert_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
commands.spawn_bundle((A, B, C));
// New (0.9)
commands.spawn((A, B, C));
// Old (0.8):
let entity = commands.spawn().id();
// New (0.9)
let entity = commands.spawn_empty().id();
// Old (0.8)
let entity = world.spawn().id();
// New (0.9)
let entity = world.spawn_empty();
```
2022-09-23 19:55:54 +00:00
|
|
|
..default()
|
2023-09-02 19:16:44 +00:00
|
|
|
})
|
|
|
|
.with_children(|c| {
|
|
|
|
c.spawn((
|
|
|
|
TextBundle::from_sections([
|
|
|
|
text_section(Color::GREEN, "Bird Count: "),
|
|
|
|
text_section(Color::CYAN, ""),
|
|
|
|
text_section(Color::GREEN, "\nFPS (raw): "),
|
|
|
|
text_section(Color::CYAN, ""),
|
|
|
|
text_section(Color::GREEN, "\nFPS (SMA): "),
|
|
|
|
text_section(Color::CYAN, ""),
|
|
|
|
text_section(Color::GREEN, "\nFPS (EMA): "),
|
|
|
|
text_section(Color::CYAN, ""),
|
|
|
|
]),
|
|
|
|
StatsText,
|
|
|
|
));
|
|
|
|
});
|
|
|
|
|
|
|
|
let mut scheduled = BirdScheduled {
|
|
|
|
per_wave: args.per_wave,
|
|
|
|
waves: args.waves,
|
|
|
|
};
|
|
|
|
|
|
|
|
if args.benchmark {
|
|
|
|
let counter = counter.into_inner();
|
|
|
|
for wave in (0..scheduled.waves).rev() {
|
|
|
|
spawn_birds(
|
|
|
|
&mut commands,
|
|
|
|
args,
|
|
|
|
&windows.single().resolution,
|
|
|
|
counter,
|
|
|
|
scheduled.per_wave,
|
|
|
|
&mut bird_resources,
|
|
|
|
Some(wave),
|
|
|
|
wave,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
scheduled.waves = 0;
|
|
|
|
}
|
|
|
|
commands.insert_resource(bird_resources);
|
|
|
|
commands.insert_resource(scheduled);
|
2020-11-13 02:03:57 +00:00
|
|
|
}
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2020-11-13 02:03:57 +00:00
|
|
|
fn mouse_handler(
|
Bevy ECS V2 (#1525)
# Bevy ECS V2
This is a rewrite of Bevy ECS (basically everything but the new executor/schedule, which are already awesome). The overall goal was to improve the performance and versatility of Bevy ECS. Here is a quick bulleted list of changes before we dive into the details:
* Complete World rewrite
* Multiple component storage types:
* Tables: fast cache friendly iteration, slower add/removes (previously called Archetypes)
* Sparse Sets: fast add/remove, slower iteration
* Stateful Queries (caches query results for faster iteration. fragmented iteration is _fast_ now)
* Stateful System Params (caches expensive operations. inspired by @DJMcNab's work in #1364)
* Configurable System Params (users can set configuration when they construct their systems. once again inspired by @DJMcNab's work)
* Archetypes are now "just metadata", component storage is separate
* Archetype Graph (for faster archetype changes)
* Component Metadata
* Configure component storage type
* Retrieve information about component size/type/name/layout/send-ness/etc
* Components are uniquely identified by a densely packed ComponentId
* TypeIds are now totally optional (which should make implementing scripting easier)
* Super fast "for_each" query iterators
* Merged Resources into World. Resources are now just a special type of component
* EntityRef/EntityMut builder apis (more efficient and more ergonomic)
* Fast bitset-backed `Access<T>` replaces old hashmap-based approach everywhere
* Query conflicts are determined by component access instead of archetype component access (to avoid random failures at runtime)
* With/Without are still taken into account for conflicts, so this should still be comfy to use
* Much simpler `IntoSystem` impl
* Significantly reduced the amount of hashing throughout the ecs in favor of Sparse Sets (indexed by densely packed ArchetypeId, ComponentId, BundleId, and TableId)
* Safety Improvements
* Entity reservation uses a normal world reference instead of unsafe transmute
* QuerySets no longer transmute lifetimes
* Made traits "unsafe" where relevant
* More thorough safety docs
* WorldCell
* Exposes safe mutable access to multiple resources at a time in a World
* Replaced "catch all" `System::update_archetypes(world: &World)` with `System::new_archetype(archetype: &Archetype)`
* Simpler Bundle implementation
* Replaced slow "remove_bundle_one_by_one" used as fallback for Commands::remove_bundle with fast "remove_bundle_intersection"
* Removed `Mut<T>` query impl. it is better to only support one way: `&mut T`
* Removed with() from `Flags<T>` in favor of `Option<Flags<T>>`, which allows querying for flags to be "filtered" by default
* Components now have is_send property (currently only resources support non-send)
* More granular module organization
* New `RemovedComponents<T>` SystemParam that replaces `query.removed::<T>()`
* `world.resource_scope()` for mutable access to resources and world at the same time
* WorldQuery and QueryFilter traits unified. FilterFetch trait added to enable "short circuit" filtering. Auto impled for cases that don't need it
* Significantly slimmed down SystemState in favor of individual SystemParam state
* System Commands changed from `commands: &mut Commands` back to `mut commands: Commands` (to allow Commands to have a World reference)
Fixes #1320
## `World` Rewrite
This is a from-scratch rewrite of `World` that fills the niche that `hecs` used to. Yes, this means Bevy ECS is no longer a "fork" of hecs. We're going out our own!
(the only shared code between the projects is the entity id allocator, which is already basically ideal)
A huge shout out to @SanderMertens (author of [flecs](https://github.com/SanderMertens/flecs)) for sharing some great ideas with me (specifically hybrid ecs storage and archetype graphs). He also helped advise on a number of implementation details.
## Component Storage (The Problem)
Two ECS storage paradigms have gained a lot of traction over the years:
* **Archetypal ECS**:
* Stores components in "tables" with static schemas. Each "column" stores components of a given type. Each "row" is an entity.
* Each "archetype" has its own table. Adding/removing an entity's component changes the archetype.
* Enables super-fast Query iteration due to its cache-friendly data layout
* Comes at the cost of more expensive add/remove operations for an Entity's components, because all components need to be copied to the new archetype's "table"
* **Sparse Set ECS**:
* Stores components of the same type in densely packed arrays, which are sparsely indexed by densely packed unsigned integers (Entity ids)
* Query iteration is slower than Archetypal ECS because each entity's component could be at any position in the sparse set. This "random access" pattern isn't cache friendly. Additionally, there is an extra layer of indirection because you must first map the entity id to an index in the component array.
* Adding/removing components is a cheap, constant time operation
Bevy ECS V1, hecs, legion, flec, and Unity DOTS are all "archetypal ecs-es". I personally think "archetypal" storage is a good default for game engines. An entity's archetype doesn't need to change frequently in general, and it creates "fast by default" query iteration (which is a much more common operation). It is also "self optimizing". Users don't need to think about optimizing component layouts for iteration performance. It "just works" without any extra boilerplate.
Shipyard and EnTT are "sparse set ecs-es". They employ "packing" as a way to work around the "suboptimal by default" iteration performance for specific sets of components. This helps, but I didn't think this was a good choice for a general purpose engine like Bevy because:
1. "packs" conflict with each other. If bevy decides to internally pack the Transform and GlobalTransform components, users are then blocked if they want to pack some custom component with Transform.
2. users need to take manual action to optimize
Developers selecting an ECS framework are stuck with a hard choice. Select an "archetypal" framework with "fast iteration everywhere" but without the ability to cheaply add/remove components, or select a "sparse set" framework to cheaply add/remove components but with slower iteration performance.
## Hybrid Component Storage (The Solution)
In Bevy ECS V2, we get to have our cake and eat it too. It now has _both_ of the component storage types above (and more can be added later if needed):
* **Tables** (aka "archetypal" storage)
* The default storage. If you don't configure anything, this is what you get
* Fast iteration by default
* Slower add/remove operations
* **Sparse Sets**
* Opt-in
* Slower iteration
* Faster add/remove operations
These storage types complement each other perfectly. By default Query iteration is fast. If developers know that they want to add/remove a component at high frequencies, they can set the storage to "sparse set":
```rust
world.register_component(
ComponentDescriptor::new::<MyComponent>(StorageType::SparseSet)
).unwrap();
```
## Archetypes
Archetypes are now "just metadata" ... they no longer store components directly. They do store:
* The `ComponentId`s of each of the Archetype's components (and that component's storage type)
* Archetypes are uniquely defined by their component layouts
* For example: entities with "table" components `[A, B, C]` _and_ "sparse set" components `[D, E]` will always be in the same archetype.
* The `TableId` associated with the archetype
* For now each archetype has exactly one table (which can have no components),
* There is a 1->Many relationship from Tables->Archetypes. A given table could have any number of archetype components stored in it:
* Ex: an entity with "table storage" components `[A, B, C]` and "sparse set" components `[D, E]` will share the same `[A, B, C]` table as an entity with `[A, B, C]` table component and `[F]` sparse set components.
* This 1->Many relationship is how we preserve fast "cache friendly" iteration performance when possible (more on this later)
* A list of entities that are in the archetype and the row id of the table they are in
* ArchetypeComponentIds
* unique densely packed identifiers for (ArchetypeId, ComponentId) pairs
* used by the schedule executor for cheap system access control
* "Archetype Graph Edges" (see the next section)
## The "Archetype Graph"
Archetype changes in Bevy (and a number of other archetypal ecs-es) have historically been expensive to compute. First, you need to allocate a new vector of the entity's current component ids, add or remove components based on the operation performed, sort it (to ensure it is order-independent), then hash it to find the archetype (if it exists). And thats all before we get to the _already_ expensive full copy of all components to the new table storage.
The solution is to build a "graph" of archetypes to cache these results. @SanderMertens first exposed me to the idea (and he got it from @gjroelofs, who came up with it). They propose adding directed edges between archetypes for add/remove component operations. If `ComponentId`s are densely packed, you can use sparse sets to cheaply jump between archetypes.
Bevy takes this one step further by using add/remove `Bundle` edges instead of `Component` edges. Bevy encourages the use of `Bundles` to group add/remove operations. This is largely for "clearer game logic" reasons, but it also helps cut down on the number of archetype changes required. `Bundles` now also have densely-packed `BundleId`s. This allows us to use a _single_ edge for each bundle operation (rather than needing to traverse N edges ... one for each component). Single component operations are also bundles, so this is strictly an improvement over a "component only" graph.
As a result, an operation that used to be _heavy_ (both for allocations and compute) is now two dirt-cheap array lookups and zero allocations.
## Stateful Queries
World queries are now stateful. This allows us to:
1. Cache archetype (and table) matches
* This resolves another issue with (naive) archetypal ECS: query performance getting worse as the number of archetypes goes up (and fragmentation occurs).
2. Cache Fetch and Filter state
* The expensive parts of fetch/filter operations (such as hashing the TypeId to find the ComponentId) now only happen once when the Query is first constructed
3. Incrementally build up state
* When new archetypes are added, we only process the new archetypes (no need to rebuild state for old archetypes)
As a result, the direct `World` query api now looks like this:
```rust
let mut query = world.query::<(&A, &mut B)>();
for (a, mut b) in query.iter_mut(&mut world) {
}
```
Requiring `World` to generate stateful queries (rather than letting the `QueryState` type be constructed separately) allows us to ensure that _all_ queries are properly initialized (and the relevant world state, such as ComponentIds). This enables QueryState to remove branches from its operations that check for initialization status (and also enables query.iter() to take an immutable world reference because it doesn't need to initialize anything in world).
However in systems, this is a non-breaking change. State management is done internally by the relevant SystemParam.
## Stateful SystemParams
Like Queries, `SystemParams` now also cache state. For example, `Query` system params store the "stateful query" state mentioned above. Commands store their internal `CommandQueue`. This means you can now safely use as many separate `Commands` parameters in your system as you want. `Local<T>` system params store their `T` value in their state (instead of in Resources).
SystemParam state also enabled a significant slim-down of SystemState. It is much nicer to look at now.
Per-SystemParam state naturally insulates us from an "aliased mut" class of errors we have hit in the past (ex: using multiple `Commands` system params).
(credit goes to @DJMcNab for the initial idea and draft pr here #1364)
## Configurable SystemParams
@DJMcNab also had the great idea to make SystemParams configurable. This allows users to provide some initial configuration / values for system parameters (when possible). Most SystemParams have no config (the config type is `()`), but the `Local<T>` param now supports user-provided parameters:
```rust
fn foo(value: Local<usize>) {
}
app.add_system(foo.system().config(|c| c.0 = Some(10)));
```
## Uber Fast "for_each" Query Iterators
Developers now have the choice to use a fast "for_each" iterator, which yields ~1.5-3x iteration speed improvements for "fragmented iteration", and minor ~1.2x iteration speed improvements for unfragmented iteration.
```rust
fn system(query: Query<(&A, &mut B)>) {
// you now have the option to do this for a speed boost
query.for_each_mut(|(a, mut b)| {
});
// however normal iterators are still available
for (a, mut b) in query.iter_mut() {
}
}
```
I think in most cases we should continue to encourage "normal" iterators as they are more flexible and more "rust idiomatic". But when that extra "oomf" is needed, it makes sense to use `for_each`.
We should also consider using `for_each` for internal bevy systems to give our users a nice speed boost (but that should be a separate pr).
## Component Metadata
`World` now has a `Components` collection, which is accessible via `world.components()`. This stores mappings from `ComponentId` to `ComponentInfo`, as well as `TypeId` to `ComponentId` mappings (where relevant). `ComponentInfo` stores information about the component, such as ComponentId, TypeId, memory layout, send-ness (currently limited to resources), and storage type.
## Significantly Cheaper `Access<T>`
We used to use `TypeAccess<TypeId>` to manage read/write component/archetype-component access. This was expensive because TypeIds must be hashed and compared individually. The parallel executor got around this by "condensing" type ids into bitset-backed access types. This worked, but it had to be re-generated from the `TypeAccess<TypeId>`sources every time archetypes changed.
This pr removes TypeAccess in favor of faster bitset access everywhere. We can do this thanks to the move to densely packed `ComponentId`s and `ArchetypeComponentId`s.
## Merged Resources into World
Resources had a lot of redundant functionality with Components. They stored typed data, they had access control, they had unique ids, they were queryable via SystemParams, etc. In fact the _only_ major difference between them was that they were unique (and didn't correlate to an entity).
Separate resources also had the downside of requiring a separate set of access controls, which meant the parallel executor needed to compare more bitsets per system and manage more state.
I initially got the "separate resources" idea from `legion`. I think that design was motivated by the fact that it made the direct world query/resource lifetime interactions more manageable. It certainly made our lives easier when using Resources alongside hecs/bevy_ecs. However we already have a construct for safely and ergonomically managing in-world lifetimes: systems (which use `Access<T>` internally).
This pr merges Resources into World:
```rust
world.insert_resource(1);
world.insert_resource(2.0);
let a = world.get_resource::<i32>().unwrap();
let mut b = world.get_resource_mut::<f64>().unwrap();
*b = 3.0;
```
Resources are now just a special kind of component. They have their own ComponentIds (and their own resource TypeId->ComponentId scope, so they don't conflict wit components of the same type). They are stored in a special "resource archetype", which stores components inside the archetype using a new `unique_components` sparse set (note that this sparse set could later be used to implement Tags). This allows us to keep the code size small by reusing existing datastructures (namely Column, Archetype, ComponentFlags, and ComponentInfo). This allows us the executor to use a single `Access<ArchetypeComponentId>` per system. It should also make scripting language integration easier.
_But_ this merge did create problems for people directly interacting with `World`. What if you need mutable access to multiple resources at the same time? `world.get_resource_mut()` borrows World mutably!
## WorldCell
WorldCell applies the `Access<ArchetypeComponentId>` concept to direct world access:
```rust
let world_cell = world.cell();
let a = world_cell.get_resource_mut::<i32>().unwrap();
let b = world_cell.get_resource_mut::<f64>().unwrap();
```
This adds cheap runtime checks (a sparse set lookup of `ArchetypeComponentId` and a counter) to ensure that world accesses do not conflict with each other. Each operation returns a `WorldBorrow<'w, T>` or `WorldBorrowMut<'w, T>` wrapper type, which will release the relevant ArchetypeComponentId resources when dropped.
World caches the access sparse set (and only one cell can exist at a time), so `world.cell()` is a cheap operation.
WorldCell does _not_ use atomic operations. It is non-send, does a mutable borrow of world to prevent other accesses, and uses a simple `Rc<RefCell<ArchetypeComponentAccess>>` wrapper in each WorldBorrow pointer.
The api is currently limited to resource access, but it can and should be extended to queries / entity component access.
## Resource Scopes
WorldCell does not yet support component queries, and even when it does there are sometimes legitimate reasons to want a mutable world ref _and_ a mutable resource ref (ex: bevy_render and bevy_scene both need this). In these cases we could always drop down to the unsafe `world.get_resource_unchecked_mut()`, but that is not ideal!
Instead developers can use a "resource scope"
```rust
world.resource_scope(|world: &mut World, a: &mut A| {
})
```
This temporarily removes the `A` resource from `World`, provides mutable pointers to both, and re-adds A to World when finished. Thanks to the move to ComponentIds/sparse sets, this is a cheap operation.
If multiple resources are required, scopes can be nested. We could also consider adding a "resource tuple" to the api if this pattern becomes common and the boilerplate gets nasty.
## Query Conflicts Use ComponentId Instead of ArchetypeComponentId
For safety reasons, systems cannot contain queries that conflict with each other without wrapping them in a QuerySet. On bevy `main`, we use ArchetypeComponentIds to determine conflicts. This is nice because it can take into account filters:
```rust
// these queries will never conflict due to their filters
fn filter_system(a: Query<&mut A, With<B>>, b: Query<&mut B, Without<B>>) {
}
```
But it also has a significant downside:
```rust
// these queries will not conflict _until_ an entity with A, B, and C is spawned
fn maybe_conflicts_system(a: Query<(&mut A, &C)>, b: Query<(&mut A, &B)>) {
}
```
The system above will panic at runtime if an entity with A, B, and C is spawned. This makes it hard to trust that your game logic will run without crashing.
In this pr, I switched to using `ComponentId` instead. This _is_ more constraining. `maybe_conflicts_system` will now always fail, but it will do it consistently at startup. Naively, it would also _disallow_ `filter_system`, which would be a significant downgrade in usability. Bevy has a number of internal systems that rely on disjoint queries and I expect it to be a common pattern in userspace.
To resolve this, I added a new `FilteredAccess<T>` type, which wraps `Access<T>` and adds with/without filters. If two `FilteredAccess` have with/without values that prove they are disjoint, they will no longer conflict.
## EntityRef / EntityMut
World entity operations on `main` require that the user passes in an `entity` id to each operation:
```rust
let entity = world.spawn((A, )); // create a new entity with A
world.get::<A>(entity);
world.insert(entity, (B, C));
world.insert_one(entity, D);
```
This means that each operation needs to look up the entity location / verify its validity. The initial spawn operation also requires a Bundle as input. This can be awkward when no components are required (or one component is required).
These operations have been replaced by `EntityRef` and `EntityMut`, which are "builder-style" wrappers around world that provide read and read/write operations on a single, pre-validated entity:
```rust
// spawn now takes no inputs and returns an EntityMut
let entity = world.spawn()
.insert(A) // insert a single component into the entity
.insert_bundle((B, C)) // insert a bundle of components into the entity
.id() // id returns the Entity id
// Returns EntityMut (or panics if the entity does not exist)
world.entity_mut(entity)
.insert(D)
.insert_bundle(SomeBundle::default());
{
// returns EntityRef (or panics if the entity does not exist)
let d = world.entity(entity)
.get::<D>() // gets the D component
.unwrap();
// world.get still exists for ergonomics
let d = world.get::<D>(entity).unwrap();
}
// These variants return Options if you want to check existence instead of panicing
world.get_entity_mut(entity)
.unwrap()
.insert(E);
if let Some(entity_ref) = world.get_entity(entity) {
let d = entity_ref.get::<D>().unwrap();
}
```
This _does not_ affect the current Commands api or terminology. I think that should be a separate conversation as that is a much larger breaking change.
## Safety Improvements
* Entity reservation in Commands uses a normal world borrow instead of an unsafe transmute
* QuerySets no longer transmutes lifetimes
* Made traits "unsafe" when implementing a trait incorrectly could cause unsafety
* More thorough safety docs
## RemovedComponents SystemParam
The old approach to querying removed components: `query.removed:<T>()` was confusing because it had no connection to the query itself. I replaced it with the following, which is both clearer and allows us to cache the ComponentId mapping in the SystemParamState:
```rust
fn system(removed: RemovedComponents<T>) {
for entity in removed.iter() {
}
}
```
## Simpler Bundle implementation
Bundles are no longer responsible for sorting (or deduping) TypeInfo. They are just a simple ordered list of component types / data. This makes the implementation smaller and opens the door to an easy "nested bundle" implementation in the future (which i might even add in this pr). Duplicate detection is now done once per bundle type by World the first time a bundle is used.
## Unified WorldQuery and QueryFilter types
(don't worry they are still separate type _parameters_ in Queries .. this is a non-breaking change)
WorldQuery and QueryFilter were already basically identical apis. With the addition of `FetchState` and more storage-specific fetch methods, the overlap was even clearer (and the redundancy more painful).
QueryFilters are now just `F: WorldQuery where F::Fetch: FilterFetch`. FilterFetch requires `Fetch<Item = bool>` and adds new "short circuit" variants of fetch methods. This enables a filter tuple like `(With<A>, Without<B>, Changed<C>)` to stop evaluating the filter after the first mismatch is encountered. FilterFetch is automatically implemented for `Fetch` implementations that return bool.
This forces fetch implementations that return things like `(bool, bool, bool)` (such as the filter above) to manually implement FilterFetch and decide whether or not to short-circuit.
## More Granular Modules
World no longer globs all of the internal modules together. It now exports `core`, `system`, and `schedule` separately. I'm also considering exporting `core` submodules directly as that is still pretty "glob-ey" and unorganized (feedback welcome here).
## Remaining Draft Work (to be done in this pr)
* ~~panic on conflicting WorldQuery fetches (&A, &mut A)~~
* ~~bevy `main` and hecs both currently allow this, but we should protect against it if possible~~
* ~~batch_iter / par_iter (currently stubbed out)~~
* ~~ChangedRes~~
* ~~I skipped this while we sort out #1313. This pr should be adapted to account for whatever we land on there~~.
* ~~The `Archetypes` and `Tables` collections use hashes of sorted lists of component ids to uniquely identify each archetype/table. This hash is then used as the key in a HashMap to look up the relevant ArchetypeId or TableId. (which doesn't handle hash collisions properly)~~
* ~~It is currently unsafe to generate a Query from "World A", then use it on "World B" (despite the api claiming it is safe). We should probably close this gap. This could be done by adding a randomly generated WorldId to each world, then storing that id in each Query. They could then be compared to each other on each `query.do_thing(&world)` operation. This _does_ add an extra branch to each query operation, so I'm open to other suggestions if people have them.~~
* ~~Nested Bundles (if i find time)~~
## Potential Future Work
* Expand WorldCell to support queries.
* Consider not allocating in the empty archetype on `world.spawn()`
* ex: return something like EntityMutUninit, which turns into EntityMut after an `insert` or `insert_bundle` op
* this actually regressed performance last time i tried it, but in theory it should be faster
* Optimize SparseSet::insert (see `PERF` comment on insert)
* Replace SparseArray `Option<T>` with T::MAX to cut down on branching
* would enable cheaper get_unchecked() operations
* upstream fixedbitset optimizations
* fixedbitset could be allocation free for small block counts (store blocks in a SmallVec)
* fixedbitset could have a const constructor
* Consider implementing Tags (archetype-specific by-value data that affects archetype identity)
* ex: ArchetypeA could have `[A, B, C]` table components and `[D(1)]` "tag" component. ArchetypeB could have `[A, B, C]` table components and a `[D(2)]` tag component. The archetypes are different, despite both having D tags because the value inside D is different.
* this could potentially build on top of the `archetype.unique_components` added in this pr for resource storage.
* Consider reverting `all_tuples` proc macro in favor of the old `macro_rules` implementation
* all_tuples is more flexible and produces cleaner documentation (the macro_rules version produces weird type parameter orders due to parser constraints)
* but unfortunately all_tuples also appears to make Rust Analyzer sad/slow when working inside of `bevy_ecs` (does not affect user code)
* Consider "resource queries" and/or "mixed resource and entity component queries" as an alternative to WorldCell
* this is basically just "systems" so maybe it's not worth it
* Add more world ops
* `world.clear()`
* `world.reserve<T: Bundle>(count: usize)`
* Try using the old archetype allocation strategy (allocate new memory on resize and copy everything over). I expect this to improve batch insertion performance at the cost of unbatched performance. But thats just a guess. I'm not an allocation perf pro :)
* Adapt Commands apis for consistency with new World apis
## Benchmarks
key:
* `bevy_old`: bevy `main` branch
* `bevy`: this branch
* `_foreach`: uses an optimized for_each iterator
* ` _sparse`: uses sparse set storage (if unspecified assume table storage)
* `_system`: runs inside a system (if unspecified assume test happens via direct world ops)
### Simple Insert (from ecs_bench_suite)
![image](https://user-images.githubusercontent.com/2694663/109245573-9c3ce100-7795-11eb-9003-bfd41cd5c51f.png)
### Simpler Iter (from ecs_bench_suite)
![image](https://user-images.githubusercontent.com/2694663/109245795-ffc70e80-7795-11eb-92fb-3ffad09aabf7.png)
### Fragment Iter (from ecs_bench_suite)
![image](https://user-images.githubusercontent.com/2694663/109245849-0fdeee00-7796-11eb-8d25-eb6b7a682c48.png)
### Sparse Fragmented Iter
Iterate a query that matches 5 entities from a single matching archetype, but there are 100 unmatching archetypes
![image](https://user-images.githubusercontent.com/2694663/109245916-2b49f900-7796-11eb-9a8f-ed89c203f940.png)
### Schedule (from ecs_bench_suite)
![image](https://user-images.githubusercontent.com/2694663/109246428-1fab0200-7797-11eb-8841-1b2161e90fa4.png)
### Add Remove Component (from ecs_bench_suite)
![image](https://user-images.githubusercontent.com/2694663/109246492-39e4e000-7797-11eb-8985-2706bd0495ab.png)
### Add Remove Component Big
Same as the test above, but each entity has 5 "large" matrix components and 1 "large" matrix component is added and removed
![image](https://user-images.githubusercontent.com/2694663/109246517-449f7500-7797-11eb-835e-28b6790daeaa.png)
### Get Component
Looks up a single component value a large number of times
![image](https://user-images.githubusercontent.com/2694663/109246129-87ad1880-7796-11eb-9fcb-c38012aa7c70.png)
2021-03-05 07:54:35 +00:00
|
|
|
mut commands: Commands,
|
2023-09-02 19:16:44 +00:00
|
|
|
args: Res<Args>,
|
2020-11-13 02:03:57 +00:00
|
|
|
time: Res<Time>,
|
2023-12-06 20:32:34 +00:00
|
|
|
mouse_button_input: Res<ButtonInput<MouseButton>>,
|
2023-01-19 00:38:28 +00:00
|
|
|
windows: Query<&Window>,
|
2023-09-02 19:16:44 +00:00
|
|
|
bird_resources: ResMut<BirdResources>,
|
2020-11-13 02:03:57 +00:00
|
|
|
mut counter: ResMut<BevyCounter>,
|
2023-09-02 19:16:44 +00:00
|
|
|
mut rng: Local<Option<StdRng>>,
|
|
|
|
mut wave: Local<usize>,
|
2020-11-13 02:03:57 +00:00
|
|
|
) {
|
2023-09-02 19:16:44 +00:00
|
|
|
if rng.is_none() {
|
|
|
|
*rng = Some(StdRng::seed_from_u64(42));
|
|
|
|
}
|
|
|
|
let rng = rng.as_mut().unwrap();
|
2023-01-19 00:38:28 +00:00
|
|
|
let window = windows.single();
|
|
|
|
|
2021-12-14 03:58:23 +00:00
|
|
|
if mouse_button_input.just_released(MouseButton::Left) {
|
2022-02-28 01:27:38 +00:00
|
|
|
counter.color = Color::rgb_linear(rng.gen(), rng.gen(), rng.gen());
|
2021-01-31 21:41:13 +00:00
|
|
|
}
|
|
|
|
|
2020-11-13 02:03:57 +00:00
|
|
|
if mouse_button_input.pressed(MouseButton::Left) {
|
2022-02-28 01:27:38 +00:00
|
|
|
let spawn_count = (BIRDS_PER_SECOND as f64 * time.delta_seconds_f64()) as usize;
|
2021-12-14 03:58:23 +00:00
|
|
|
spawn_birds(
|
|
|
|
&mut commands,
|
2023-09-02 19:16:44 +00:00
|
|
|
args.into_inner(),
|
2023-01-19 00:38:28 +00:00
|
|
|
&window.resolution,
|
2021-12-14 03:58:23 +00:00
|
|
|
&mut counter,
|
|
|
|
spawn_count,
|
2023-09-02 19:16:44 +00:00
|
|
|
bird_resources.into_inner(),
|
|
|
|
None,
|
|
|
|
*wave,
|
2021-12-14 03:58:23 +00:00
|
|
|
);
|
2023-09-02 19:16:44 +00:00
|
|
|
*wave += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn bird_velocity_transform(
|
|
|
|
half_extents: Vec2,
|
|
|
|
mut translation: Vec3,
|
|
|
|
velocity_rng: &mut StdRng,
|
|
|
|
waves: Option<usize>,
|
|
|
|
dt: f32,
|
|
|
|
) -> (Transform, Vec3) {
|
|
|
|
let mut velocity = Vec3::new(MAX_VELOCITY * (velocity_rng.gen::<f32>() - 0.5), 0., 0.);
|
|
|
|
|
|
|
|
if let Some(waves) = waves {
|
|
|
|
// Step the movement and handle collisions as if the wave had been spawned at fixed time intervals
|
|
|
|
// and with dt-spaced frames of simulation
|
|
|
|
for _ in 0..(waves * (FIXED_TIMESTEP / dt).round() as usize) {
|
|
|
|
step_movement(&mut translation, &mut velocity, dt);
|
|
|
|
handle_collision(half_extents, &translation, &mut velocity);
|
|
|
|
}
|
2021-12-14 03:58:23 +00:00
|
|
|
}
|
2023-09-02 19:16:44 +00:00
|
|
|
(
|
|
|
|
Transform::from_translation(translation).with_scale(Vec3::splat(BIRD_SCALE)),
|
|
|
|
velocity,
|
|
|
|
)
|
2021-12-14 03:58:23 +00:00
|
|
|
}
|
2020-11-13 02:03:57 +00:00
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
const FIXED_DELTA_TIME: f32 = 1.0 / 60.0;
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
2021-12-14 03:58:23 +00:00
|
|
|
fn spawn_birds(
|
|
|
|
commands: &mut Commands,
|
2023-09-02 19:16:44 +00:00
|
|
|
args: &Args,
|
2023-01-19 00:38:28 +00:00
|
|
|
primary_window_resolution: &WindowResolution,
|
2021-12-14 03:58:23 +00:00
|
|
|
counter: &mut BevyCounter,
|
2022-02-28 01:27:38 +00:00
|
|
|
spawn_count: usize,
|
2023-09-02 19:16:44 +00:00
|
|
|
bird_resources: &mut BirdResources,
|
|
|
|
waves_to_simulate: Option<usize>,
|
|
|
|
wave: usize,
|
2021-12-14 03:58:23 +00:00
|
|
|
) {
|
2023-01-19 00:38:28 +00:00
|
|
|
let bird_x = (primary_window_resolution.width() / -2.) + HALF_BIRD_SIZE;
|
|
|
|
let bird_y = (primary_window_resolution.height() / 2.) - HALF_BIRD_SIZE;
|
2022-02-28 01:27:38 +00:00
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
let half_extents = 0.5
|
|
|
|
* Vec2::new(
|
|
|
|
primary_window_resolution.width(),
|
|
|
|
primary_window_resolution.height(),
|
|
|
|
);
|
2023-02-19 03:26:11 +00:00
|
|
|
|
|
|
|
let color = counter.color;
|
|
|
|
let current_count = counter.count;
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
match args.mode {
|
|
|
|
Mode::Sprite => {
|
|
|
|
let batch = (0..spawn_count)
|
|
|
|
.map(|count| {
|
2023-09-21 17:53:20 +00:00
|
|
|
let bird_z = if args.ordered_z {
|
|
|
|
(current_count + count) as f32 * 0.00001
|
|
|
|
} else {
|
|
|
|
bird_resources.transform_rng.gen::<f32>()
|
|
|
|
};
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
let (transform, velocity) = bird_velocity_transform(
|
|
|
|
half_extents,
|
|
|
|
Vec3::new(bird_x, bird_y, bird_z),
|
|
|
|
&mut bird_resources.velocity_rng,
|
|
|
|
waves_to_simulate,
|
|
|
|
FIXED_DELTA_TIME,
|
|
|
|
);
|
|
|
|
|
|
|
|
let color = if args.vary_per_instance {
|
|
|
|
Color::rgb_linear(
|
|
|
|
bird_resources.color_rng.gen(),
|
|
|
|
bird_resources.color_rng.gen(),
|
|
|
|
bird_resources.color_rng.gen(),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
color
|
|
|
|
};
|
|
|
|
(
|
|
|
|
SpriteBundle {
|
|
|
|
texture: bird_resources
|
|
|
|
.textures
|
|
|
|
.choose(&mut bird_resources.material_rng)
|
|
|
|
.unwrap()
|
|
|
|
.clone(),
|
|
|
|
transform,
|
|
|
|
sprite: Sprite { color, ..default() },
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
Bird { velocity },
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
commands.spawn_batch(batch);
|
|
|
|
}
|
|
|
|
Mode::Mesh2d => {
|
|
|
|
let batch = (0..spawn_count)
|
|
|
|
.map(|count| {
|
2023-09-21 17:53:20 +00:00
|
|
|
let bird_z = if args.ordered_z {
|
|
|
|
(current_count + count) as f32 * 0.00001
|
|
|
|
} else {
|
|
|
|
bird_resources.transform_rng.gen::<f32>()
|
|
|
|
};
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
let (transform, velocity) = bird_velocity_transform(
|
|
|
|
half_extents,
|
|
|
|
Vec3::new(bird_x, bird_y, bird_z),
|
|
|
|
&mut bird_resources.velocity_rng,
|
|
|
|
waves_to_simulate,
|
|
|
|
FIXED_DELTA_TIME,
|
|
|
|
);
|
|
|
|
|
|
|
|
let material =
|
|
|
|
if args.vary_per_instance || args.material_texture_count > args.waves {
|
|
|
|
bird_resources
|
|
|
|
.materials
|
|
|
|
.choose(&mut bird_resources.material_rng)
|
|
|
|
.unwrap()
|
|
|
|
.clone()
|
|
|
|
} else {
|
|
|
|
bird_resources.materials[wave % bird_resources.materials.len()].clone()
|
|
|
|
};
|
|
|
|
(
|
|
|
|
MaterialMesh2dBundle {
|
|
|
|
mesh: bird_resources.quad.clone(),
|
|
|
|
material,
|
|
|
|
transform,
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
Bird { velocity },
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
commands.spawn_batch(batch);
|
|
|
|
}
|
|
|
|
}
|
2023-02-19 03:26:11 +00:00
|
|
|
|
2021-12-14 03:58:23 +00:00
|
|
|
counter.count += spawn_count;
|
2023-09-02 19:16:44 +00:00
|
|
|
counter.color = Color::rgb_linear(
|
|
|
|
bird_resources.color_rng.gen(),
|
|
|
|
bird_resources.color_rng.gen(),
|
|
|
|
bird_resources.color_rng.gen(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn step_movement(translation: &mut Vec3, velocity: &mut Vec3, dt: f32) {
|
|
|
|
translation.x += velocity.x * dt;
|
|
|
|
translation.y += velocity.y * dt;
|
|
|
|
velocity.y += GRAVITY * dt;
|
2020-11-13 02:03:57 +00:00
|
|
|
}
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
fn movement_system(
|
|
|
|
args: Res<Args>,
|
|
|
|
time: Res<Time>,
|
|
|
|
mut bird_query: Query<(&mut Bird, &mut Transform)>,
|
|
|
|
) {
|
|
|
|
let dt = if args.benchmark {
|
|
|
|
FIXED_DELTA_TIME
|
|
|
|
} else {
|
|
|
|
time.delta_seconds()
|
|
|
|
};
|
2022-07-11 15:28:50 +00:00
|
|
|
for (mut bird, mut transform) in &mut bird_query {
|
2023-09-02 19:16:44 +00:00
|
|
|
step_movement(&mut transform.translation, &mut bird.velocity, dt);
|
2020-11-13 02:03:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
fn handle_collision(half_extents: Vec2, translation: &Vec3, velocity: &mut Vec3) {
|
|
|
|
if (velocity.x > 0. && translation.x + HALF_BIRD_SIZE > half_extents.x)
|
2023-12-24 17:43:01 +00:00
|
|
|
|| (velocity.x <= 0. && translation.x - HALF_BIRD_SIZE < -half_extents.x)
|
2023-09-02 19:16:44 +00:00
|
|
|
{
|
|
|
|
velocity.x = -velocity.x;
|
|
|
|
}
|
|
|
|
let velocity_y = velocity.y;
|
|
|
|
if velocity_y < 0. && translation.y - HALF_BIRD_SIZE < -half_extents.y {
|
|
|
|
velocity.y = -velocity_y;
|
|
|
|
}
|
|
|
|
if translation.y + HALF_BIRD_SIZE > half_extents.y && velocity_y > 0.0 {
|
|
|
|
velocity.y = 0.0;
|
|
|
|
}
|
|
|
|
}
|
2023-01-19 00:38:28 +00:00
|
|
|
fn collision_system(windows: Query<&Window>, mut bird_query: Query<(&mut Bird, &Transform)>) {
|
|
|
|
let window = windows.single();
|
|
|
|
|
2023-09-02 19:16:44 +00:00
|
|
|
let half_extents = 0.5 * Vec2::new(window.width(), window.height());
|
2020-11-13 02:03:57 +00:00
|
|
|
|
2022-07-11 15:28:50 +00:00
|
|
|
for (mut bird, transform) in &mut bird_query {
|
2023-09-02 19:16:44 +00:00
|
|
|
handle_collision(half_extents, &transform.translation, &mut bird.velocity);
|
2020-11-13 02:03:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn counter_system(
|
Allow systems using Diagnostics to run in parallel (#8677)
# Objective
I was trying to add some `Diagnostics` to have a better break down of
performance but I noticed that the current implementation uses a
`ResMut` which forces the functions to all run sequentially whereas
before they could run in parallel. This created too great a performance
penalty to be usable.
## Solution
This PR reworks how the diagnostics work with a couple of breaking
changes. The idea is to change how `Diagnostics` works by changing it to
a `SystemParam`. This allows us to hold a `Deferred` buffer of
measurements that can be applied later, avoiding the need for multiple
mutable references to the hashmap. This means we can run systems that
write diagnostic measurements in parallel.
Firstly, we rename the old `Diagnostics` to `DiagnosticsStore`. This
clears up the original name for the new interface while allowing us to
preserve more closely the original API.
Then we create a new `Diagnostics` struct which implements `SystemParam`
and contains a deferred `SystemBuffer`. This can be used very similar to
the old `Diagnostics` for writing new measurements.
```rust
fn system(diagnostics: ResMut<Diagnostics>) { diagnostics.new_measurement(ID, || 10.0)}
// changes to
fn system(mut diagnostics: Diagnostics) { diagnostics.new_measurement(ID, || 10.0)}
```
For reading the diagnostics, the user needs to change from `Diagnostics`
to `DiagnosticsStore` but otherwise the function calls are the same.
Finally, we add a new method to the `App` for registering diagnostics.
This replaces the old method of creating a startup system and adding it
manually.
Testing it, this PR does indeed allow Diagnostic systems to be run in
parallel.
## Changelog
- Change `Diagnostics` to implement `SystemParam` which allows
diagnostic systems to run in parallel.
## Migration Guide
- Register `Diagnostic`'s using the new
`app.register_diagnostic(Diagnostic::new(DIAGNOSTIC_ID,
"diagnostic_name", 10));`
- In systems for writing new measurements, change `mut diagnostics:
ResMut<Diagnostics>` to `mut diagnostics: Diagnostics` to allow the
systems to run in parallel.
- In systems for reading measurements, change `diagnostics:
Res<Diagnostics>` to `diagnostics: Res<DiagnosticsStore>`.
2023-06-05 20:51:22 +00:00
|
|
|
diagnostics: Res<DiagnosticsStore>,
|
2020-11-13 02:03:57 +00:00
|
|
|
counter: Res<BevyCounter>,
|
2022-02-28 01:27:38 +00:00
|
|
|
mut query: Query<&mut Text, With<StatsText>>,
|
2020-11-13 02:03:57 +00:00
|
|
|
) {
|
2022-02-28 01:27:38 +00:00
|
|
|
let mut text = query.single_mut();
|
|
|
|
|
|
|
|
if counter.is_changed() {
|
2022-07-20 14:14:29 +00:00
|
|
|
text.sections[1].value = counter.count.to_string();
|
2022-02-28 01:27:38 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 15:42:51 +00:00
|
|
|
if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
|
2022-10-24 13:46:37 +00:00
|
|
|
if let Some(raw) = fps.value() {
|
|
|
|
text.sections[3].value = format!("{raw:.2}");
|
|
|
|
}
|
|
|
|
if let Some(sma) = fps.average() {
|
|
|
|
text.sections[5].value = format!("{sma:.2}");
|
|
|
|
}
|
|
|
|
if let Some(ema) = fps.smoothed() {
|
|
|
|
text.sections[7].value = format!("{ema:.2}");
|
2020-11-13 02:03:57 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2023-09-02 19:16:44 +00:00
|
|
|
|
|
|
|
fn init_textures(textures: &mut Vec<Handle<Image>>, args: &Args, images: &mut Assets<Image>) {
|
|
|
|
let mut color_rng = StdRng::seed_from_u64(42);
|
|
|
|
while textures.len() < args.material_texture_count {
|
|
|
|
let pixel = [color_rng.gen(), color_rng.gen(), color_rng.gen(), 255];
|
|
|
|
textures.push(images.add(Image::new_fill(
|
|
|
|
Extent3d {
|
|
|
|
width: BIRD_TEXTURE_SIZE as u32,
|
|
|
|
height: BIRD_TEXTURE_SIZE as u32,
|
|
|
|
depth_or_array_layers: 1,
|
|
|
|
},
|
|
|
|
TextureDimension::D2,
|
|
|
|
&pixel,
|
|
|
|
TextureFormat::Rgba8UnormSrgb,
|
RenderAssetPersistencePolicy → RenderAssetUsages (#11399)
# Objective
Right now, all assets in the main world get extracted and prepared in
the render world (if the asset's using the RenderAssetPlugin). This is
unfortunate for two cases:
1. **TextureAtlas** / **FontAtlas**: This one's huge. The individual
`Image` assets that make up the atlas are cloned and prepared
individually when there's no reason for them to be. The atlas textures
are built on the CPU in the main world. *There can be hundreds of images
that get prepared for rendering only not to be used.*
2. If one loads an Image and needs to transform it in a system before
rendering it, kind of like the [decompression
example](https://github.com/bevyengine/bevy/blob/main/examples/asset/asset_decompression.rs#L120),
there's a price paid for extracting & preparing the asset that's not
intended to be rendered yet.
------
* References #10520
* References #1782
## Solution
This changes the `RenderAssetPersistencePolicy` enum to bitflags. I felt
that the objective with the parameter is so similar in nature to wgpu's
[`TextureUsages`](https://docs.rs/wgpu/latest/wgpu/struct.TextureUsages.html)
and
[`BufferUsages`](https://docs.rs/wgpu/latest/wgpu/struct.BufferUsages.html),
that it may as well be just like that.
```rust
// This asset only needs to be in the main world. Don't extract and prepare it.
RenderAssetUsages::MAIN_WORLD
// Keep this asset in the main world and
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
// This asset is only needed in the render world. Remove it from the asset server once extracted.
RenderAssetUsages::RENDER_WORLD
```
### Alternate Solution
I considered introducing a third field to `RenderAssetPersistencePolicy`
enum:
```rust
enum RenderAssetPersistencePolicy {
/// Keep the asset in the main world after extracting to the render world.
Keep,
/// Remove the asset from the main world after extracting to the render world.
Unload,
/// This doesn't need to be in the render world at all.
NoExtract, // <-----
}
```
Functional, but this seemed like shoehorning. Another option is renaming
the enum to something like:
```rust
enum RenderAssetExtractionPolicy {
/// Extract the asset and keep it in the main world.
Extract,
/// Remove the asset from the main world after extracting to the render world.
ExtractAndUnload,
/// This doesn't need to be in the render world at all.
NoExtract,
}
```
I think this last one could be a good option if the bitflags are too
clunky.
## Migration Guide
* `RenderAssetPersistencePolicy::Keep` → `RenderAssetUsage::MAIN_WORLD |
RenderAssetUsage::RENDER_WORLD` (or `RenderAssetUsage::default()`)
* `RenderAssetPersistencePolicy::Unload` →
`RenderAssetUsage::RENDER_WORLD`
* For types implementing the `RenderAsset` trait, change `fn
persistence_policy(&self) -> RenderAssetPersistencePolicy` to `fn
asset_usage(&self) -> RenderAssetUsages`.
* Change any references to `cpu_persistent_access`
(`RenderAssetPersistencePolicy`) to `asset_usage` (`RenderAssetUsage`).
This applies to `Image`, `Mesh`, and a few other types.
2024-01-30 13:22:10 +00:00
|
|
|
RenderAssetUsages::RENDER_WORLD,
|
2023-09-02 19:16:44 +00:00
|
|
|
)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn init_materials(
|
|
|
|
args: &Args,
|
|
|
|
textures: &[Handle<Image>],
|
|
|
|
assets: &mut Assets<ColorMaterial>,
|
|
|
|
) -> Vec<Handle<ColorMaterial>> {
|
|
|
|
let capacity = if args.vary_per_instance {
|
|
|
|
args.per_wave * args.waves
|
|
|
|
} else {
|
|
|
|
args.material_texture_count.max(args.waves)
|
|
|
|
}
|
|
|
|
.max(1);
|
|
|
|
|
|
|
|
let mut materials = Vec::with_capacity(capacity);
|
|
|
|
materials.push(assets.add(ColorMaterial {
|
|
|
|
color: Color::WHITE,
|
2023-12-02 22:13:42 +00:00
|
|
|
texture: textures.first().cloned(),
|
2023-09-02 19:16:44 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
let mut color_rng = StdRng::seed_from_u64(42);
|
|
|
|
let mut texture_rng = StdRng::seed_from_u64(42);
|
|
|
|
materials.extend(
|
|
|
|
std::iter::repeat_with(|| {
|
|
|
|
assets.add(ColorMaterial {
|
|
|
|
color: Color::rgb_u8(color_rng.gen(), color_rng.gen(), color_rng.gen()),
|
|
|
|
texture: textures.choose(&mut texture_rng).cloned(),
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.take(capacity - materials.len()),
|
|
|
|
);
|
|
|
|
|
|
|
|
materials
|
|
|
|
}
|