mirror of
https://github.com/bevyengine/bevy.git
synced 2026-07-01 08:12:51 -04:00
create-pull-request/patch
245 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9ed66d7a06 |
bsn: throw error when using caching syntax on unsupported scene entries (#24473)
# Objective Implement changes discussed with cart around erroring when using caching syntax where its not implemented. ## Solution Throw a compile error, in a way which is easy to remove once caching exists/works for more cases. Also contains a commit to alleviate trait errors which will (from my experience writing this PR and fixing them in bevy) arise from fixing this for existing bsn code. This commit can be dropped if not desired. This would ideally be fixed upstream so this workaround can be removed: https://github.com/rust-lang/rust/issues/141258#issuecomment-4565897810 Note: The error caused by this workaround is far from ideal. It initially looks like a method call is missing, but ultimately the hint/advice *is* correct <img width="1982" height="477" alt="image" src="https://github.com/user-attachments/assets/879f7630-ada8-4e63-9c59-80be466f37cf" /> ## Testing - `cargo test -p bevy_scene --lib` - `cargo check` --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com> |
||
|
|
9dfc7a6d33 |
Use absolute paths in declarative macros (#24261)
# Objective - Improve declarative macro hygiene. - Fix declarative macros using non-imported items. - `AnimatedField` is unqualified in the `animated_field` macro. - `offset_of` is unqualified in the `impl_atomic_pod` macro. - `format` is unqualified in `hash_error` macro (requires import in `no_std`). ## Solution - Qualify unqualified items and use absolute paths in every public (`#[macro_export]`) `macro_rules`. - Re-export `format` in `bevy_reflect::__macro_exports::alloc_utils`. ## Testing - Ran `cargo run -p ci -- compile` |
||
|
|
281062d418 |
Remove leftover add_remove.rs benchmark file (#24340)
Delete unused duplicate benchmark. Cart's fork of ecs_bench_suite split `add_remove` into table and sparse set versions, but forgot to delete the unused old file. |
||
|
|
81520a73c8 |
bsn: Allow passing EntityTemplate into scene functions and Scene Component @props (#24174)
# Objective Prior PR, see for somewhat outdated description regarding global entity indices: - #24173 Currently, its not possible to pass an entity name reference to a scene function (`-> impl Scene`) or a Scene Component `@props` This PR enables this, based on #24173 by allowing easy passing on EntityTemplate: ```rs #[derive(Component, FromTemplate)] struct Reference(Entity); fn widget(entity: EntityTemplate) -> impl Scene { bsn! { Reference(entity) } } bsn! { #Name Children [ :widget(#Name) ] }; ``` ## Solution I tried this before in https://github.com/bevyengine/bevy/compare/main...laundmo:bevy:bsn-hacky-name-passing but had to compromise so it was obvious it wasn't going to ever be merged. After talking with @cart on discord about this [around here](https://discord.com/channels/691052431525675048/1264881140007702558/1501663053551239209) i went with attempting the global entitiy indices, which resulted in #24173 (all of those changes also being part of this PR) ## Testing - [x] `cargo test -p bevy_scene --lib` with new tests for this feature - [x] `cargo run --example feathers_gallery --features=bevy_feathers` still works the same - [x] new basic benchmark for name resolution doesn't show any major regression --- ## Showcase In a `bsn!` macro, its now possible to pass a `#Name` entity reference into another Scene as an `EntityTemplate`. To use this, write a scene function (or scene component prop) which takes `EntityTemplate`. Components which contain an `Entity` and implement `FromTemplate` can directly take in `EntityTemplate` ```rust #[derive(Component, FromTemplate)] struct FooBar(Entity); fn foo(entity: EntityTemplate) -> impl Scene { bsn! { Reference(entity) } } ``` Now, theres multiple ways to pass in an `EntityTemplate` like this, heres some examples: ```rust bsn! { #SomeName Children [ :foo(#SomeName), foo(#SomeName) foo(#{some_entity_value}) // some_entity_value: any expression evaluating to Entity, most likely a variable :Bar { @myprop: #SomeName } ] } ``` --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com> |
||
|
|
a50b65dfa5 |
Store resources on sparse sets (#24077)
# Objective Part of the #23988 and #24058 saga. We attempt to speed up resource access. ## Solution When messing around with #23988 I noticed that changing the storage type mattered a lot for the benchmarks. ## Testing Added benchmarks from #24058 and got the following micro benchmarks compared to main: ``` ecs::resources::get time: [6.3584 ns 6.3840 ns 6.4097 ns] change: [−10.625% −10.075% −9.6652%] (p = 0.00 < 0.05) Performance has improved. Found 9 outliers among 100 measurements (9.00%) 1 (1.00%) low mild 8 (8.00%) high mild ecs::resources::get_mut time: [7.4181 ns 7.4343 ns 7.4515 ns] change: [−39.895% −39.304% −38.809%] (p = 0.00 < 0.05) Performance has improved. Found 7 outliers among 100 measurements (7.00%) 5 (5.00%) high mild 2 (2.00%) high severe ecs::resources::insert_remove time: [89.515 ns 89.654 ns 89.815 ns] change: [−20.163% −16.527% −11.930%] (p = 0.00 < 0.05) Performance has improved. Found 10 outliers among 100 measurements (10.00%) 2 (2.00%) low severe 1 (1.00%) low mild 3 (3.00%) high mild 4 (4.00%) high severe ``` If someone wants to double-check these numbers, I encourage you to do so. |
||
|
|
aaec9e8d5b |
Add camera primitives benchmark (#23863)
# Objective
- Add performance benchmarks for camera primitives to track the
efficiency of intersection tests.
## Solution
- Created a new benchmark suite `bevy_camera`.
## Testing
- Ran the benchmarks locally using `cargo bench -p benches --bench
camera -- --save-baseline main intersects_obb`.
```
intersects_obb/sphere_intersects_obb
time: [6.5819 ns 6.6022 ns 6.6255 ns]
intersects_obb/frustum_intersects_obb
time: [14.919 ns 14.954 ns 14.991 ns]
intersects_obb/frustum_intersects_obb_fallback_identity
time: [14.940 ns 14.965 ns 14.993 ns]
intersects_obb/frustum_intersects_obb_identity
time: [6.8076 ns 6.8173 ns 6.8276 ns]
```
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
|
||
|
|
738b147b53 |
Add bevy_transform benchmarks (#23906)
# Objective - Add dedicated benchmarks for `bevy_transform`'s propagation systems to catch performance regressions. ## Solution **Benchmarks** (`benches/benches/bevy_transform/propagate.rs`): Added small benchmarks targeting each stage of the propagation pipeline individually. - `mark_dirty_trees` — localized vs. distributed leaf update patterns - `propagate_parent_transforms` — full recompute with roots changed vs. leaves changed (static optimization disabled to isolate traversal cost) - `transform_pipeline` — end-to-end pipeline with static optimization on/off, plus `ChildOf` reparent churn The hierarchy setup uses 48 roots × 6 depth layers with mixed fanout (4-4-3-3-2-2) and extra archetype padding (`MarkerA/B/C`) to reflect realistic gameplay scenes. ## Testing ```sh cargo bench -p benches --bench transform ``` --------- Co-authored-by: ickshonpe <27962798+ickshonpe@users.noreply.github.com> |
||
|
|
6e9522c5af |
Asset Value Templates via asset_value() and HandleTemplate::Value (#23839)
# Objective Temporary simple / suboptimal solution to #23822 It would be very nice to be able to define assets inline in BSN. ## Solution - Add a new `HandleTemplate::Value` variant, which contains an `Arc<Mutex<AssetOrHandle<T>>>` - This template, when first applied, will lock the mutex, insert the asset value as a new asset, cache the handle in the `Arc<Mutex<HandleOrTemplate>` and return the handle. Future calls will lock the mutex and return the cached handle. - Add `asset_value(SOME_ASSET)` function to improve ergonomics. - Port `3d_scene` to illustrate Doing a lock on every spawn is obviously suboptimal. The long term plan for "inline assets in BSN" is defined in #23822 and will not use this locking approach. |
||
|
|
dc70b2e0fd |
Insert ResolvedScenes as dynamic bundles (#23808)
# Objective
Currently BSN inserts each component one-by-one. This is incredibly
expensive, as it forces an archetype move after every insert.
## Solution
Scene templates now write their outputs to reusable bundle scratch space
(which uses a bump allocator). The final bundle is written after all
components (including inherited components) are written to the bundle.
- Introduce a `BundleWriter`, which enables defining a dynamic bundle
using scratch space in a bump allocator. Currently this only supports
writing individual components to the `BundleWriter`, because supporting
arbitrary bundles is much harder (ex: dynamic bundle effects). This
sadly means that we are temporarily constraining both `bsn! { Node }`
and `bsn! { @SomeTemplate }` "template patches" to _require_ a component
output. Custom scene impls can still push arbitrary bundles, which are
inserted before the final dynamic bundle write. In practice I believe
this will cover the relevant use cases in the short term.
- We now skip duplicate insertions of components when spawning inherited
scenes.
- We now write empty RelationshipTarget collections, pre-allocated to
the correct size to the dynamic bundle, which both avoids another
archetype move and ensures we only allocate the inner relationship
target collection once.
- We now write the Relationship component to the dynamic bundle,
avoiding an archetype move
- I added a new "loaded scene inheritance" test variant, just to make
sure that case still works
- `ErasedTemplate` has been moved to `bevy_scene`, as it is now
"opinionated", more specific to `bevy_scene`, and less safe to use in a
general context
<img width="795" height="274" alt="image"
src="https://github.com/user-attachments/assets/68ea02c7-6b7e-4f7c-9474-603188fe6d0b"
/>
These are benchmarks that produce the same UI scene hierarchy through
different means (the benchmarks have been updated to have a few "matrix
wrapper" components to show the cost of archetype moves):
- `immediate_function_scene`: a test of going through the whole "scene
building" process, then spawning. this is the cost of ad-hoc scenes that
don't reuse work from inherited scenes
- `immediate_loaded_scene`: a test where we instantiate a bunch of
inherited scene instances, where the inherited scene has already been
computed / cached.
- `raw_bundle_no_scene`: just spawning the raw bundle directly
|
||
|
|
85212f763e |
Fix CI errors for Rust 1.95 (#23829)
# Objective CI is [broken](https://github.com/bevyengine/bevy/actions/runs/24511176705/job/71642514168?pr=23785) ## Solution Look at the CI errors. Fix them. ## Testing Let's see if CI is green! |
||
|
|
a8a6002064 |
Optimize render asset extraction by reusing heap allocations (#23758)
# Objective
- Address CPU overhead in the render asset extraction phase.
- Prior to this change, `extract_render_asset` and
`extract_erased_render_asset` created several fresh `HashSet` and `Vec`
collections every frame.
- Furthermore, these collections were being sent to the render world via
`Commands::insert_resource`, which causes the previous frame's resource
(and its allocated capacity) to be dropped and deallocated.
- This results in unnecessary pressure on the global allocator and lower
CPU cache efficiency due to constant memory re-initialization.
## Solution
- Converted the temporary `needs_extracting` `HashSet` into a `Local`
system parameter. This allows the collection to retain its capacity
between system runs.
- Switched from using `Commands::insert_resource` to using
`ResMut<ExtractedAssets<A>>`. By calling `.clear()` on the existing
resource instead of replacing it, we reuse the heap-allocated buffers
for the `extracted` Vec and the `removed`, `modified`, and `added`
HashSets.
## Testing
- Introduced a micro-benchmark simulating N unique asset modifications
per frame to trigger the extraction logic.
```
cargo bench -p benches --bench render -- extract_render_asset
```
---
## Showcase
### Criterion Table
```
extract_render_asset/allocations/10
time: [8.6633 µs 8.7004 µs 8.7432 µs]
thrpt: [1.1437 Melem/s 1.1494 Melem/s 1.1543 Melem/s]
change:
time: [−0.3467% +0.2334% +0.8050%] (p = 0.42 > 0.05)
thrpt: [−0.7986% −0.2329% +0.3479%]
No change in performance detected.
extract_render_asset/allocations/100
time: [11.256 µs 11.297 µs 11.340 µs]
thrpt: [8.8183 Melem/s 8.8519 Melem/s 8.8838 Melem/s]
change:
time: [−13.487% −12.672% −11.536%] (p = 0.00 < 0.05)
thrpt: [+13.041% +14.511% +15.590%]
Performance has improved.
extract_render_asset/allocations/1000
time: [34.867 µs 35.037 µs 35.222 µs]
thrpt: [28.392 Melem/s 28.541 Melem/s 28.681 Melem/s]
change:
time: [−42.567% −41.359% −40.454%] (p = 0.00 < 0.05)
thrpt: [+67.938% +70.530% +74.116%]
Performance has improved.
extract_render_asset/allocations/10000
time: [302.54 µs 305.30 µs 308.22 µs]
thrpt: [32.445 Melem/s 32.755 Melem/s 33.053 Melem/s]
change:
time: [−39.411% −37.491% −35.597%] (p = 0.00 < 0.05)
thrpt: [+55.272% +59.976% +65.047%]
Performance has improved.
extract_render_asset/allocations/100000
time: [4.7822 ms 4.9347 ms 5.1028 ms]
thrpt: [19.597 Melem/s 20.265 Melem/s 20.911 Melem/s]
change:
time: [−9.8539% −6.8588% −3.4015%] (p = 0.00 < 0.05)
thrpt: [+3.5212% +7.3639% +10.931%]
Performance has improved.
```
|
||
|
|
9c5508c1ae |
Remove SystemState from more benches (#23705)
# Objective Fixes #23238 I thought there would be more instances to fix :-) ## Solution Same as #23687 ## Testing Ran affected benches and the regression results are a bit flaky -- but seeing that only the ~regression~ bench logic has been changed, this should be OK. ```sh cargo bench -p benches --bench ecs -- iter_fragmented ``` |
||
|
|
0d0fa6ad12 |
Use run_system instead of SystemState for ECS benches (#23687)
# Objective We want to use `World::run_system` where possible - especially in older code that did not have the benefit of using it when it was written. This is a small step towards #23238. More work is needed on the unit tests and possibly elsewhere if this approach has been verified. ## Solution - Instead of `SystemState` I used a closure system and registered it. - I called the registered system with `World::run_system` inside the bench. ## Testing I ran the benches effected and found that they did not regress on my machine. Quick check: ```sh cargo bench -p benches --bench ecs -- 50000_entities_table ``` |
||
|
|
6b0fb37e2c |
Rename bevy_scene2 to bevy_scene (#23668)
# Objective Part 3 of #23619 ## Solution Renames `bevy_scene2` to `bevy_scene` and adds it to the prelude. |
||
|
|
f4d17d9eb2 |
Next Generation Scenes: Core scene system, bsn! macro, Templates (#23413)
After much [iteration](https://github.com/bevyengine/bevy/pull/20158), [designing](https://github.com/bevyengine/bevy/discussions/14437) and [collaborating](https://discord.com/channels/691052431525675048/1264881140007702558), it is finally time to land a baseline featureset of Bevy's Next Generation Scene system, often known by its new scene format name ... BSN (Bevy Scene Notation). This PR adds the following: - **The new scene system**: The core in-memory traits, asset types, and functionality for Bevy's new scene system. Spawn `Scene`s and `SceneList`s. Inherit from other scenes. Patch component fields. Depend on assets before loading as scene. Resolve Entity references throughout your scene. - **The `bsn!` and `bsn_list!` macro**s: Define Bevy scenes in your code using a new ergonomic Rust-ey syntax, which plays nicely with Rust Analyzer and supports autocomplete, go-to definition, semantic highlighting, and doc hover. - **`Template` / `GetTemplate`**: construct types (ex: Components) from a "template context", which includes access to the current entity _and_ access to the `World`. This is a foundational piece of the scene system. Note that this _does not_ include a loader for the BSN asset format, which will be added in a future PR. See the "Whats Next?" section for a roadmap of the future. Part of #23030 ## Review Etiquette This is a big PR. _Please use threaded comments everywhere, not top level comments_. Even if what you have to say is not anchored in code, find a line to leave your comment on. ## Overview This is a reasonably comprehensive conceptual overview / feature list. This uses a "bottom up" approach to illustrate concepts, as they build on each other. If you just want to see what BSN looks like, scroll down a bit! ### Templates `Template` is a simple trait implemented for "template types", which when passed an entity/world context, can produce an output type such as a `Component` or `Bundle`: ```rust pub trait Template { type Output; fn build_template(&mut self, context: &mut TemplateContext) -> Result<Self::Output>; } ``` Template is the cornerstone of the new scene system. It allows us to define types (and hierarchies) that require no `World` context to define, but can _use_ the `World` to produce the final runtime state. Templates are notably: * **Repeatable**: Building a Template does not consume it. This allows us to reuse "baked" scenes / avoid rebuilding scenes each time we want to spawn one. If a Template produces a value this often means some form of cloning is required. * **Clone-able**: Templates can be duplicated via `Template::clone_template`, enabling scenes to be duplicated, supporting copy-on-write behaviors, etc. * **Serializable**: Templates are intended to be easily serialized and deserialized, as they are typically composed of raw data. The poster-child for templates is the asset `Handle<T>`. We now have a `HandleTemplate<T>`, which wraps an `AssetPath`. This can be used to load the requested asset and produce a strong `Handle` for it. ```rust impl<T: Asset> Template for HandleTemplate<T> { type Output = Handle<T>; fn build_template(&mut self, context: &mut TemplateContext) -> Result<Handle<T>> { Ok(context.resource::<AssetServer>().load(&self.path)) } } ``` Types that have a "canonical" `Template` can implement the `GetTemplate` trait, allowing us to correlate to something's `Template` in the type system. ```rust impl<T: Asset> GetTemplate for Handle<T> { type Template = HandleTemplate<T>; } ``` This is where things start to get interesting. `GetTemplate` can be derived for types whose fields also implement `GetTemplate`: ```rust #[derive(Component, GetTemplate)] struct Sprite { image: Handle<Image>, } ``` Internally this produces the following: ```rust #[derive(Template)] struct SpriteTemplate { image: HandleTemplate<Image>, } impl GetTemplate for Sprite { type Template = SpriteTemplate; } ``` Another common use case for templates is `Entity`. With templates we can resolve an identifier of an entity in a scene to the final `Entity` it points to (for example: an entity path or an "entity reference" ... this will be described in detail later). Both `Template` and `GetTemplate` are blanket-implemented for any type that implements both Clone and Default. This means that _most_ types are automatically usable as templates. Neat! ```rust impl<T: Clone + Default> Template for T { type Output = T; fn build_template(&mut self, context: &mut TemplateContext) -> Result<Self::Output> { Ok(self.clone()) } } impl<T: Clone + Default> GetTemplate for T { type Template = T; } ``` It is best to think of `GetTemplate` as an alternative to `Default` for types that require world/spawn context to instantiate. Note that because of the blanket impl, you _cannot_ implement `GetTemplate`, `Default`, and `Clone` together on the same type, as it would result in two conflicting GetTemplate impls. This is also why `Template` has its own `Template::clone_template` method (to avoid using the Clone impl, which would pull in the auto-impl). ### Scenes Templates on their own already check many of the boxes we need for a scene system, but they aren't enough on their own. We want to define scenes as _patches_ of Templates. This allows scenes to inherit from / write on top of other scenes without overwriting fields set in the inherited scene. We want to be able to "resolve" scenes to a final group of templates. This is where the `Scene` trait comes in: ```rust pub trait Scene: Send + Sync + 'static { fn resolve(&self, context: &mut ResolveContext, scene: &mut ResolvedScene) -> Result<(), ResolveSceneError>; fn register_dependencies(&self, _dependencies: &mut Vec<AssetPath<'static>>); } ``` The `ResolvedScene` is a collection of "final" `Template` instances which can be applied to an entity. `Scene::resolve` applies the `Scene` as a "patch" on top of the final `ResolvedScene`. It stores a flat list of templates to be applied to the top-level entity _and_ typed lists of related entities (ex: Children, Observers, etc), which each have their own ResolvedScene. `Scene`s are free to modify these lists, but in most cases they should probably just be pushing to the back of them. `ResolvedScene` can handle both repeated and unique instances of a template of a given type, depending on the context. `Scene::register_dependencies` allows the Scene to register whatever asset dependencies it needs to perform `Scene::resolve`. The scene system will ensure `Scene::resolve` is not called until all of the dependencies have loaded. `Scene` is always _one_ top level / root entity. For "lists of scenes" (such as a list of related entities), we have the `SceneList` trait, which can be used in any place where zero to many scenes are expected. These are separate traits for logical reasons: world.spawn() is a "single entity" action, scene inheritance only makes sense when both scenes are single roots, etc. ### Template Patches The `TemplatePatch` type implements `Scene`, and stores a function that mutates a template. Functionally, a `TemplatePatch` scene will initialize a `Default` value of the patched `Template` if it does not already exist in the `ResolvedScene`, then apply the patch on top of the current Template in the `ResolvedScene`. Types that implement `Template` can generate a `TemplatePatch` like this: ```rust #[derive(Template)] struct MyTemplate { value: usize, } MyTemplate::patch_template(|my_template, context| { my_template.value = 10; }); ``` Likewise, types that implement `GetTemplate` can generate a patch _for their template type_ like this: ```rust #[derive(GetTemplate)] struct Sprite { image: Handle<Image>, } Sprite::patch(|sprite_template| { // note that this is HandleTemplate<Image> sprite.image = "player.png".into(); }) ``` We can now start composing scenes by writing functions that return `impl Scene`! ```rust fn player() -> impl Scene { ( Sprite::patch(|sprite| { sprite.image = "player.png".into(); ), Transform::patch(|transform| { transform.translation.y = 4.0; }), ) } ``` ### The `on()` Observer / event handler Scene `on` is a function that returns a scene that creates an Observer template: ```rust fn player() -> impl Scene { ( Sprite::patch(|sprite| { sprite.image = "player.png".into(); ), on(|jump: On<Jump>| { info!("player jumped!"); }) ) } ``` ### The BSN Format `BSN` is a new specification for defining Bevy Scenes. It is designed to be as Rust-ey as possible, while also eliminating unnecessary syntax and context. The goal is to make defining arbitrary scenes and UIs as easy, delightful, and legible as possible. It is intended to be usable as both an asset format (ex: `level.bsn` files) _and_ defined in code via a `bsn!` macro. These are notably _compatible with each other_. You can define a BSN asset file (ex: in a visual scene editor, such as the upcoming Bevy Editor), then inherit from that and use it in `bsn!` defined in code. ```rust :"player.bsn" Player Sprite { image: "player.png" } Health(10) Transform { translation: Vec3 { y: 4.0 } } on(|jump: On<Jump>| { info!("player jumped!"); }) Children [ ( Hat Sprite { image: "cute_hat.png" } Transform { translation: Vec3 { y: 3.0 } } ) ), (:sword Transform { translation: Vec3 { x: 10. } } ] ``` Note that this PR includes the `bsn!` macro, but it does not include the BSN asset format. It _does_ include all of the in-memory / in-code support for the asset format. All that remains is defining a BSN asset loader, which will be done in a followup. ### The `bsn!` Macro `bsn!` is an _optional_ ergonomic syntax for defining `Scene` expressions. It was built in such a way that Rust Analyzer autocomplete, go-to definition, doc hover, and semantic token syntax highlighting works as expected pretty much everywhere (but there are _some_ gaps and idiosyncrasies at the moment, which I believe we can iron out). It looks like this: ```rust fn player() -> impl Scene { bsn! { Player Sprite { image: "player.png" } Health(10) Transform { translation: Vec3 { y: 4.0 } } on(|jump: On<Jump>| { info!("player jumped!"); }) Children [ ( Hat Sprite { image: "cute_hat.png" } Transform { translation: Vec3 { y: 3.0 } } ) ), (:sword Transform { translation: Vec3 { x: 10. } } ] } } fn sword() -> impl Scene { bsn! { Sword Sprite { image: "sword.png" } } } fn blue_player() -> impl Scene { bsn! { :player Team::Blue Children [ Sprite { image: "blue_shirt.png" } ] } } ``` I'll do a brief overview of each implemented `bsn!` feature now. ### `bsn!`: Patch Syntax When you see a normal "type expression", that resolves to a `TemplatePatch` as defined above. ```rust bsn! { Player { image: "player.png" } } ``` This resolve to the following: ```rust <Player as GetTemplatePatch>::patch(|template| { template.image = "player.png".into(); }) ``` This means you only need to define the fields you actually want to set! Notice the implicit `.into()`. Wherever possible, `bsn!` provides implicit `into()` behavior, which allows developers to skip defining wrapper types, such as the `HandleTemplate<Image>` expected in the example above. This also works for nested struct-style types: ```rust bsn! { Transform { translation: Vec3 { x: 1.0 } } } ``` Note that you can just define the type name if you don't care about setting specific field values / just want to add the component: ```rust bsn! { Transform } ``` To add multiple patches to the entity, just separate them with spaces or newlines: ```rust bsn! { Player Transform } ``` Enum patching is also supported: ```rust #[derive(Component, GetTemplate)] enum Emotion { Happy { amount: usize, quality: HappinessQuality }, Sad(usize), } bsn! { Emotion::Happy { amount: 10. } } ``` Notably, when you derive GetTemplate for an enum, you get default template values for _every_ variant: ```rust // We can skip fields for this variant because they have default values bsn! { Emotion::Happy } // We can also skip fields for this variant bsn! { Emotion::Sad } ``` This means that unlike the `Default` trait, enums that derive `GetTemplate` are "fully patchable". If a patched variant matches the current template variant, it will just write fields on top. If it corresponds to a different variant, it initializes that variant with default values and applies the patch on top. For practical reasons, enums only use this "fully patchable" approach when in "top-level scene entry patch position". _Nested_ enums (aka fields on patches) require specifying _every_ value. This is because the majority of types in the Rust and Bevy ecosystem will not derive `GetTemplate` and therefore will break if we try to create default variants values for them. I think this is the right constraint solve in terms of default behaviors, but we can discuss how to support both nested scenarios effectively. Constructors also work (note that constructor args are _not_ patched. you must specify every argument). A constructor patch will fully overwrite the current value of the Template. ```rust bsn! { Transform::from_xyz(1., 2., 3.) } ``` You can also use type-associated constants, which will also overwrite the current value of the template: ```rust bsn! { Transform::IDENTITY } ``` If you have a type that does not currently implement Template/GetTemplate, you have two options: ```rust bsn! { // This will return a Template that produces the returned type. // `context` has World access! template(|context| { Ok(TextFont { font: context .resource::<AssetServer>() .load("fonts/FiraSans-Bold.ttf").into(), ..default() }) }) // This will return the value as a Template template_value(Foo::Bar) } ``` ### `bsn!` Template patch syntax Types that are expressed using the syntax we learned above are expected to implement `GetTemplate`. If you want to patch a `Template` _directly_ by type name (ex: your Template is not paired with a GetTemplate type), you can do so using `@` syntax: ```rust struct MyTemplate { value: usize, } impl Template for MyTemplate { /* impl here */ } bsn! { @MyTemplate { value: 10. } } ``` In most cases, BSN encourages you to work with the _final_ type names (ex: you type `Sprite`, not `SpriteTemplate`). However in cases where you really want to work with the template type directly (such as custom / manually defined templates), "Template patch syntax" lets you do that! ### `bsn!`: Inline function syntax You can call functions that return `Scene` impls inline. The `on()` function that adds an Observer (described above) is a particularly common use case ```rust bsn! { Player on(|jump: On<Jump>| { info!("Player jumped"); }) } ``` ### `bsn!`: Relationship Syntax `bsn!` provides native support for spawning related entities, in the format `RelationshipTarget [ SCENE_0, ..., SCENE_X ]`: ```rust bsn! { Node { width: Px(10.) } Children [ Node { width: Px(4.0) }, (Node { width: Px(4.0) } BackgroundColor(srgb(1.0, 0.0, 0.0)), ] } ``` Note that related entity scenes are comma separated. Currently they can either be flat _or_ use `()` to group them: ```rust bsn! { Children [ // Child 1 Node BorderRadius::MAX, // Child 2 (Node BorderRadius::MAX), ] } ``` It is generally considered best practice to wrap related entities with more than one entry in `()` to improve legibility. ### `bsn!`: Expression Syntax `bsn!` supports expressions in a number of locations using `{}`: ```rust let x: u32 = 1; let world = "world"; bsn! { // Field position expressions Health({ x + 2 }) Message { text: {format!("hello {world}")} } } ``` Expressions in field position have implicit `into()`. Expressions are also supported in "scene entry" position, enabling nesting `bsn!` inside `bsn!`: ```rust let position = bsn! { Transform { translation: Vec3 { x: 10. } } }; bsn! { Player {position} } ``` ### `bsn!`: Inline variables You can specify variables inline: ```rust let black = Color::BLACK; bsn! { BackgroundColor(black) } ``` This also works in "scene entry" position: ```rust let position = bsn! { Transform { translation: Vec3 { x: 10. } } }; bsn! { Player position } ``` ### Inheritance `bsn!` uses `:` to designate "inheritance". Unlike defining scenes inline (as mentioned above), this will _pre-resolve_ the inherited scene, making your current scene cheaper to spawn. This is great when you inherit from large scene (ex: an asset defined by a visual editor). Scenes can only inherit from one scene at a time, and it must be defined first. You can inherit from scene assets like this: ```rust fn red_button() -> impl Scene { bsn! { :"button.bsn" BackgroundColor(RED) } } ``` Note that while there is currently no implemented `.bsn` asset format, you can still test this using `AssetServer::load_with_path`. You can also inherit from functions that return a `Scene`: ```rust fn button() -> impl Scene { bsn! { Button Children [ Text("Button") ] } } fn red_button() -> impl Scene { bsn! { :button BackgroundColor(RED) } } ``` Note that because inheritance is cached / pre-resolved, function inheritance does not support function parameters. You can still use parameterized scene functions by defining them directly in the scene (rather than using inheritance): ```rust fn button(text: &str) -> impl Scene { bsn! { Button Children [ Text(text) ] } } fn red_button() -> impl Scene { bsn! { button("Click Me") BackgroundColor(RED) } } ``` Related entities can also inherit: ```rust bsn! { Node Children [ (:button BackgroundColor(RED)), (:button BackgroundColor(BLUE)), ] } ``` Inheritance concatenates related entities: ```rust fn a() -> impl Scene { bsn! { Children [ Name("1"), Name("2"), ] } } fn b() -> impl Scene { /// this results in Children [ Name("1"), Name("2"), Name("3") ] bsn! { :a Children [ Name("3"), ] } } ``` ### `bsn_list!` / SceneList Relationship expression syntax `{}` expects a SceneList. Many things, such as `Vec<S: Scene>` implement `SceneList` allowing for some cool patterns: ```rust fn inventory() -> impl Scene { let items = (0..10usize) .map(|i| bsn! {Item { size: {i} }}) .collect::<Vec<_>>(); bsn! { Inventory [ {items} ] } } ``` The `bsn_list!` macro allows defining a list of BSN entries (using the same syntax as relationships). This returns a type that implements `SceneList`, making it useable in relationship expressions! ```rust fn container() -> impl Scene { let children = bsn_list! [ Name("Child1"), Name("Child2"), (Name("Child3") FavoriteChild), ] bsn! { Container [ {children} ] } } ``` This, when combined with inheritance, means you can build abstractions like this: ```rust fn list_widget(children: impl SceneList) -> impl Scene { bsn! { Node { width: Val::Px(1.0) } Children [ Text("My List:") {children} ] } } fn ui() -> impl Scene { bsn! { Node Children [ list_widget({bsn_list! [ Node { width: Px(4.) }, Node { width: Px(5.) }, ]}) ] } } ``` ### `bsn!`: Name Syntax You can quickly define `Name` components using `#Name` shorthand. ```rust bsn! { #Root Node Children [ (#Child1, Node), (#Child2, Node), ] } ``` `#MyName` produces the `Name("MyName")` component output. Within a given `bsn!` or `bsn_list!` scope, `#Name` can _also_ be used in _value position_ as an `Entity` Template: ```rust #[derive(Component, GetTemplate)] struct UiRoot(Entity); #[derive(Component, GetTemplate)] struct CurrentButton(Entity); bsn! { #Root CurrentButton(#MyButton) Children [ ( #MyButton, UiRoot(#Root) ) ] } ``` These behave a bit like variable names. In the context of inheritance and embedded scenes, `#Name` is only valid within the current "scene scope": ```rust fn button() -> impl Scene { bsn! { #Button Node Children [ ButtonRef(#Button) ] } } fn red_button() -> impl Scene { bsn! { :button // #Button is not valid here, but #MyButton // will refer to the same final entity as #Button #MyButton Children [ AnotherReference(#MyButton) ] } } ``` In the example above, because `#MyButton` is defined "last" / is the most "specific" `Name`, the spawned entity will have `Name("MyButton")` Name references are allowed to conflict across inheritance scopes and they will not interfere with each other. `#Name` can also be used in the context of `bsn_list!`, which enables defining graph structures: ```rust bsn_list! [ (#Node1, Sibling(#Node2)), (#Node2, Sibling(#Node1)), ] ``` ### Name Restructure The core name component has also been restructured to play nicer with `bsn!`. The impl on `main` requires `Name::new("MyName")`. By making the name string field public and internalizing the prehash logic on that field, and utilizing implicit `.into()`, we can now define names like this: ```rust bsn! { Name("Root") Children [ Name("Child1"), Name("Child2"), ] } ``` ### BSN Spawning You can spawn scenes using `World::spawn_scene` and `Commands::spawn_scene`: ```rust world.spawn_scene(bsn! { Node Children [ (Node BackgroundColor(RED)) ] })?; commands.spawn_scene(widget()); ``` The `spawn_scene` operation happens _immediately_, and therefore assumes that all of the `Scene`'s dependencies have been loaded (or alternatively, that there are no dependencies). If the scene has a dependency that hasn't been loaded yet, `World::spawn_scene` will return an error (or log an error in the context of `Commands::spawn_scene`). If your scene has dependencies, you can use `World::queue_spawn_scene` and `Commands::queue_spawn_scene`. This will spawn the entity as soon as all of the `Scene`'s dependencies have been loaded. ```rust // This will spawn the entity once the "player.bsn" asset is loaded world.queue_spawn_scene(bsn! { :"player.bsn" Transform { position: Vec3 { x: 10. } } }); ``` There are also `spawn_scene_list` variants for everything above: ```rust world.spawn_scene_list(bsn_list! [ button("Ok"), button("Cancel"), ]) ``` `EntityWorldMut` and `EntityCommands` also have some new functionality: ```rust entity.queue_spawn_related_scene::<Children>(bsn_list! [ (:"player.bsn", #Player1), (:"player.bsn", #Player2), ]); ``` ```rust entity.apply_scene(bsn! { Transform { position: Vec3 { x: 10. } } })?; ``` For scene assets, you can also just add the `ScenePatchInstance(handle)` component, just like the old Bevy scene system. ### VariantDefaults derive `GetTemplate` automatically generates default values for enum Template variants. But for types that don't use `GetTemplate`, I've also implemented a `VariantDefaults` derive that also generates these methods. ## What's Next? ### Must happen before 0.19 - [ ] **Sort out `bevy_scene` vs `bevy_scene2`**: The current plan is to rename `bevy_scene` to `bevy_ecs_serialization`, and remove "scene" terminology from it. That then frees up `bevy_scene2` to be renamed to `bevy_scene`. The current `bevy_scene` will need to exist for awhile in parallel to BSN, as BSN is not yet ready for "full world serialization" scenarios. - [x] ~~**Resolve the Default Handle situation**: Currently, to provide Template support for `Handle`, it implements `GetTemplate`. This of course conflicts with `impl Default for Handle`. This is pretty disruptive to non-BSN users (which is currently everyone). We'll want to sort out a middleground solution in the short term that ideally allows us to keep `impl Default for Handle` during the transition.~~ - Resolved this by using a [specialization trick](https://github.com/bevyengine/bevy/pull/23413#discussion_r2961341173) - [ ] Nested `bsn!` `Scene` tuples to surpass tuple impl limits ### Ideally before 0.19 We likely won't land all of these. The plan is to (ideally) land this PR before Bevy 0.19 RC1, then _maybe_ land a couple more of these before - [ ] **Feathers BSN Port**: Largely already done. Just need to reconcile with current state of main. This will help BSN land well, so landing it alongside BSN is a high priority. - [ ] **ResolvedScene-as-dynamic-bundle**: ResolvedScene should insert all of the components at once as a single bundle, rather than one-by-one, which is really bad from an archetype move perspective. Without this, using `world.spawn_scene(scene)` as a `world.spawn(bundle)` replacement will result in a pretty significant performance reduction. - [ ] **`#Name` references in more places**: The UI eventing scenario _really_ wants `#Name` to be usable in closures. This would functionally be expressed as a template that returns a closure that accesses a specific entity. This unlocks a lot of value for UI devs, so ideally it lands alongside BSN. - [ ] **Top-down vs bottom-up spawn order**: Currently BSN follows the normal bevy top-down spawn order. I think we should heavily consider spawning bottom-up, in the interest of making scene contents available to "higher level" components in their lifecycle events (ex: a `Player` component accessing nested entities like "equipment" when inserted). If we decide to keep things as they are, we probably want to introduce additional "scene ready" entity events that trigger "bottom up". - [ ] **Inline field value expressions**: Support cases such as `px(10).all() - [ ] **Add EntityPath to EntityTemplate**: Support resolving entity paths (ex: `"Root/Child1/GrandChild1"`). This is relatively low hanging fruit, especially if we switch to bottom-up spawning order. - [ ] **Function Inheritance Caching**: Currently only scene asset inheritance is pre-computed / cached. For consistency / predictability / optimizations, function inheritance (ex `:button`) should also be cached. - [ ] **`derive(GetTemplate)` generics ergonomics**: Currently this requires casting spells: `T: GetTemplate<Template: Default + Template<Output = T>>` ### Near Future - [ ] **BSN Asset Format**: Add a `.bsn` parser / AssetLoader that can produce the current `ScenePatch` assets. - [ ] **Struct-style inheritance**: It would be nice to be able to do something like `:Button { prop } ` instead of `:button(prop)`. I'd really like us to explore this being component-tied (ex: associate a scene with a Button component). - [ ] **Descendant Patching**: It should be possible to "reach in" to an inherited scene and patch one of its descendants / children. - [ ] **Optimize Related Entity Spawning**: This currently inserts the relationship component first, then spawns the related scene. This results in an unnecessary archetype move. - [ ] Observers as relationships - [ ] **Scene-owned-entities**: Currently when spawning a `Scene`, every entity defined in the scene is instantiated. Some scenarios would benefit from Scene instances _sharing_ some unique entity. For example: defining assets _inside_ of scenes (this would pair nicely with Assets as Entities) , sharing Observer entities, etc. - [ ] The `touch_type::<Nested>()` approach could be replaced with `let x: &mut Nested` for actual type safety (and probably better autocomplete). - [ ] Fix Rust Analyzer autocomplete bug that fails to resolve functions and enums for `<Transform as GetTemplate>::Template::from_transform()` - [ ] Fix Rust Analyzer autocomplete bug that also suggests function names when type struct field names. This _should_ be fixed by using irrefutable `if let` statements. And it would probably allow us to reuse macro code across enums / structs (and avoid needing to use PathType inference in this case, which has gnarly corner cases). ### Longer Term - [ ] **`bsn!` hot patching via subsecond**: [Proof of concept here](https://github.com/cart/bevy/pull/36) - [ ] **Reactivity**: This has been proven out [here](https://github.com/viridia/bevy_reactor/) - [ ] **BSN Sets**: See the [old design doc](https://github.com/bevyengine/bevy/discussions/14437) for the design space I'm talking about here * This would also allow expressing "flattened" forms of BSN, which makes diffs easier to read in some case - [ ] **World to BSN**: If we can support this, BSN can be used for things like saving Worlds to disk. This might also be useful for building scene editors. --------- Co-authored-by: andriyDev <andriydzikh@gmail.com> Co-authored-by: Nico Zweifel <34443492+NicoZweifel@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: copygirl <copygirl@mcft.net> |
||
|
|
4b09a461e9 |
Simplify Command error handling traits (#23432)
# Objective
I have working `Command` reflection for my game:
```rust
#[derive(Clone)]
pub struct ReflectCommand {
pub apply: fn(&mut World, &dyn PartialReflect, &TypeRegistry),
}
impl ReflectCommand {
pub fn apply(&self, world: &mut World, command: &dyn PartialReflect, registry: &TypeRegistry) {
(self.apply)(world, command, registry);
}
}
impl<C: Command<Result> + Reflect + TypePath> FromType<C> for ReflectCommand {
fn from_type() -> Self {
ReflectCommand {
apply: |world, command, registry| {
let command = from_reflect_with_fallback::<C>(command, world, registry);
command.apply(world);
},
}
}
}
```
However, I am currently only allowed to support a single output type
across *all* command types (which I've chosen to be `Result` for the
time being). This is because, by virtue of `Out` being a generic
parameter, `Command` *can* be implemented multiple times for the same
type, but with different output types. In order for my command
reflection logic to support command types with *any* output type, I need
the ability to guarantee that `Command` will only be implemented once
for some `FooCommand`.
That's why `Out` should be changed into an associated type.
## Solution
- Turned the `Out` generic parameter into an associated type on both
`Command` and `EntityCommand`.
- Bounded `Command::Out` associated type with a new `CommandOutput`
trait.
- This replaces the functionality of the now removed `HandleError`
trait, and allows us to add its functions directly on the `Command`
trait.
- Also bounded `EntityCommand::Out` associated type with the new
`CommandOutput` trait.
- This replaces the functionality of the now removed `CommandWithEntity`
trait, and allows us to add its functions directly on the
`EntityCommand` trait.
Additionally, the new `CommandOutput` trait gives a place for bevy users
to hook into error handling logic with their own types! It also comes
with `diagnostic::on_unimplemented` diagnostics!
## Testing
Maybe TODO: Current tests appear green but should we add tests for the
new `CommandOutput` trait?
|
||
|
|
1aea391609 |
Support Custom SystemExecutors (#23414)
# Objective - Audio wants a realtime-safe executor with minimal checking. This means no command application or multithreading etc. ## Solution - Make it possible to supply our own executor. ## Testing - Tests are updated - New custom executor example Note: reviewing commits individually is probably easier. |
||
|
|
b265dc042a |
Merge SystemParam::validate_param into SystemParam::get_param (#23225)
# Objective As raised in [#23174](https://github.com/bevyengine/bevy/pull/23174#discussion_r2868030355_), we currently duplicate working when looking up our system parameters: once during validation, and then again when actually fetching the data. This is (maybe) slow, and would worsen the performance regression incurred by resources-as-components (#19731). This strategy also imposes some non-trivial complexity and maintainability costs. Because "validate" is a distinct step from "use", it's possible to skip validation! As far as I could tell, this is the case in a number of places before this PR: particularly in the unconventional "please just run my system" path. While in most cases this will simply result in a crash in a different place, it causes these paths to not handle Fixes #23179. Fixes #15505. ## Solution Fundamentally, what we're doing is rolling the `SystemParam::validate_param` behavior into `SystemParam::get_param`, by making the latter return a `Result`. However, there is a tremendous amount of splash damage required to get that to actually compile and expose the correct semantics. The most important of these are: - `SystemState::get` and friends now returns a `Result` - this leads to a fair bit of assorted unwrap spam in our tests and weird internal usages - these tests can probably be refactored to not use `SystemState` directly in the future now that we have better tools like `run_system_once`, but eh, not this PR's job - this is semantically correct, as these params could fail validation - `System::validate_param_unsafe` has been removed, and validation now occurs inside of `System::run_unsafe` - very much a net positive for both abstract robustness and current correctness - this impacts the strategy that various executors use: see the next section There are a *lot* of moving parts here: I'm sorry that I couldn't get this into a smaller, more incremental refactor. When reviewing this PR, you should begin with the migration guide to help get you oriented on the details: `validation_merging.md`. From there, the most important files to review are: 1. `system_param.rs`: trait changes and implementers 2. `function_system.rs`: primary implementer of `System` 3. `multithreaded.rs`: the parallel executor **NOTE TO REVIEWERS:** Please make comments to generate threads; this PR review might get fairly hairy. ### Performance discussion For the parallel `MultithreadedExecutor`, validation was previously done as a cheap pre-validation step, while checking run conditions. Now, tasks will be spawned for systems which would fail or are skipped during validation. In most cases, avoiding the extra overhead of looking up the required data twice should dominate. However, this change may negatively affect systems which are frequently skipped (e.g. due to `Single`). ### Paths not taken In this PR, I've decided not to: - Add another variant [RunSystemError](https://docs.rs/bevy/latest/bevy/ecs/system/enum.RunSystemError.html), distinguishing "validation failed" from "system ran but returned an error". - While reusing [RunSystemError::Failed](https://docs.rs/bevy/latest/bevy/ecs/system/enum.RunSystemError.html#variant.Failed) for both cases is messy, this PR is already a bit of a nightmare to review. - Return a result from `ParamSet::get_mut`. - Instead, we just `unwrap`. - Bubbling up the `Result` is technically more correct, but these were already panicking before if e.g. a resource is missing, and `ParamSet` is already an ergonomic abomination. ## Testing I've added a number of new tests to exercise the system param validation paths, ensuring that validation is done when systems are run. However, I would appreciate some help benchmarking the net impact on realistic-ish scenes. `breakout`, `bevy_city` and `many_animated_foxes` are probably a decent scattering, but I'd be very open to other suggestions. Having done this refactor, I think that it's a net improvement for robustness and clarity even without the perf benefits however, and that we should proceed unless this is a clear regression. --------- Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com> |
||
|
|
f255b8e57a |
Upgrade glam, hexasphere, rand & uuid to latest versions (#22928)
# Objective - `glam`, `hexasphere` & `rand` have released their latest versions, update Bevy to support them. ## Solution - The above have been updated to their compatible versions. `rand_distr` updated as well to match `rand` v0.10 support. - `rand_chacha` is soft deprecated and no longer used by `rand`, so its usage has been changed to `chacha20` to match `rand` dep tree. - `uuid` is in the process of updating to `getrandom` v0.4, which `rand` v0.10 supports. This PR remains in draft until a new `uuid` release hits crates.io. - `RngCore` is now `Rng`, and `Rng` is now `RngExt`, so this required updating across many files. - `choose_multiple` method is deprecated, changed to `sample`. ## Testing - Chase all compiler errors, since this should not regress any already existing behaviour. - This must pass CI without regressions. ## Additional Notes `getrandom` v0.4 doesn't add anything new for Web WASM support, so the same `wasm_js` feature is used. |
||
|
|
4cc92a00e7 |
Amortize the cost of freeing entities (#22658)
# Objective The biggest drawback of #18670 was that it made freeing `Entity`'s back to the allocator 4x slower. That meant a 20% regression in despawn performance. This PR vastly improves the performance of the entity allocator for freeing entities. ## Solution Add a local free list in pace in the main entity allocator. This is an `ArrayVec` called `quick_free`. When an entity is freed, add it to the `quick_free`. If it is full, flush the array to the full shared allocator. Currently the array has length 64, taking 512 bytes. Since this is directly included in the already massive `World` type, I don't think this is an issue, and I would guess boxing it would hurt performance here. It also means that there will be at most 64 freed entities that simply can't be allocated. This reduces the worst case maximum entity count from 4,294,967,296 to 4,294,967,232 (big deal). This also adds a new `free_many` function that is very fast compared to doing them one by one. ## Testing - CI and benches. --- ## Showcase Here are some rough benchmarks on my M2 MAX: ```txt group post_quick_free_list pre_quick_free_list pre_remote_reservation ----- -------------------- ------------------- ---------------------- entity_allocator_free/10000_entities 1.00 29.7±0.48µs ? ?/sec 1.31 38.9±0.97µs ? ?/sec 1.00 29.8±0.85µs ? ?/sec entity_allocator_free/100_entities 1.00 393.3±26.21ns ? ?/sec 1.35 531.8±26.34ns ? ?/sec 1.14 446.7±11.32ns ? ?/sec entity_allocator_free/1_entities 1.00 4.6±2.17ns ? ?/sec 42.27 195.3±32.49ns ? ?/sec 4.25 19.6±8.67ns ? ?/sec entity_allocator_free_bulk/10000_entities 1.00 8.7±0.36µs ? ?/sec entity_allocator_free_bulk/100_entities 1.00 240.9±31.01ns ? ?/sec entity_allocator_free_bulk/1_entities 1.00 206.8±39.95ns ? ?/sec ``` Looking at the cost of freeing 1,000 entities, this makes the new allocator exactly as fast as the pre-#18670 one, 30% faster than main. The new `free_many` takes 8.7µs to free 1,000 entities where the optimized `free` takes `29.7`, so another big win there. This should make up the 20% regression to despawning. It might be even faster than pre-#18670 if we increase 64 to 128 or something, but I think that's unnecessary. This could also much improve performance for despawning scenes if we can find a way to make use of `free_many`, but that's a different task. |
||
|
|
b842bcb923 |
Contiguous access (#21984)
# Objective Enables accessing slices from tables directly via Queries. Fixes: #21861 ## Solution One new trait: - `ContiguousQueryData` allows to fetch all values from tables all at once (an implementation for `&T` returns a slice of components in the set table, for `&mut T` returns a mutable slice of components in the set table as well as a struct with methods to set update ticks (to match the `fetch` implementation)) Methods `contiguous_iter`, `contiguous_iter_mut` and similar in `Query` and `QueryState` making possible to iterate using these traits. Macro `QueryData` was updated to support contiguous items when `contiguous(target)` attribute is added (a target can be `all`, `mutable` and `immutable`, refer to the `custom_query_param` example) ## Testing - `sparse_set_contiguous_query` test verifies that you can't use `next_contiguous` with sparse set components - `test_contiguous_query_data` test verifies that returned values are valid - `base_contiguous` benchmark (file is named `iter_simple_contiguous.rs`) - `base_no_detection` benchmark (file is named `iter_simple_no_detection.rs`) - `base_no_detection_contiguous` benchmark (file is named `iter_simple_no_detection_contiguous.rs`) - `base_contiguous_avx2` benchmark (file is named `iter_simple_contiguous_avx2.rs`) --- ## Showcase Examples `contiguous_query`, `custom_query_param` ### Example ```rust // - self.0 is a World // - self.1 is a QueryState // - velocity is a slice of components with Vec3 inside. // - position is a data structure which implements Deref/DerefMut and IntoIterator methods to access the slice // as well as mechanism to update update ticks (which it does automatically on dereference), // which may be bypassed via `bypass_change_detection` methods. for (velocity, mut position) in self.1.contiguous_iter_mut(&mut self.0).unwrap() { assert!(velocity.len() == position.len()); for (v, p) in velocity.iter().zip(position.iter_mut()) { p.0 += v.0; } } ``` ### Benchmarks Code for `base` benchmark: ```rust #[derive(Component, Copy, Clone)] struct Transform(Mat4); #[derive(Component, Copy, Clone)] struct Position(Vec3); #[derive(Component, Copy, Clone)] struct Rotation(Vec3); #[derive(Component, Copy, Clone)] struct Velocity(Vec3); pub struct Benchmark<'w>(World, QueryState<(&'w Velocity, &'w mut Position)>); impl<'w> Benchmark<'w> { pub fn new() -> Self { let mut world = World::new(); world.spawn_batch(core::iter::repeat_n( ( Transform(Mat4::from_scale(Vec3::ONE)), Position(Vec3::X), Rotation(Vec3::X), Velocity(Vec3::X), ), 10_000, )); let query = world.query::<(&Velocity, &mut Position)>(); Self(world, query) } #[inline(never)] pub fn run(&mut self) { for (velocity, mut position) in self.1.iter_mut(&mut self.0) { position.0 += velocity.0; } } } ``` Iterating over 10000 entities from **a single** table and increasing a 3-dimensional vector from component `Position` by a 3-dimensional vector from component `Velocity` | Name | Time | Time (AVX2) | Description | |------------------------------|-----------|-------------|--------------------------------------------------------------------| | base | 5.5828 µs | 5.5122 µs | Iteration over components | | base_contiguous | 4.8825 µs | 1.8665 µs | Iteration over contiguous chunks | | base_contiguous_avx2 | 2.0740 µs | 1.8665 µs | Iteration over contiguous chunks with enforced avx2 optimizations | | base_no_detection | 4.8065 µs | 4.7723 µs | Iteration over components while bypassing change detection through `bypass_change_detection()` method | | base_no_detection_contiguous | 4.3979 µs | 1.5797 µs | Iteration over components without registering update ticks | Using contiguous 'iterator' makes the program a little bit faster and it can be further vectorized to make it even faster |
||
|
|
8c8e77158c |
Benchmark remote allocation (#22659)
# Objective After #18670, we have a `RemoteAllocator`, but we don't have benchmarks for it compared to the non-remote allocator. This PR just adds those benchmarks. I don't know if we actually want these benchmarks, but it seems reasonable to have, and it took no time to make, so I figured I'd put it out there. ## Solution Add `entity_allocator_allocate_fresh_remote` and `entity_allocator_allocate_reused_remote` benchmark groups. ## Testing - CI, benchmarks --- ## Showcase ```txt entity_allocator_allocate_fresh/10000_entities 1.00 22.8±0.29µs ? ?/sec entity_allocator_allocate_fresh/100_entities 1.00 227.9±6.37ns ? ?/sec entity_allocator_allocate_fresh/1_entities 1.00 6.2±3.83ns ? ?/sec entity_allocator_allocate_fresh_bulk/10000_entities 1.00 19.9±0.25µs ? ?/sec entity_allocator_allocate_fresh_bulk/100_entities 1.00 227.5±6.95ns ? ?/sec entity_allocator_allocate_fresh_bulk/1_entities 1.00 11.5±4.69ns ? ?/sec entity_allocator_allocate_fresh_remote/10000_entities 1.00 19.4±0.32µs ? ?/sec entity_allocator_allocate_fresh_remote/100_entities 1.00 174.2±3.63ns ? ?/sec entity_allocator_allocate_fresh_remote/1_entities 1.00 3.5±3.02ns ? ?/sec entity_allocator_allocate_reused/10000_entities 1.00 21.5±0.37µs ? ?/sec entity_allocator_allocate_reused/100_entities 1.00 233.3±11.77ns ? ?/sec entity_allocator_allocate_reused/1_entities 1.00 8.3±3.70ns ? ?/sec entity_allocator_allocate_reused_bulk/10000_entities 1.00 20.4±0.64µs ? ?/sec entity_allocator_allocate_reused_bulk/100_entities 1.00 261.5±45.59ns ? ?/sec entity_allocator_allocate_reused_bulk/1_entities 1.00 19.7±10.77ns ? ?/sec entity_allocator_allocate_reused_remote/10000_entities 1.00 77.9±1.53µs ? ?/sec entity_allocator_allocate_reused_remote/100_entities 1.00 774.9±16.28ns ? ?/sec entity_allocator_allocate_reused_remote/1_entities 1.00 7.3±3.60ns ? ?/sec ``` Long story short, remote allocation is a little over 3 times slower than non-remote. All things considered, I think that's pretty good. |
||
|
|
abbaa47aac |
Glam 0.31 (#22681)
# Objective Adopt and closes #22665 ## Solution Delete bevy's `Affine3`, create an extension trait for methods create for old bevy's `Affine3` to be used by glam's `Affine3`, and register glam's `Affine3` for reflection ## Testing `cargo run -p ci` --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
|
|
bb78bbf3bd |
Add benchmarks for raw entity allocation (#22640)
# Objective As per [this](https://github.com/bevyengine/bevy/pull/18670#issuecomment-3781624261) comment on #18670, this adds benchmarks for direct access to the entity allocator. ## Solution Add 5 groups of benchmarks: - allocating fresh entities - allocating fresh entities in bulk - freeing entities - allocating reused entities - allocating reused entities in bulk ## Testing - CI and benches |
||
|
|
cf8f9beea7 |
Improve realism of entity benches by warming up the entity allocator (#22639)
# Objective As per [this](https://github.com/bevyengine/bevy/pull/18670#issuecomment-3776301267) comment on #18670, this PR attempts to make entity related benchmarks more realistic by warming up the entity allocator. This helps test the freelist in the entity allocator. ## Solution This PR introduces a new `WorldBuilder` type that starts with `World::new`, allows configuration options for warming up the world via the builder pattern, and then builds the warmed up, realistic world. For now, the only available "warm up" is for entities, but we could also add functionality in the future to cache bundle info, pre-create tables, etc to make our benchmarks more realistic. That is, more closely match the performance of a running app, rather than an app at startup. The current implementation for entity warmups allocates some entities and frees them in a random order. It also spawns the highest allocated entity index to prepare `Entities`'s location storage, etc. This involves using `rng` (deterministically), but without this, the entities are allocated in a linear index order (0, 1, 2, ...), which is unrealistic and extremely cache friendly (so it probably makes an impact in performance not desirable for a benchmark). The major downsides here are that the benches take a little longer to run now and that startup/caching time is no longer benchmarked. That is for example, that benchmarking despawning only one entity used to tell us some information about performance of allocating the free list (amongst other one time actions). Now, that information is lost since the world is already warmed up. In practice, for N values of entities, it used to be the case that a higher N showed the performance of the operation, and a lower N showed the performance of the operation + any registration/caching costs. Now, the different N values only tell us more about how well the CPU accommodates a batch of the operation. Currently in Bevy, making a change might make the `...1_entity` benches much worse but the `...1000_entities` much much better because the change added some new caching. The inverse is also common. With this PR, that will no longer be the case, at least for entities and whatever else we add to the `WorldBuilder` in the future. And that change may or may not be desirable. ## Testing Ran a sampling of the benchmarks. |
||
|
|
41f3ee7e47 |
Improve time complexity of get_component_mut (#22572)
# Objective - Addresses #22483. ## Solution Add a bloom filter to `has_conflicts` as a pre-check to see if we need to check a given access against every other access or not. If the access doesn't hit any component or resource a previous access does, we know we don't need to check it element-by-element. Well-formed calls to get_components_mut should then be linear in time taken to check for conflicts, as they would always pass the pre-check. The filter used is exported in the `bevy_utils` API as it is not specific to components or resources. ## Testing Tested via `cargo test`, where the get_component_mut tests pass or panic as expected. The get_component_mut benchmarks show >=20% improvements at as little as 5 components compared to the existing fallback for smaller sets. The larger, 32 component benchmark added shows massive improvements. In general, the new pre-check filter means we're only about 4-5x slower than not checking at all. ``` ecs::world::world_get::world_query_get_components_mut/10_components_50000_entities time: [6.5252 ms 6.5314 ms 6.5388 ms] change: [−34.225% −33.954% −33.747%] (p = 0.00 < 0.05) Performance has improved. [...] ecs::world::world_get::world_query_get_components_mut/32_components time: [21.608 ms 21.696 ms 21.828 ms] change: [−92.284% −92.236% −92.186%] (p = 0.00 < 0.05) Performance has improved. ``` | bench | mean (prev, with fallback threshold) | mean (post, always complex) | delta | |-|-|-|-| | 2_components | 770.73 us | 925.90 us | +19.62% | | unchecked_2_components | 288.66 us | 288.71 us | -0.84% | | 5_components | 2.960 ms | 2.286 ms | -22.77% | | unchecked_5_components | 505.45 us | 521.56 us | +2.14% | | 10_components | 9.889 ms | 6.531 ms | -33.95% | | unchecked_10_components | 1.452 ms | 1.538 ms | +5.48% | | 32_components | 279.44 ms | 21.696 ms | -92.24% | | unchecked_32_components | 4.460 ms | 4.458 ms | -0.05% |
||
|
|
fb5a669925 |
Add critcmp to benches README (#22616)
This is useful and we use it regularly. Worth documenting! |
||
|
|
7b03032783 |
Reorganize some of bevy_reflect's exports into their respective modules (#22342)
# Objective - #22321 ## Solution Do it ## Testing `cargo clippy` and CI. --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> |
||
|
|
238e1ea665 |
Add a benchmark for spawn_batch (#22439)
# Objective
There was no benchmark for spawn_batch that I could find.
## Solution
Add one.
The intent here is to make it comparable to the `spawn_world` benchmark,
so I kept the construction of the vec of component data in the
benchmark.
The benchmark uses the iter to use batch sizes from 1 to 10,000, which
also explains the addition of the 1000 level.
## Testing
```
cargo bench -p benches -- world_spawn
```
## Showcase
```
spawn_world/1_entities time: [47.966 ns 48.690 ns 49.464 ns]
change: [+2.1054% +4.5065% +6.9285%] (p = 0.00 < 0.05)
Performance has regressed.
spawn_world/100_entities
time: [4.8244 µs 4.8961 µs 4.9715 µs]
change: [−2.9086% −0.5143% +1.9117%] (p = 0.68 > 0.05)
No change in performance detected.
spawn_world/10000_entities
time: [473.97 µs 481.61 µs 490.26 µs]
change: [−0.5065% +1.7613% +4.2000%] (p = 0.14 > 0.05)
No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
spawn_world_batch/1_entities
time: [663.32 µs 669.68 µs 677.00 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
spawn_world_batch/100_entities
time: [379.35 µs 386.57 µs 394.14 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) low mild
spawn_world_batch/1000_entities
time: [354.48 µs 361.06 µs 367.44 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) low mild
spawn_world_batch/10000_entities
time: [382.73 µs 392.63 µs 402.06 µs]
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
```
---------
Co-authored-by: François Mockers <francois.mockers@vleue.com>
|
||
|
|
16409b8a02 |
Fix lints after Rust 1.92 (#22092)
# Objective CI is currently failing ## Solution Fix the lints (and work around all the rustc lint bugs that are apparently included) --------- Co-authored-by: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> |
||
|
|
d76c3aab95 |
get_components_mut (#21780)
# Objective - Add a checked version of `EntityMut::get_components_mut` and `EntityWorldMut::get_components_mut` that does not allocate ## Solution - Add a iterator over the access type to `QueryData`. This is then used to iterate over the pairs of access to check if they are compatible or not. ## Testing - Added a unit test ### Bench checked vs unchecked (50000 entities) | #components | unchecked | checked | times slower | |-------------|-----------|----------|----------| | 2 | 509 us | 1123 us | 2.2x | | 5 | 903 us | 2902us | 3.2x | | 10 | 1700 us | 11424 us | 6.72x | so at 10 components each call was taking about 0.22us vs 0.03 us --- ## ToDo * [x] add release note * [x] add migration guide * [x] add macro for more benches * [x] add bench results to pr description * [ ] look into if this will help with uncached queries * [x] see if we can optimize it a bit --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
|
|
c378b7aa7e |
Update criterion requirement from 0.7.0 to 0.8.0 (#21994)
Updates the requirements on [criterion](https://github.com/criterion-rs/criterion.rs) to permit the latest version. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/criterion-rs/criterion.rs/blob/master/CHANGELOG.md">criterion's changelog</a>.</em></p> <blockquote> <h2><a href="https://github.com/criterion-rs/criterion.rs/compare/criterion-v0.7.0...criterion-v0.8.0">0.8.0</a> - 2025-11-29</h2> <h3>BREAKING</h3> <ul> <li>Drop async-std support</li> </ul> <h3>Changed</h3> <ul> <li>Bump MSRV to 1.86, stable to 1.91.1</li> </ul> <h3>Added</h3> <ul> <li>Add ability to plot throughput on summary page.</li> <li>Add support for reporting throughput in elements and bytes - <code>Throughput::ElementsAndBytes</code> allows the text summary to report throughput in both units simultaneously.</li> <li>Add alloca-based memory layout randomisation to mitigate memory effects on measurements.</li> <li>Add doc comment to benchmark runner in criterion_group macro (removes linter warnings)</li> </ul> <h3>Fixed</h3> <ul> <li>Fix plotting NaN bug</li> </ul> <h3>Other</h3> <ul> <li>Remove Master API Docs links temporarily while we restore the docs publishing.</li> </ul> <h2>[0.7.0] - 2025-07-25</h2> <ul> <li>Bump version of criterion-plot to align dependencies.</li> </ul> <h2>[0.6.0] - 2025-05-17</h2> <h3>Changed</h3> <ul> <li>MSRV bumped to 1.80</li> <li>The <code>real_blackbox</code> feature no longer has any impact. Criterion always uses <code>std::hint::black_box()</code> now. Users of <code>criterion::black_box()</code> should switch to <code>std::hint::black_box()</code>.</li> <li><code>clap</code> dependency unpinned.</li> </ul> <h3>Fixed</h3> <ul> <li>gnuplot version is now correctly detected when using certain Windows binaries/configurations that used to fail</li> </ul> <h3>Added</h3> <ul> <li>Async benchmarking with Tokio may be done via a <code>tokio::runtime::Handle</code>, not only a <code>tokio::runtime::Runtime</code></li> </ul> <h2>[0.5.1] - 2023-05-26</h2> <h3>Fixed</h3> <ul> <li>Quick mode (--quick) no longer crashes with measured times over 5 seconds when --noplot is not active</li> </ul> <h2>[0.5.0] - 2023-05-23</h2> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/criterion-rs/criterion.rs/commit/b49ade728c064f49cb2a70b0368658a15cf21833"><code>b49ade7</code></a> chore: release v0.8.0</li> <li>See full diff in <a href="https://github.com/criterion-rs/criterion.rs/compare/criterion-plot-v0.7.0...criterion-v0.8.0">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
6c29824a15 |
Update criterion requirement from 0.6.0 to 0.7.0 (#21726)
Updates the requirements on [criterion](https://github.com/bheisler/criterion.rs) to permit the latest version. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md">criterion's changelog</a>.</em></p> <blockquote> <h2>[0.7.0] - 2025-07-25</h2> <ul> <li>Bump version of criterion-plot to align dependencies.</li> </ul> <h2>[0.6.0] - 2025-05-17</h2> <h3>Changed</h3> <ul> <li>MSRV bumped to 1.80</li> <li>The <code>real_blackbox</code> feature no longer has any impact. Criterion always uses <code>std::hint::black_box()</code> now. Users of <code>criterion::black_box()</code> should switch to <code>std::hint::black_box()</code>.</li> <li><code>clap</code> dependency unpinned.</li> </ul> <h3>Fixed</h3> <ul> <li>gnuplot version is now correctly detected when using certain Windows binaries/configurations that used to fail</li> </ul> <h3>Added</h3> <ul> <li>Async benchmarking with Tokio may be done via a <code>tokio::runtime::Handle</code>, not only a <code>tokio::runtime::Runtime</code></li> </ul> <h2>[0.5.1] - 2023-05-26</h2> <h3>Fixed</h3> <ul> <li>Quick mode (--quick) no longer crashes with measured times over 5 seconds when --noplot is not active</li> </ul> <h2>[0.5.0] - 2023-05-23</h2> <h3>Changed</h3> <ul> <li>Replaced lazy_static dependency with once_cell</li> <li>Improved documentation of the <code>html_reports</code> feature</li> <li>Replaced atty dependency with is-terminal</li> <li>MSRV bumped to 1.64</li> <li>Upgraded clap dependency to v4</li> <li>Upgraded tempfile dependency to v3.5.0</li> </ul> <h3>Fixed</h3> <ul> <li>Quick mode (<code>--quick</code>) no longer outputs 1ms for measured times over 5 seconds</li> <li>Documentation updates</li> </ul> <h2>[0.4.0] - 2022-09-10</h2> <h3>Removed</h3> <ul> <li>The <code>Criterion::can_plot</code> function has been removed.</li> <li>The <code>Criterion::bench_function_over_inputs</code> function has been removed.</li> <li>The <code>Criterion::bench_functions</code> function has been removed.</li> <li>The <code>Criterion::bench</code> function has been removed.</li> </ul> <h3>Changed</h3> <ul> <li>HTML report hidden behind non-default feature flag: 'html_reports'</li> <li>Standalone support (ie without cargo-criterion) feature flag: 'cargo_bench_support'</li> <li>MSRV bumped to 1.57</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/bheisler/criterion.rs/commit/567405d25363804dd1e6d440a0c9d6612c4cecd8"><code>567405d</code></a> release: bump criterion and criterion-plot versions (<a href="https://redirect.github.com/bheisler/criterion.rs/issues/878">#878</a>)</li> <li><a href="https://github.com/bheisler/criterion.rs/commit/ccccbcc15237233af22af4c76751a7aa184609b3"><code>ccccbcc</code></a> fix: deal with throughput in bits (<a href="https://redirect.github.com/bheisler/criterion.rs/issues/861">#861</a>)</li> <li><a href="https://github.com/bheisler/criterion.rs/commit/deb0eb021dbaa58678222725a455662f780751d0"><code>deb0eb0</code></a> feat: support throughput reports in bits (<a href="https://redirect.github.com/bheisler/criterion.rs/issues/833">#833</a>)</li> <li><a href="https://github.com/bheisler/criterion.rs/commit/d4fd7cc478dfb15e82ea9726c8e4c5a3afc4bc49"><code>d4fd7cc</code></a> Add CI job checking library builds with oldest allowed dependencies (<a href="https://redirect.github.com/bheisler/criterion.rs/issues/854">#854</a>)</li> <li>See full diff in <a href="https://github.com/bheisler/criterion.rs/compare/0.6.0...0.7.0">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
279a8678d4 |
Improved Entity Lifecycle: remove flushing, support manual spawning and despawning (#19451)
# Objective This is the next step for #19430 and is also convinient for #18670. For context, the way entities work on main is as a "allocate and use" system. Entity ids are allocated, and given a location. The location can then be changed, etc. Entities that are free have an invalid location. To allocate an entity, one must also set its location. This introduced the need for pending entities, where an entity would be reserved, pending, and at some point flushed. Pending and free entities have an invalid location, and others are assumed to have a valid one. This paradigm has a number of downsides: First, the entities metadata table is inseparable from the allocator, which makes remote reservation challenging. Second, the `World` must be flushed, even to do simple things, like allocate a temporary entity id. Third, users have little control over entity ids, only interacting with conceptual entities. This made things like `Entities::alloc_at` clunky and slow, leading to its removal, despite some users still having valid need of it. So the goal of this PR is to: - Decouple `Entities` from entity allocation to make room for other allocators and resolve `alloc_at` issues. - Decouple entity allocation from spawning to make reservation a moot point. - Introduce constructing and destructing entities, in addition to spawn/despawn. - Change `reserve` and `flush` patterns to `alloc` and `construct` patterns. It is possible to break this up into multiple prs, as I originally intended, but doing so would require lots of temporary scaffolding that would both hurt performance and make things harder to review. ## Solution This solution builds on #19433, which changed the representation of invalid entity locations from a constant to `None`. There's quite a few steps to this, each somewhat controversial: ### Entities with no location This pr introduces the idea of entity rows both with and without locations. This corresponds to entities that are constructed (the row has a location) and not constructed (the row has no location). When a row is free or pending, it is not constructed. When a row is outside the range of the meta list, it still exists; it's just not constructed. This extends to conceptual entities; conceptual entities may now be in one of 3 states: empty (constructed; no components), normal (constructed; 1 or more components), or null (not constructed). This extends to entity pointers (`EntityWorldMut`, etc): These now can point to "null"/not constructed entities. Depending on the privilege of the pointer, these can also construct or destruct the entity. This also changes how `Entity` ids relate to conceptual entities. An `Entity` now exists if its generation matches that of its row. An `Entity` that has the right generation for its row will claim to exist, even if it is not constructed. This means, for example, an `Entity` manually constructed with a large index and generation of 0 *will* exist if it has not been allocated yet. ### `Entities` is separate from the allocator This pr separates entity allocation from `Entities`. `Entities` is now only focused on tracking entity metadata, etc. The new `EntitiesAllocator` on `World` manages all allocations. This forces `Entities` to not rely on allocator state to determine if entities exist, etc, which is convinient for remote reservation and needed for custom allocators. It also paves the way for allocators not housed within the `World`, makes some unsafe code easier since the allocator and metadata live under different pointers, etc. This separation requires thinking about interactions with `Entities` in a new way. Previously, the `Entities` set the rules for what entities are valid and what entities are not. Now, it has no way of knowing. Instead, interaction with `Entities` are more like declaring some information for it to track than changing some information it was already tracking. To reflect this, `set` has been split up into `declare` and `update`. ### Constructing and destructing As mentioned, entities that have no location (not constructed) can be constructed at any time. This takes on exactly the same meaning as the previous `spawn_non_existent`. It creates/declares a location instead of updating an old one. As an example, this makes spawning an entity now literately just allocate a new id and construct it immediately. Conversely, entities that are constructed may be destructed. This removes all components and despawns related entities, just like `despawn`. The only difference is that destructing does not free the entity id for reuse. Between constructing and destructing, all needs for `alloc_at` are resolved. If you want to keep the id for custom reuse, just destruct instead of despawn! Despawn, now just destructs the entity and frees it. Destructing a not constructed entity will do nothing. Constructing an already constructed entity will panic. This is to guard against users constructing a manually formed `Entity` that the allocator could later hand out. However, public construction methods have proper error handling for this. Despawning a not constructed entity just frees its id. ### No more flushing All places that once needed to reserve and flush entity ids now allocate and construct them instead. This improves performance and simplifies things. ### Flow chart  (Thanks @ItsDoot) ## Testing - CI - Some new tests - A few deleted (no longer applicable) tests - If you see something you think should have a test case, I'll gladly add it. ## Showcase Here's an example of constructing and destructing ```rust let e4 = world.spawn_null(); world .entity_mut(e4) .construct((TableStored("junk"), A(0))) .unwrap() .destruct() .construct((TableStored("def"), A(456))) .unwrap(); ``` ## Future Work - [x] More expansive docs. This should definitely should be done, but I'd rather do that in a future pr to separate writing review from code review. If you have more ideas for how to introduce users to these concepts, I'd like to see them. As it is, we don't do a very good job of explaining entities to users. Ex: `Entity` doesn't always correspond to a conceptual entity. - [ ] Try to remove panics from `EntityWorldMut`. There is (and was) a lot of assuming the entity is constructed there (was assuming it was not despawned). - [ ] A lot of names are still centered around spawn/despawn, which is more user-friendly than construct/destruct but less precise. Might be worth changing these over. - [ ] Making a centralized bundle despawner would make sense now. - [ ] Of course, build on this for remote reservation and, potentially, for paged entities. ## Performance <details> <summary>Benchmarks</summary> ```txt critcmp main pr19451 -t 1 group main pr19451 ----- ---- ------- add_remove/sparse_set 1.13 594.7±6.80µs ? ?/sec 1.00 527.4±8.01µs ? ?/sec add_remove/table 1.08 799.6±15.53µs ? ?/sec 1.00 739.7±15.10µs ? ?/sec add_remove_big/sparse_set 1.10 614.6±6.50µs ? ?/sec 1.00 557.0±19.04µs ? ?/sec add_remove_big/table 1.03 2.8±0.01ms ? ?/sec 1.00 2.7±0.02ms ? ?/sec added_archetypes/archetype_count/100 1.01 30.9±0.50µs ? ?/sec 1.00 30.5±0.44µs ? ?/sec added_archetypes/archetype_count/1000 1.00 638.0±19.77µs ? ?/sec 1.03 657.0±73.61µs ? ?/sec added_archetypes/archetype_count/10000 1.02 5.5±0.14ms ? ?/sec 1.00 5.4±0.09ms ? ?/sec all_added_detection/50000_entities_ecs::change_detection::Sparse 1.02 47.9±1.22µs ? ?/sec 1.00 46.8±0.40µs ? ?/sec all_added_detection/50000_entities_ecs::change_detection::Table 1.02 45.4±1.89µs ? ?/sec 1.00 44.6±0.78µs ? ?/sec build_schedule/1000_schedule 1.02 942.6±11.53ms ? ?/sec 1.00 925.2±10.35ms ? ?/sec build_schedule/100_schedule 1.01 5.8±0.12ms ? ?/sec 1.00 5.7±0.12ms ? ?/sec build_schedule/100_schedule_no_constraints 1.03 803.1±28.93µs ? ?/sec 1.00 781.1±50.11µs ? ?/sec build_schedule/500_schedule_no_constraints 1.00 5.6±0.31ms ? ?/sec 1.08 6.0±0.27ms ? ?/sec busy_systems/01x_entities_03_systems 1.00 24.4±1.35µs ? ?/sec 1.01 24.7±1.35µs ? ?/sec busy_systems/03x_entities_03_systems 1.00 38.1±1.70µs ? ?/sec 1.04 39.7±1.49µs ? ?/sec busy_systems/03x_entities_09_systems 1.01 111.4±2.27µs ? ?/sec 1.00 109.9±2.46µs ? ?/sec busy_systems/03x_entities_15_systems 1.00 174.8±2.56µs ? ?/sec 1.01 176.6±4.22µs ? ?/sec contrived/03x_entities_09_systems 1.00 59.0±2.92µs ? ?/sec 1.01 59.8±3.03µs ? ?/sec contrived/03x_entities_15_systems 1.00 97.5±4.87µs ? ?/sec 1.01 98.8±4.69µs ? ?/sec contrived/05x_entities_09_systems 1.00 75.3±3.76µs ? ?/sec 1.01 76.4±4.11µs ? ?/sec despawn_world/10000_entities 1.32 344.8±4.47µs ? ?/sec 1.00 261.4±4.91µs ? ?/sec despawn_world/100_entities 1.22 4.3±0.04µs ? ?/sec 1.00 3.5±0.54µs ? ?/sec despawn_world/1_entities 1.01 169.6±7.88ns ? ?/sec 1.00 167.8±11.45ns ? ?/sec despawn_world_recursive/10000_entities 1.20 1723.0±53.82µs ? ?/sec 1.00 1437.0±26.11µs ? ?/sec despawn_world_recursive/100_entities 1.16 17.9±0.10µs ? ?/sec 1.00 15.5±0.16µs ? ?/sec despawn_world_recursive/1_entities 1.01 372.8±15.68ns ? ?/sec 1.00 367.7±16.90ns ? ?/sec ecs::entity_cloning::hierarchy_many/clone 1.03 227.9±24.67µs 1559.9 KElem/sec 1.00 221.1±29.74µs 1607.8 KElem/sec ecs::entity_cloning::hierarchy_many/reflect 1.00 406.2±23.46µs 875.2 KElem/sec 1.02 413.9±22.45µs 858.9 KElem/sec ecs::entity_cloning::hierarchy_tall/clone 1.01 12.2±0.34µs 4.0 MElem/sec 1.00 12.0±1.41µs 4.1 MElem/sec ecs::entity_cloning::hierarchy_tall/reflect 1.02 15.3±0.39µs 3.2 MElem/sec 1.00 15.0±2.14µs 3.2 MElem/sec ecs::entity_cloning::single/clone 1.02 659.0±100.01ns 1481.8 KElem/sec 1.00 643.3±101.49ns 1517.9 KElem/sec ecs::entity_cloning::single/reflect 1.03 1135.2±72.17ns 860.2 KElem/sec 1.00 1098.3±65.99ns 889.1 KElem/sec empty_archetypes/for_each/10 1.02 8.1±0.57µs ? ?/sec 1.00 8.0±0.37µs ? ?/sec empty_archetypes/for_each/100 1.01 8.1±0.34µs ? ?/sec 1.00 8.1±0.28µs ? ?/sec empty_archetypes/for_each/1000 1.03 8.4±0.25µs ? ?/sec 1.00 8.2±0.29µs ? ?/sec empty_archetypes/iter/100 1.01 8.1±0.29µs ? ?/sec 1.00 8.0±0.34µs ? ?/sec empty_archetypes/iter/1000 1.02 8.5±0.31µs ? ?/sec 1.00 8.4±0.62µs ? ?/sec empty_archetypes/iter/10000 1.01 10.6±1.22µs ? ?/sec 1.00 10.5±0.49µs ? ?/sec empty_archetypes/par_for_each/10 1.01 8.8±0.49µs ? ?/sec 1.00 8.7±0.31µs ? ?/sec empty_archetypes/par_for_each/100 1.00 8.7±0.48µs ? ?/sec 1.04 9.0±0.34µs ? ?/sec empty_archetypes/par_for_each/10000 1.01 21.2±0.41µs ? ?/sec 1.00 20.9±0.44µs ? ?/sec empty_commands/0_entities 1.72 3.7±0.01ns ? ?/sec 1.00 2.1±0.02ns ? ?/sec empty_systems/100_systems 1.00 82.9±3.29µs ? ?/sec 1.07 88.3±3.77µs ? ?/sec empty_systems/2_systems 1.01 8.2±0.71µs ? ?/sec 1.00 8.2±0.38µs ? ?/sec empty_systems/4_systems 1.00 8.2±0.72µs ? ?/sec 1.03 8.4±0.71µs ? ?/sec entity_hash/entity_set_build/10000 1.10 45.9±1.60µs 207.7 MElem/sec 1.00 41.6±0.39µs 229.0 MElem/sec entity_hash/entity_set_build/3162 1.06 12.7±0.77µs 236.7 MElem/sec 1.00 12.0±0.75µs 250.6 MElem/sec entity_hash/entity_set_lookup_hit/10000 1.02 14.5±0.30µs 658.3 MElem/sec 1.00 14.2±0.07µs 672.6 MElem/sec entity_hash/entity_set_lookup_hit/3162 1.01 4.4±0.03µs 682.7 MElem/sec 1.00 4.4±0.01µs 691.3 MElem/sec entity_hash/entity_set_lookup_miss_gen/10000 1.01 61.3±4.12µs 155.6 MElem/sec 1.00 60.6±1.47µs 157.3 MElem/sec entity_hash/entity_set_lookup_miss_gen/3162 1.00 9.5±0.02µs 316.3 MElem/sec 1.01 9.7±0.88µs 311.7 MElem/sec entity_hash/entity_set_lookup_miss_id/100 1.00 145.5±1.49ns 655.4 MElem/sec 1.03 149.8±1.59ns 636.7 MElem/sec entity_hash/entity_set_lookup_miss_id/10000 1.85 63.9±3.57µs 149.3 MElem/sec 1.00 34.6±3.81µs 275.8 MElem/sec entity_hash/entity_set_lookup_miss_id/316 1.00 562.0±9.58ns 536.2 MElem/sec 1.02 573.9±1.27ns 525.1 MElem/sec entity_hash/entity_set_lookup_miss_id/3162 1.03 9.1±0.10µs 330.7 MElem/sec 1.00 8.9±0.24µs 339.0 MElem/sec event_propagation/four_event_types 1.12 541.5±3.84µs ? ?/sec 1.00 482.7±4.64µs ? ?/sec event_propagation/single_event_type 1.07 769.5±10.21µs ? ?/sec 1.00 715.9±15.16µs ? ?/sec event_propagation/single_event_type_no_listeners 1.56 393.4±2.89µs ? ?/sec 1.00 251.4±3.68µs ? ?/sec events_iter/size_16_events_100 1.01 64.0±0.18ns ? ?/sec 1.00 63.4±0.23ns ? ?/sec events_iter/size_4_events_100 1.02 64.8±0.90ns ? ?/sec 1.00 63.4±0.24ns ? ?/sec events_iter/size_4_events_1000 1.01 586.5±8.00ns ? ?/sec 1.00 579.1±4.93ns ? ?/sec events_send/size_16_events_100 1.00 142.7±24.34ns ? ?/sec 1.03 147.1±28.36ns ? ?/sec events_send/size_16_events_10000 1.01 12.2±0.13µs ? ?/sec 1.00 12.1±0.12µs ? ?/sec fake_commands/10000_commands 1.43 63.3±8.21µs ? ?/sec 1.00 44.1±0.16µs ? ?/sec fake_commands/1000_commands 1.40 6.2±0.01µs ? ?/sec 1.00 4.4±0.02µs ? ?/sec fake_commands/100_commands 1.38 629.4±1.69ns ? ?/sec 1.00 457.1±0.84ns ? ?/sec few_changed_detection/50000_entities_ecs::change_detection::Table 1.00 57.7±0.86µs ? ?/sec 1.07 61.6±1.19µs ? ?/sec few_changed_detection/5000_entities_ecs::change_detection::Sparse 1.05 5.4±0.53µs ? ?/sec 1.00 5.1±0.56µs ? ?/sec few_changed_detection/5000_entities_ecs::change_detection::Table 1.00 4.3±0.30µs ? ?/sec 1.18 5.1±0.35µs ? ?/sec insert_commands/insert 1.11 402.5±10.75µs ? ?/sec 1.00 363.6±8.07µs ? ?/sec insert_commands/insert_batch 1.00 174.9±3.03µs ? ?/sec 1.02 177.9±5.74µs ? ?/sec insert_simple/base 1.04 564.1±23.01µs ? ?/sec 1.00 544.3±60.70µs ? ?/sec insert_simple/unbatched 1.32 929.3±180.10µs ? ?/sec 1.00 704.1±132.88µs ? ?/sec iter_fragmented/base 1.02 280.0±2.86ns ? ?/sec 1.00 274.0±4.85ns ? ?/sec iter_fragmented/foreach 1.00 97.3±0.42ns ? ?/sec 1.03 100.6±3.44ns ? ?/sec iter_fragmented/foreach_wide 1.04 2.7±0.04µs ? ?/sec 1.00 2.6±0.03µs ? ?/sec iter_fragmented_sparse/base 1.00 5.6±0.05ns ? ?/sec 1.04 5.8±0.06ns ? ?/sec multiple_archetypes_none_changed_detection/100_archetypes_10000_entities_ecs::change_detection::Sparse 1.00 737.7±27.38µs ? ?/sec 1.01 747.5±30.01µs ? ?/sec multiple_archetypes_none_changed_detection/100_archetypes_10000_entities_ecs::change_detection::Table 1.02 678.3±25.13µs ? ?/sec 1.00 662.1±19.63µs ? ?/sec multiple_archetypes_none_changed_detection/100_archetypes_1000_entities_ecs::change_detection::Sparse 1.09 76.0±9.35µs ? ?/sec 1.00 70.0±3.29µs ? ?/sec multiple_archetypes_none_changed_detection/100_archetypes_1000_entities_ecs::change_detection::Table 1.03 64.7±3.40µs ? ?/sec 1.00 62.8±1.80µs ? ?/sec multiple_archetypes_none_changed_detection/100_archetypes_100_entities_ecs::change_detection::Table 1.02 7.6±0.12µs ? ?/sec 1.00 7.5±0.16µs ? ?/sec multiple_archetypes_none_changed_detection/100_archetypes_10_entities_ecs::change_detection::Sparse 1.00 1003.5±12.38ns ? ?/sec 1.01 1013.7±32.64ns ? ?/sec multiple_archetypes_none_changed_detection/20_archetypes_10_entities_ecs::change_detection::Sparse 1.03 187.1±21.18ns ? ?/sec 1.00 181.9±22.86ns ? ?/sec multiple_archetypes_none_changed_detection/5_archetypes_10_entities_ecs::change_detection::Sparse 1.00 52.8±8.19ns ? ?/sec 1.03 54.3±8.06ns ? ?/sec multiple_archetypes_none_changed_detection/5_archetypes_10_entities_ecs::change_detection::Table 1.00 46.8±2.23ns ? ?/sec 1.03 48.0±2.48ns ? ?/sec no_archetypes/system_count/0 1.00 16.3±0.17ns ? ?/sec 1.02 16.6±0.16ns ? ?/sec no_archetypes/system_count/100 1.02 851.5±9.32ns ? ?/sec 1.00 832.9±7.93ns ? ?/sec none_changed_detection/5000_entities_ecs::change_detection::Sparse 1.00 3.4±0.04µs ? ?/sec 1.02 3.5±0.05µs ? ?/sec nonempty_spawn_commands/10000_entities 1.89 261.1±6.99µs ? ?/sec 1.00 137.8±8.47µs ? ?/sec nonempty_spawn_commands/1000_entities 1.90 26.4±3.18µs ? ?/sec 1.00 13.9±2.38µs ? ?/sec nonempty_spawn_commands/100_entities 1.87 2.6±0.07µs ? ?/sec 1.00 1388.8±97.31ns ? ?/sec observe/trigger_simple 1.09 347.5±1.51µs ? ?/sec 1.00 317.7±2.62µs ? ?/sec observe/trigger_targets_simple/10000_entity 1.04 696.5±15.50µs ? ?/sec 1.00 672.0±13.88µs ? ?/sec par_iter_simple/with_0_fragment 1.01 34.4±0.51µs ? ?/sec 1.00 33.9±0.53µs ? ?/sec par_iter_simple/with_1000_fragment 1.04 45.5±0.93µs ? ?/sec 1.00 43.9±1.85µs ? ?/sec par_iter_simple/with_100_fragment 1.03 36.2±0.50µs ? ?/sec 1.00 35.1±0.44µs ? ?/sec par_iter_simple/with_10_fragment 1.03 37.5±0.97µs ? ?/sec 1.00 36.5±0.74µs ? ?/sec param/combinator_system/8_dyn_params_system 1.00 10.4±0.73µs ? ?/sec 1.01 10.5±0.79µs ? ?/sec param/combinator_system/8_piped_systems 1.05 8.0±0.65µs ? ?/sec 1.00 7.6±0.57µs ? ?/sec query_get/50000_entities_sparse 1.06 136.7±0.35µs ? ?/sec 1.00 128.6±0.44µs ? ?/sec query_get_many_10/50000_calls_sparse 1.02 1649.4±77.80µs ? ?/sec 1.00 1614.4±78.91µs ? ?/sec query_get_many_2/50000_calls_sparse 1.00 191.3±3.66µs ? ?/sec 1.01 193.3±0.75µs ? ?/sec query_get_many_2/50000_calls_table 1.00 243.9±0.55µs ? ?/sec 1.05 257.2±8.62µs ? ?/sec query_get_many_5/50000_calls_sparse 1.00 585.9±7.70µs ? ?/sec 1.03 600.6±5.99µs ? ?/sec query_get_many_5/50000_calls_table 1.00 673.7±7.44µs ? ?/sec 1.07 722.3±10.77µs ? ?/sec run_condition/no/1000_systems 1.00 23.7±0.06µs ? ?/sec 1.06 25.1±0.07µs ? ?/sec run_condition/no/100_systems 1.00 1460.5±4.28ns ? ?/sec 1.03 1510.1±3.69ns ? ?/sec run_condition/no/10_systems 1.00 201.5±0.53ns ? ?/sec 1.04 209.1±2.37ns ? ?/sec run_condition/yes/1000_systems 1.00 1225.7±22.58µs ? ?/sec 1.02 1253.7±24.90µs ? ?/sec run_condition/yes/100_systems 1.02 89.4±3.43µs ? ?/sec 1.00 88.0±3.96µs ? ?/sec run_condition/yes_using_query/1000_systems 1.00 1288.3±26.57µs ? ?/sec 1.03 1323.0±24.73µs ? ?/sec run_condition/yes_using_query/100_systems 1.00 108.8±2.51µs ? ?/sec 1.03 112.3±3.09µs ? ?/sec run_condition/yes_using_resource/100_systems 1.03 99.0±3.37µs ? ?/sec 1.00 96.2±4.80µs ? ?/sec run_empty_schedule/MultiThreaded 1.03 15.3±0.10ns ? ?/sec 1.00 14.9±0.03ns ? ?/sec run_empty_schedule/Simple 1.01 15.2±0.15ns ? ?/sec 1.00 15.0±0.25ns ? ?/sec sized_commands_0_bytes/10000_commands 1.57 52.6±0.41µs ? ?/sec 1.00 33.5±0.10µs ? ?/sec sized_commands_0_bytes/1000_commands 1.57 5.3±0.01µs ? ?/sec 1.00 3.4±0.00µs ? ?/sec sized_commands_0_bytes/100_commands 1.56 536.5±4.83ns ? ?/sec 1.00 343.6±1.12ns ? ?/sec sized_commands_12_bytes/10000_commands 1.22 63.0±0.53µs ? ?/sec 1.00 51.5±6.06µs ? ?/sec sized_commands_12_bytes/1000_commands 1.25 5.7±0.01µs ? ?/sec 1.00 4.6±0.05µs ? ?/sec sized_commands_12_bytes/100_commands 1.27 579.3±1.28ns ? ?/sec 1.00 455.4±0.85ns ? ?/sec sized_commands_512_bytes/10000_commands 1.11 248.4±85.81µs ? ?/sec 1.00 224.3±52.11µs ? ?/sec sized_commands_512_bytes/1000_commands 1.09 22.8±0.18µs ? ?/sec 1.00 21.0±0.17µs ? ?/sec sized_commands_512_bytes/100_commands 1.13 1852.2±11.21ns ? ?/sec 1.00 1635.3±4.91ns ? ?/sec spawn_commands/10000_entities 1.04 844.2±11.96µs ? ?/sec 1.00 811.5±13.25µs ? ?/sec spawn_commands/1000_entities 1.05 84.9±3.66µs ? ?/sec 1.00 80.5±4.13µs ? ?/sec spawn_commands/100_entities 1.06 8.6±0.12µs ? ?/sec 1.00 8.1±0.12µs ? ?/sec spawn_world/10000_entities 1.03 413.2±25.20µs ? ?/sec 1.00 400.9±49.97µs ? ?/sec spawn_world/100_entities 1.02 4.1±0.62µs ? ?/sec 1.00 4.1±0.69µs ? ?/sec spawn_world/1_entities 1.04 42.2±3.23ns ? ?/sec 1.00 40.6±6.81ns ? ?/sec world_entity/50000_entities 1.18 88.3±0.42µs ? ?/sec 1.00 74.7±0.16µs ? ?/sec world_get/50000_entities_sparse 1.02 182.2±0.32µs ? ?/sec 1.00 179.5±0.84µs ? ?/sec world_get/50000_entities_table 1.01 198.3±0.46µs ? ?/sec 1.00 196.2±0.63µs ? ?/sec world_query_for_each/50000_entities_sparse 1.00 32.7±0.12µs ? ?/sec 1.01 33.1±0.46µs ? ?/sec ``` </details> This roughly doubles command spawning speed! Despawning also sees a 20-30% improvement. Dummy commands improve by 10-50% (due to not needing an entity flush). Other benchmarks seem to be noise and are negligible. It looks to me like a massive performance win! --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Co-authored-by: urben1680 <55257931+urben1680@users.noreply.github.com> Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> Co-authored-by: Trashtalk217 <24552941+Trashtalk217@users.noreply.github.com> Co-authored-by: James Liu <contact@jamessliu.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com> |
||
|
|
17ba0fd733 |
Remove #[derive(Resource, Component)] from tests (#21589)
# Objective Multiple tests derive both `Resource` and `Component` on a single struct. In the current resources-for-components plan (#19731), this leads to a conflict. ## Solution ```rust #[derive(Resource, Component)] struct A; ``` Becomes ```rust #[derive(Component)] struct A; #[derive(Resource)] struct ResA; ``` and the tests are changed accordingly. There was one test that had to be removed as it specifically tested that a query could both query a resource and a component with the same name. That test doesn't make any sense anymore, so I removed it. ## Testing I tested the changes by adding code into `Resource` derive macro, that also derives `Component`, so any conflicts showed up by running `cargo build`. ## Future work `AmbientLight` in `bevy_light` still derives both, but since that requires a little more work, I'm saving it for later. |
||
|
|
af1ff013e1 |
Fix a few "repeated word" typos (#21547)
# Objective Fix some typos ## Solution Have a lovely stroll through the codebase with my friend `\b(\w+)\s+\1\b` and ```diff - word word + word ``` |
||
|
|
44b904ed05 |
Rename bevy_mesh_picking_backend to mesh_picking (#21436)
# Objective - Only prefix features bevy_ if they correspond to a crate - Another step towards #20867 ## Solution - rename it ## Testing - ci |
||
|
|
e713c7b606 |
Change bevy_math to use inline instead of inline(always) (#20887)
## Objective Move `bevy_math` closer to recommended inlining practices, and avoid problems with debuggers and optimising for size. ## Background Some `bevy_math` modules apply `#[inline(always)]` to almost every function. This has downsides for some users - it can prevent optimising for size, and can stop the debugger from stepping into functions. I can't find any source that advises `inline(always)` by default - the most common advice is "use rarely and only after profiling" (example: [std lib guide](https://std-dev-guide.rust-lang.org/policy/inline.html)). I've poked around the `bevy_math` history but couldn't find anything that explains why `inline(always)` was chosen. ## Solution This PR changes all instances of `#[inline(always)]` to `#[inline]`. The change is very unlikely to make any difference in optimised builds - almost all the functions are tiny so they're going to be inlined either way. Benchmarks showed no difference. The change can sometimes decrease performance in `opt-level = 0` builds - one math heavy microbenchmark took a -10% hit. But this is arguably the right trade-off if it lets the user step into functions in the debugger. Overall, I think this is the safer default for most users. `inline(always)` has several concrete downsides, while `inline` has some trade-offs but no clear downsides. The change also adds a new `bevy_math` benchmark which includes some of the affected functions. ## Alternatives The change could have been taken further. - Remove `#[inline]` from heftier functions like `BoundingSphere::from_point_cloud`. - Could help optimising for size. I left this out to keep things simple. - Remove `#[inline]` entirely. - I think this is likely to be a good thing, but it needs more testing and would probably be controversial. ## Testing ```sh cargo bench -p benches --bench math ``` Also: - Checked benchmark disassembly to confirm what was inlined. - Compiled `alien_cake_addict` with various optimisation levels to check there weren't major differences in size. EDIT: More details in [comment 1](https://github.com/bevyengine/bevy/pull/20887#issuecomment-3261735671), [comment 2](https://github.com/bevyengine/bevy/pull/20887#issuecomment-3263596184). |
||
|
|
a0e9aabcb5 |
chore: update encase to 0.12 (#21078)
Objective ---- Change log for the upgrade: https://github.com/teoxoy/encase/blob/main/CHANGELOG.md#v0120-2025-09-12 |
||
|
|
12f802b1f0 |
Revived #18741, Remove SimpleExecutor. (#21176)
# Objective Revive #18741 : Reduce complexity and code duplication of schedule executors. This is an alternative to fix #18453. It sounds like `SimpleExecutor` was a temporary solution to "sync points" in schedules, but now those can be inferred, so `SimpleExecutor` is unnecessary now. Further, `SimpleExecutor` and `SingleThreadedExecutor` were *very* similar, which was becoming a consistency and code quality headache. ## Solution Remove `SimpleExecutor`. ## Testing CI |
||
|
|
4d74baf1ae |
BufferedEvent -> Message Rename (#20953)
This renames the concept of `BufferedEvent` to `Message`, and updates our APIs, comments, and documentation to refer to these types as "messages" instead of "events". It also removes/updates anything that considers messages to be "observable", "listenable", or "triggerable". This is a followup to https://github.com/bevyengine/bevy/pull/20731, which omitted the `BufferedEvent -> Message` rename for brevity. See that post for rationale. |
||
|
|
eda118d033 |
Event Rearchitecture (#20731)
There is general consensus that our terminology for Events, "entity events", Observers, and BufferedEvents needs clarity. Additionally, many of us also agree that the current Observer system would benefit from additional static-ness: currently it is assumed that you can use events in pretty much any context, and they all go through the exact same code path. Alice put forth a proposal to [Overhaul Observers](https://hackmd.io/@bevy/rk4S92hmlg), and we have already partially implemented it for 0.17. I think it does a great job of outlining many of the issues at play, and it solves them reasonably well. But I _also_ think the proposed solution isn't yet ideal. Given that it is already partially implemented for 0.17, it is a breaking change, _and_ given that we have already broken the Observer API a number of times, I think we need to sort this out before the next release. This is a big changeset, but it is _largely_ just a reframing of what is already there. I haven't fundamentally changed the behaviors. I've just refined and constrained in a way that allows us to do what we are currently doing in a clearer, simpler, and more performant way. First, I'll give some quick notes on Alice's proposal (which you all should read if you haven't yet!): ### Notes on Alice's Proposal - I like the move toward a more static API - I think we've gone too far down the "separate terminology" path. The proposal introduces a zoo of apis, terms, and "subterms". I think we need to simplify our concepts and names to make this all easier to talk about and use in practice. - BroadcastEvent feels like the wrong name. EntityEvent is also "broadcast" in the exact same way - BufferedEvent is a completely different system than EntityEvent and BroadcastEvent. This muddles concepts too much. It needs its own standalone, single-word concept name. - "Universal observers": I think this should be fully context driven, rather than needing encoding in the API. - I agree we can't get rid of buffered events, and that merging them with "broadcast events" isn't helpful - I'm not quite sure how we'd make the proposed PropagateEvent subtrait work transparently. This can't be "layered on top" as a trait. It needs to be baked in at more fundamental level. * I don't like `app.add_broadcast_observers()`, `app.add_universal_observers()`, `Observer::entity_observer`, `Observer::broadcast`, etc. The `On` event should statically determine whether an observer is an "entity observer" or a "broadcast" Observer. This would already be encoded in the type system and is therefore something we can do on the developer's behalf. Likewise, any observer being registered at a top level is inherently _not_ a specific entity observer. All of these variants serve to make users guess and poke around in a way that is unnecessary. I want simple one word concept names, single constructors, etc. ### Proposed Principals - Static-ness: - Events should only be usable in the context they were defined to be used. - When triggered, Observers should *only* have access to fields and behaviors that are relevant: - Dont return Option or PLACEHOLDER: the field or function shouldn't exist - Entity events that don't support propagation shouldn't expose that functionality - Don't do unnecessary work at runtime - Event triggers shouldn't branch through every potential event code path - Don't clone potentially large lists of event context unnecessarily (Ex: we currently clone the component list for every observer invocation) - Minimize codegen - Don't recompile things redundantly. - Don't compile unnecessary code paths. - Clear and Simple - Minimize the number of concept names floating around, and lock each concept down heavily to a specific context - I'm convinced at this point that "buffered events" and "observer events" sharing concept names is wrong. We need two clean and clear terms, and I'm willing to give "buffered events" a slightly worse name if it means "observer events" can be nicer. - Don't throw the concept name "Event" out ... it is a very good name. Instead, constrain it to one specific thing. - Minimize our API surface - Events contain all context, including what used to be the "target". This lets people define the "target" name that makes the most sense for the context, and lets the documentation fully describe the context of that "target". ### Concepts - **Event** (the thing you "observe") - Rationale: "Event" is the clear choice for this concept. An "event" feels like something that happens in real time. "Event observers" are things that observe events when they occur (are triggered). Additionally, this is the concept that "propagates", and "event propagation" is a term people understand. - **Trigger**: (the verb that "causes" events to happen for targets). Events are Triggered. This can include additional context/ data that is passed to observers / informs the trigger behavior. Events have _exactly_ one Trigger. If you want a different trigger behavior, define a new event. This makes the system more static, more predictable, and easier to understand and document. `world.trigger_ref_with` makes it possible to pass in mutable reference to your own Trigger data, making it possible to customize the input trigger data and read out the final trigger data. - **Observer** (the thing that "observes" events): An event's `Trigger` determines which observers will run. - **Event Types**: You can build any "type" of event. The concept of a "target" has been removed. Instead, define a `Trigger` that expects a specific kind of event (ex: `E: EntityEvent`). - **EntityEvent** We add a new `EntityEvent` trait, which defines an `event.entity()` accessor. This is used by the `Trigger` impls : `EntityTrigger`, `PropagateEntityTrigger`, and `EntityComponentsTrigger`. - **Message** (the buffered thing you "read" and "write") - `Message` is a solid metaphor for what this is ... it is data that is written and then at some later point read by someone / something else. I expect existing consumers of "buffered events" to lament this name change, as "event" feels nicer. But having a separate name is within everyone's best interest. - **MessageReader** (the thing that reads messages) - **MessageWriter** (the thing that writes messages) ### The Changes - `Event` trait changes - Event is now used exclusively by Observers - Added `Event::Trigger`, which defines what trigger implementation this event will use - Added the `Trigger` trait - All of the shared / hard-coded observer trigger logic has been broken out into individual context-specific Trigger traits. - "Trigger Targets" have been removed. - Instead, Events, in combination with their Trigger impl, decide how they will be triggered. In general, this means that Events now include their "targets" as fields on the event. - APIs like `trigger_targets` have been replaced by `trigger`, which can now be used for any `Event` - `EntityEvent` trait changes - Propagation config has been removed from the `EntityEvent` trait. It now lives on the `Trigger` trait (specifically the `PropagateEntityTrigger` trait). - `EntityEvent` now provides `entity / entity_mut` accessors for the Event it is implemented for - `EntityEvent` defaults to having no propagation (uses the simpler `EntityTrigger`) - `#[entity_event(propagate)]` enables the "default" propagation logic (uses ChildOf). The existing `#[entity_event(traversal = X)]` has been renamed to `#[entity_event(propagate = X)` - Deriving `EntityEvent` requires either a single `MyEvent(Entity)`, the `entity` field name (`MyEvent { entity: Entity}`), or `MyEvent { #[event_entity] custom: Entity }` - Animation event changes - Animation events now have their own `AnimationEvent` trait, which sets the `AnimationEventTrigger`. This allows developers to pass in events that _dont_ include the Entity field (as this is set by the system). The custom trigger also opens the doors to cheaply passing in additional animation system context, accessible through `On` - `EntityComponentsTrigger` - The built in Add/Remove/etc lifecycle events now use the `EntityComponentsTrigger`, which passes in the components as additional state. This _significantly_ cuts down on clones, as it does a borrow rather than cloning the list into _each_ observer execution. - Each event now has an `entity` field. - Style changes - Prefer the event name for variables: `explode: On<Explode>` not `event: On<Explode>` - Prefer using the direct field name for the entity on entity events, rather than `event.entity()`. This allows us to use more specific names where appropriate, provides better / more contextual docs, and coaches developers to think of `On<MyEvent>` _as_ the event itself. Take a look at the changes to the examples and the built-in events to see what this looks like in practice. ### Downsides - Moving the "target" into the event adds some new constraints: - Triggering the same event for multiple entities requires multiple trigger calls. For "expensive" events (ex: lots of data attached to the event), this will be more awkward. Your options become: - Create multiple instances of the event, cloning the expensive data - Use `trigger_ref`, and mutate the event on each call to change the target. - Move the "expensive" shared data into the Trigger, and use `trigger_ref_with`` - We could build a new EntityEvent method that abstracts over the "event mutation" behavior and provides something like the old `trigger_target` behavior. - Use a different `EntityTargetTrigger` (not currently provided by bevy, but we could), which brings back the old behavior. This would be used with `trigger_with` to replicate the old pattern: `world.trigger_with(MyEvent, [e1, e2].into())` (or we could make the `into()` implicit) - Bubbling the event involves mutating the event to set the entity. This means that `trigger_ref` will result in the event's `EntityEvent::entity()` being the final bubbled entity instead of the initial entity. - Some APIs (trivially) benefit from the "target entity" being separate from the event. Specifically, this new API requires changes to the "Animation Event" system in AnimationPlayer. I think this is actually a good change set, as it allows us to: - Cheaply expose more animation state as part of a new AnimationEventTrigger impl - Move that "implict" entity target provided by the AnimationPlayer into the AnimationEventTrigger - Encode the "animation event trigger-ness" of the event into the type itself (by requiring `#[event(trigger = AnimationEventTrigger)]`) - By not implementing Default for AnimationEventTrigger, we can block animation events from being fired manually by the user. ### Draft TODO - [x] Fill in documentation and update existing docs - [ ] Benchmark: I expect this impl to be significantly faster. There might also be tangible binary size improvements, as I've removed a lot of redundant codegen. - [x] Update release notes and migration guides ### Next Steps - The `BufferedEvent -> Message` rename was not included to keep the size down. Fixes #19648 --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Jan Hohenheim <jan@hohenheim.ch> |
||
|
|
90ae57d8ac |
use affine inverse in picking (#20713)
# Objective - affine inverse not mat4 inverse ## Solution - do it yet again ## Testing - debug_picking example |
||
|
|
5058f8a9e6 |
Improve On Terminology (#20648)
# Objective Fixes #19263 (and expands on it) Within `Observers`, we have started to distance ourselves from the "trigger" terminology. Specifically, we have renamed `Trigger` to `On`. I think this was a very good move, but we're currently in an awkward middle ground state. Users interact with `On` as if it were an event: `On<E>` exposes the event and derefs directly to it. I think we should embrace this mindset fully, in the interest of clarity. ## Solution - Rename all `trigger: On<SomeEvent>` cases to `event: On<SomeEvent>`. - Rename `On::target` to `On::entity`. This reads _much_ better when writing `query.get(event.entity())` and pairs more effectively with the `EntityEvent` terminology. - Rename `On::original_target` to `On::original_entity`, for the same reasons. - Take advantage of the `Deref` behavior where appropriate ```rust // Before entity.observe(|trigger: On<Explode>| { println!("{} exploded!", trigger.target()); }) // After entity.observe(|event: On<Explode>| { println!("{} exploded!", event.entity()); }) ``` |
||
|
|
7b98b7b9da |
Use bevy_shader in pbr, core pipelines, sprite, aa, gizmos, feathers, solari, dev_tools instead of bevy_render::shader re-export (#20493)
# Objective - Prepare for removing re-exports - Probably depends on #20491 merging first ## Solution - title ## Testing - cargo check --examples --all-features --------- Co-authored-by: Tim Blackbird <JustTheCoolDude@gmail.com> |
||
|
|
67b4ecdb01 |
Use bevy_light in pbr instead of bevy_pbr::light re-export + add bevy_light prelude (#20488)
# Objective - Prepare for removing re-exports ## Solution - title ## Testing - cargo check --examples |
||
|
|
ef845e0cea |
Update rand, glam and encase to latest versions (#18047)
# Objective New `rand` version, which means updating `glam` and `encase` to support the newer ecosystem update. Does mean that this changes how WASM builds need to be done in order to configure `getrandom` correctly, but this can be remedied with updated docs. ## Solution Updating all needed dependencies to their compatible versions. ~~This PR is currently blocked by `encase`, which is waiting on [this PR](https://github.com/teoxoy/encase/pull/88) to be merged and then a new version published.~~ ~~This PR is no longer blocked~~, ~~`hexasphere` is blocking this PR now due to not yet having a new release with the latest `glam` version support~~, The PR is all good to go now, everything in order across glam/rand deps. ## Testing - Must pass CI for all checks, tests, not introduce breaking changes. --- ## Migration Guide With newer versions of `glam` & `encase`, the updated versions don't seem to have introduced breakages, though as always, best to consult their docs [1](https://docs.rs/glam/latest/glam/) [2](https://docs.rs/encase/0.11.0/encase/) for any changes. `rand` changes are more extensive, with changes such as `thread_rng()` -> `rng()`, `from_entropy()` -> `from_os_rng()`, and so forth. `RngCore` is now split into infallible `RngCore` and fallible `TryRngCore`, and the `distributions` module has been renamed to `distr`. Most of this affects only internals, and doesn't directly affect Bevy's APIs. For the full set of changes, see `rand` [migration notes](https://rust-random.github.io/book/update-0.9.html). `getrandom` is also updated, and will require additional configuration when building Bevy for WASM/Web (if also using `rand`). The full details of how to do this is in the `getrandom` docs [1](https://github.com/rust-random/getrandom?tab=readme-ov-file#opt-in-backends) [2](https://github.com/rust-random/getrandom?tab=readme-ov-file#webassembly-support). --------- Co-authored-by: François Mockers <francois.mockers@vleue.com> |
||
|
|
0fc17e9bc1 |
Trim dependencies. (#20426)
# Objective Remove unneeded dependencies. ## Solution Found using `cargo-shear`, `cargo-machete`, and `cargo-udeps`. They all do the same thing (identify unused dependencies), they are all so-so at it (with lots of false negatives and positives) but the combination of all three is enough to get a useful outcome. ## Testing - I double-checked all the removals by grepping for each removed dependency's name within the affected crate, to make sure there weren't any uses remaining. I think they're all ok. - `cargo run -p ci` Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: James Liu <contact@jamessliu.com> |
||
|
|
4b1b70d501 |
Deprecate iter_entities and iter_entities_mut (#20260)
# Objective Both `iter_entities()` and `iter_entities_mut` are awkward functions as they both return _all_ entities, which is something you (should) rarely want. ## Solution Deprecate both functions and substitute them where they are used. ## Testing Not necessary. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com> |
||
|
|
6354a950ee |
Adjust benches to be more robust (#20207)
# Objective Currently, benchmarks access the world's entities in a very unsafe way, which doesn't hold up if we add more internal entities. Part of #19711. ## Solution Have the setups return a `Vec<Entity>` we can iterate over. ## Testing Not needed. |