Compare commits

...

95 Commits

Author SHA1 Message Date
François Mockers
b231ebbc19
Release 0.14.0 version bump (#14126)
# Objective

- Bump the version before the release

- This should not be merged until ready for the release to have prettier
git history and tags
2024-07-03 18:22:10 -07:00
Rob Parrett
201dd62a74
Fix border color in ui_texture_slice and ui_texture_atlas_slice examples. (#14121)
# Objective

Fixes #14120

`ui_texture_slice` and `ui_texture_atlas_slice` were working as
intended, so undo the changes.

## Solution

Partially revert https://github.com/bevyengine/bevy/pull/14115 for
`ui_texture_slice` and `ui_texture_atlas_slice`.

## Testing

Ran those two examples, confirmed the border color is the thing that
changes when buttons are hovered.
2024-07-03 17:28:47 +02:00
François Mockers
ff070da7e2
fix remaining issues with background color in examples (#14115)
# Objective

- Fixes #14097

## Solution

- Switching the uses of `UiImage` in examples to `BackgroundColor` when
needed
2024-07-03 07:52:06 +02:00
Gagnus
1db0214f24
Added feature switch to default Standard Material's new anisotropy texture to off (#14048)
# Objective

- Standard Material is starting to run out of samplers (currently uses
13 with no additional features off, I think in 0.13 it was 12).
- This change adds a new feature switch, modelled on the other ones
which add features to Standard Material, to turn off the new anisotropy
feature by default.

## Solution

- feature + texture define

## Testing

- Anisotropy example still works fine
- Other samples work fine
- Standard Material now takes 12 samplers by default on my Mac instead
of 13

## Migration Guide

- Add feature pbr_anisotropy_texture if you are using that texture in
any standard materials.

---------

Co-authored-by: John Payne <20407779+johngpayne@users.noreply.github.com>
2024-07-02 22:10:00 +02:00
Mincong Lu
309c224ca8
Added get_main_animation for AnimationTransitions (#14104)
# Objective

Added a getter for the main animation of `AnimationTransitions`.

## Solution

Added `get_main_animation` for `AnimationTransitions`.
2024-07-02 22:10:00 +02:00
Vic
24fdad3a36
add missing mention of sort_unstable_by_key in QuerySortedIter docs (#14108)
# Objective

There is a missing mention of `sort_unstable_by_key` in the
`QuerySortedIter` docs.

## Solution

Add it.
2024-07-02 22:10:00 +02:00
Jan Hohenheim
e7333510c3
Fix push_children inserting a Children component even when no children are supplied (#14109)
# Objective

The Bevy API around manipulating hierarchies removes `Children` if the
operation results in an entity having no children. This means that
`Children` is guaranteed to hold actual children. However, the following
code unexpectedly inserts empty `Children`:

```rust
commands.entity(entity).with_children(|_| {});
```

This was discovered by @Jondolf:
https://discord.com/channels/691052431525675048/1124043933886976171/1257660865625325800

## Solution

- `with_children` is now a noop when no children were passed

## Testing

- Added a regression test
2024-07-02 22:10:00 +02:00
Alice Cecile
31b861401c
Backport #14083 (deregister events) to 0.14 branch (#14114)
Changes by @lee-orr. Fixes #14113.

Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com>
2024-07-02 16:01:57 -04:00
Joseph
99c465dcb5
Clarify the difference between default render layers and none render layers (#14075)
# Objective

It's not always obvious what the default value for `RenderLayers`
represents. It is documented, but since it's an implementation of a
trait method the documentation may or may not be shown depending on the
IDE.

## Solution

Add documentation to the `none` method that explicitly calls out the
difference.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-07-02 07:18:21 +02:00
Joseph
d7a0cc6bce
Support operations for render layers and fix equality comparisons (#13310)
# Objective

Allow combining render layers with a more-ergonomic syntax than
`RenderLayers::from_iter(a.iter().chain(b.iter()))`.

## Solution

Add the `or` operation (and corresponding `const` method) to allow
computing the union of a set of render layers. While we're here, also
added `and` and `xor` operations. Someone might find them useful

## Testing

Added a simple unit test.
2024-07-02 07:18:21 +02:00
Aevyrie
fda2e4b59c
Fix compile failure in WASM without wgpu backend (#14081)
# Objective

- When no wgpu backend is selected, there should be a clear explanation.
- Fix a regression in 0.14 when not using default features. I hit this
compile failure when trying to build bevy_framepace for 0.14.0-rc.4
```
error[E0432]: unresolved import `crate::core_3d::DEPTH_TEXTURE_SAMPLING_SUPPORTED`
  --> /Users/aevyrie/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_core_pipeline-0.14.0-rc.4/src/dof/mod.rs:59:19
   |
59 |         Camera3d, DEPTH_TEXTURE_SAMPLING_SUPPORTED,
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `DEPTH_TEXTURE_SAMPLING_SUPPORTED` in `core_3d`
   |
note: found an item that was configured out
  --> /Users/aevyrie/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_core_pipeline-0.14.0-rc.4/src/core_3d/mod.rs:53:11
   |
53 | pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = false;
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: found an item that was configured out
  --> /Users/aevyrie/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_core_pipeline-0.14.0-rc.4/src/core_3d/mod.rs:63:11
   |
63 | pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true;
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

## Solution

- Ensure that `DEPTH_TEXTURE_SAMPLING_SUPPORTED` is either `true` or
`false`, it shouldn't be completely missing.

## Testing

- Building on WASM without default features, which now seemingly no
longer includes webgl, will panic on startup with a message saying that
no wgpu backend was selected. This is much more helpful than the compile
time failure:
```
No wgpu backend feature that is implemented for the target platform was enabled
```
- I can see an argument for making this a compile time failure, however
the current failure mode is very confusing for novice users, and
provides no clues for how to fix it. If we want this to fail at compile
time, we should do it in a way that fails with a helpful message,
similar to what this PR acheives.
2024-07-02 07:18:20 +02:00
François Mockers
dc56614b86
only run one update per frame drawn (#14023)
# Objective

- Fixes #13965 

## Solution

- Don't run multiple updates for a single frame
2024-06-30 10:01:10 +02:00
François Mockers
4a05c737a2
don't put previous skin/morph in the morphed_skinned_mesh_layout (#14065)
# Objective

- Fixes #14059
- `morphed_skinned_mesh_layout` is the same as
`morphed_skinned_motion_mesh_layout` but shouldn't have the skin / morph
from previous frame, as they're used for motion

## Solution

- Remove the extra entries

## Testing

- Run with the glTF file reproducing #14059, it works
2024-06-29 03:29:45 +02:00
François
036d0026be
Release Candidate 0.14.0-rc.4 2024-06-27 23:29:08 +02:00
Periwink
73b43aa6cf
Fix error in AnyOf (#14027)
# Objective

- Fixes a correctness error introduced in
https://github.com/bevyengine/bevy/pull/14013 ...

## Solution

I've been playing around a lot of with the access code and I realized
that I introduced a soundness error when trying to simplify the code.
When we have a `Or<(With<A>, With<B>)>` filter, we cannot call
```
  let mut intermediate = FilteredAccess::default();
  $name::update_component_access($name, &mut intermediate);
  _new_access.append_or(&intermediate);
```
because that's just equivalent to adding the new components as `Or`
clauses.
For example if the existing `filter_sets` was `vec![With<C>]`, we would
then get `vec![With<C>, With<A>, With<B>]` which translates to `A or B
or C`.
Instead what we want is `(A and B) or (A and C)`, so we need to have
each new OR clause compose with the existing access like so:
```
let mut intermediate = _access.clone();
// if we previously had a With<C> in the filter_set, this will become `With<C> AND With<A>`
$name::update_component_access($name, &mut intermediate);
_new_access.append_or(&intermediate);
```

## Testing

- Added a unit test that is broken in main, but passes in this PR
2024-06-27 22:47:36 +02:00
Tamás Kiss
20638f3a10
add PartialEq to Outline (#14055)
# Objective

`sickle_ui` needs `PartialEq` on components to turn them into animatable
style attributes.

## Solution

All properties of Outline is already `PartialEq`, add derive on
`Outline` as well.

## Testing

- used `sickle_ui` to test if it can be made animatable
2024-06-27 22:47:36 +02:00
Patrick Walton
26f7313212
Allow phase items not associated with meshes to be binned. (#14029)
As reported in #14004, many third-party plugins, such as Hanabi, enqueue
entities that don't have meshes into render phases. However, the
introduction of indirect mode added a dependency on mesh-specific data,
breaking this workflow. This is because GPU preprocessing requires that
the render phases manage indirect draw parameters, which don't apply to
objects that aren't meshes. The existing code skips over binned entities
that don't have indirect draw parameters, which causes the rendering to
be skipped for such objects.

To support this workflow, this commit adds a new field,
`non_mesh_items`, to `BinnedRenderPhase`. This field contains a simple
list of (bin key, entity) pairs. After drawing batchable and unbatchable
objects, the non-mesh items are drawn one after another. Bevy itself
doesn't enqueue any items into this list; it exists solely for the
application and/or plugins to use.

Additionally, this commit switches the asset ID in the standard bin keys
to be an untyped asset ID rather than that of a mesh. This allows more
flexibility, allowing bins to be keyed off any type of asset.

This patch adds a new example, `custom_phase_item`, which simultaneously
serves to demonstrate how to use this new feature and to act as a
regression test so this doesn't break again.

Fixes #14004.

## Changelog

### Added

* `BinnedRenderPhase` now contains a `non_mesh_items` field for plugins
to add custom items to.
2024-06-27 19:41:42 +02:00
Chris Russell
bea8823aa9
Add missing StaticSystemParam::queue implementation. (#14051)
# Objective

`StaticSystemParam` should delegate all `SystemParam` methods to the
inner param, but it looks like it was missed when the new `queue()`
method was added in #10839.

## Solution

Implement `StaticSystemParam::queue()` to delegate to the inner param.
2024-06-27 19:41:42 +02:00
Vic
7b98db6d7c
add missing sort_unstable_by_key to QueryIter (#14040)
# Objective

`QueryIter::sort_unstable_by_key` is missing.

## Solution

Add `QueryIter::sort_unstable_by_key`.

## Testing

Added the new method to existing test.

## Changelog

Added `QueryIter::sort_unstable_by_key`.
2024-06-27 19:41:42 +02:00
Arseny Kapoulkine
7c603874bf
Fix incorrect computation of mips for cluster occlusion lookup (#14042)
The comment was incorrect - we are already looking at the pyramid
texture so we do not need to transform the size in any way. Doing that
resulted in a mip that was too fine to be selected in certain cases,
which resulted in a 2x2 pixel footprint not actually fully covering the
cluster sphere - sometimes this could lead to a non-conservative depth
value being computed which resulted in the cluster being marked as
invisible incorrectly.
2024-06-27 19:41:42 +02:00
Arseny Kapoulkine
5d7da827b7
Improve MeshletMesh::from_mesh performance further (#14038)
This change updates meshopt-rs to 0.3 to take advantage of the newly
added sparse simplification mode: by default, simplifier assumes that
the entire mesh is simplified and runs a set of calculations that are
O(vertex count), but in our case we simplify many small mesh subsets
which is inefficient.

Sparse mode instead assumes that the simplified subset is only using a
portion of the vertex buffer, and optimizes accordingly. This changes
the meaning of the error (as it becomes relative to the subset, in our
case a meshlet group); to ensure consistent error selection, we also use
the ErrorAbsolute mode which allows us to operate in mesh coordinate
space.

Additionally, meshopt 0.3 runs optimizeMeshlet automatically as part of
`build_meshlets` so we no longer need to call it ourselves.

This reduces the time to build meshlet representation for Stanford Bunny
mesh from ~1.65s to ~0.45s (3.7x) in optimized builds.
2024-06-27 07:05:24 +02:00
Arseny Kapoulkine
4f0b0e0989
Make meshlet processing deterministic (#13913)
This is a followup to https://github.com/bevyengine/bevy/pull/13904
based on the discussion there, and switches two HashMaps that used
meshlet ids as keys to Vec.

In addition to a small further performance boost for `from_mesh` (1.66s
=> 1.60s), this makes processing deterministic modulo threading issues
wrt CRT rand described in the linked PR. This is valuable for debugging,
as you can visually or programmatically inspect the meshlet distribution
before/after making changes that should not change the output, whereas
previously every asset rebuild would change the meshlet structure.

Tested with https://github.com/bevyengine/bevy/pull/13431; after this
change, the visual output of meshlets is consistent between asset
rebuilds, and the MD5 of the output GLB file does not change either,
which was not the case before.
2024-06-27 07:05:24 +02:00
Arseny Kapoulkine
cc1764772e
Improve MeshletMesh::from_mesh performance (#13904)
This change reworks `find_connected_meshlets` to scale more linearly
with the mesh size, which significantly reduces the cost of building
meshlet representations. As a small extra complexity reduction, it moves
`simplify_scale` call out of the loop so that it's called once (it only
depends on the vertex data => is safe to cache).

The new implementation of connectivity analysis builds edge=>meshlet
list data structure, which allows us to only iterate through
`tuple_combinations` of a (usually) small list. There is still some
redundancy as if two meshlets share two edges, they will be represented
in the meshlet lists twice, but it's overall much faster.

Since the hash traversal is non-deterministic, to keep this part of the
algorithm deterministic for reproducible results we sort the output
adjacency lists.

Overall this reduces the time to process bunny mesh from ~4.2s to ~1.7s
when using release; in unoptimized builds the delta is even more
significant.

This was tested by using https://github.com/bevyengine/bevy/pull/13431
and:

a) comparing the result of `find_connected_meshlets` using old and new
code; they are equal in all steps of the clustering process
b) comparing the rendered result of the old code vs new code *after*
making the rest of the algorithm deterministic: right now the loop that
iterates through the result of `group_meshlets()` call executes in
different order between program runs. This is orthogonal to this change
and can be fixed separately.

Note: a future change can shrink the processing time further from ~1.7s
to ~0.4s with a small diff but that requires an update to meshopt crate
which is pending in https://github.com/gwihlidal/meshopt-rs/pull/42.
This change is independent.
2024-06-27 07:05:24 +02:00
Michael "Scott" McBee
99db59c176
Have WindowPosition::Centered take scale_factor_override into account (#13949)
# Objective

Fixes #8916 

My game has a low resolution pixel art style, and I use
`.with_scale_factor_override()` to make the window larger.
`WindowPosition::Centered` doesn't work for me.

## Solution

If `scale_factor_override` is set, use that over `monitor.scale_factor`

## Testing

Tested on Windows 11 with an Nvidia GPU:

### Main

![image](https://github.com/bevyengine/bevy/assets/3324533/5f9ae90e-b65a-48d9-b601-117df8f08a28)

### This PR

![image](https://github.com/bevyengine/bevy/assets/3324533/cd860611-7b6a-4ae5-b690-28d9ba8ea6ad)
2024-06-26 21:57:12 +02:00
Martin Svanberg
9a4de9c54d
Fix typo in CPU adapter warning (#14037)
An annoying typo slipped through in #13780
2024-06-26 21:56:23 +02:00
Joseph
a6feb5ba74
Emit a warning if the result of EntityCommand::with_entity is not used (#14028)
When using combinators such as `EntityCommand::with_entity` to build
commands, it can be easy to forget to apply that command, leading to
dead code. In many cases this doesn't even lead to an unused variable
warning, which can make these mistakes difficult to track down

Annotate the method with `#[must_use]`

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-06-26 21:56:23 +02:00
François Mockers
e65e4be98c
fix examples color_grading and mobile after BackgroundColor changes (#14033)
# Objective

- #14017 changed how `UiImage` and `BackgroundColor` work
- one change was missed in example `color_grading`, another in the
mobile example

## Solution

- Change it in the examples
2024-06-26 21:55:00 +02:00
François Mockers
27aa9d2b7e
example showcase: keep the order of the shaders imported (#14035)
# Objective

- After #13908, shaders imported are collected
- this is done with a hashset, so order is random between executions

## Solution

- Don't use a hashset, and keep the order they were found in the example
2024-06-26 21:55:00 +02:00
Martin Svanberg
f8e165be0a
Print warning when using llvmpipe (#13780)
# Objective

Numerous people have been confused that Bevy runs slowly, when the
reason is that the `llvmpipe` software rendered is being used.

## Solution

Printing a warning could reduce the confusion.
2024-06-26 21:55:00 +02:00
François Mockers
4736fe0dea
don't crash without features bevy_pbr, ktx2, zstd (#14020)
# Objective

- Fixes #13728 

## Solution

- add a new feature `smaa_luts`. if enables, it also enables `ktx2` and
`zstd`. if not, it doesn't load the files but use placeholders instead
- adds all the resources needed in the same places that system that uses
them are added.
2024-06-26 21:55:00 +02:00
Periwink
f89f7f306c
AnyOf soundness fix (#14013)
# Objective
Fixes https://github.com/bevyengine/bevy/issues/13993 
PR inspired by https://github.com/bevyengine/bevy/pull/14007 to
accomplish the same thing, but maybe in a clearer fashion.

@Gingeh feel free to take my changes and add them to your PR, I don't
want to steal any credit

---------

Co-authored-by: Gingeh <39150378+Gingeh@users.noreply.github.com>
Co-authored-by: Bob Gardner <rgardner@inworld.ai>
Co-authored-by: Martín Maita <47983254+mnmaita@users.noreply.github.com>
2024-06-26 02:31:20 +02:00
Alice Cecile
82f01569e8
Make default behavior for BackgroundColor and BorderColor more intuitive (#14017)
# Objective

In Bevy 0.13, `BackgroundColor` simply tinted the image of any
`UiImage`. This was confusing: in every other case (e.g. Text), this
added a solid square behind the element. #11165 changed this, but
removed `BackgroundColor` from `ImageBundle` to avoid confusion, since
the semantic meaning had changed.

However, this resulted in a serious UX downgrade / inconsistency, as
this behavior was no longer part of the bundle (unlike for `TextBundle`
or `NodeBundle`), leaving users with a relatively frustrating upgrade
path.

Additionally, adding both `BackgroundColor` and `UiImage` resulted in a
bizarre effect, where the background color was seemingly ignored as it
was covered by a solid white placeholder image.

Fixes #13969.

## Solution

Per @viridia's design:

> - if you don't specify a background color, it's transparent.
> - if you don't specify an image color, it's white (because it's a
multiplier).
> - if you don't specify an image, no image is drawn.
> - if you specify both a background color and an image color, they are
independent.
> - the background color is drawn behind the image (in whatever pixels
are transparent)

As laid out by @benfrankel, this involves:

1. Changing the default `UiImage` to use a transparent texture but a
pure white tint.
2. Adding `UiImage::solid_color` to quickly set placeholder images.
3. Changing the default `BorderColor` and `BackgroundColor` to
transparent.
4. Removing the default overrides for these values in the other assorted
UI bundles.
5. Adding `BackgroundColor` back to `ImageBundle` and `ButtonBundle`.
6. Adding a 1x1 `Image::transparent`, which can be accessed from
`Assets<Image>` via the `TRANSPARENT_IMAGE_HANDLE` constant.

Huge thanks to everyone who helped out with the design in the linked
issue and [the Discord
thread](https://discord.com/channels/691052431525675048/1255209923890118697/1255209999278280844):
this was very much a joint design.

@cart helped me figure out how to set the UiImage's default texture to a
transparent 1x1 image, which is a much nicer fix.

## Testing

I've checked the examples modified by this PR, and the `ui` example as
well just to be sure.

## Migration Guide

- `BackgroundColor` no longer tints the color of images in `ImageBundle`
or `ButtonBundle`. Set `UiImage::color` to tint images instead.
- The default texture for `UiImage` is now a transparent white square.
Use `UiImage::solid_color` to quickly draw debug images.
- The default value for `BackgroundColor` and `BorderColor` is now
transparent. Set the color to white manually to return to previous
behavior.
2024-06-26 00:16:30 +02:00
JMS55
65daab8517
Fix MeshletMesh material system ordering (#14016)
# Objective
- Fixes #13811 (probably, I lost my test code...)

## Solution
- Turns out that Queue and PrepareAssets are _not_ ordered. We should
probably either rethink our system sets (again), or improve the
documentation here. For reference, I've included the current ordering
below.
- The `prepare_meshlet_meshes_X` systems need to run after
`prepare_assets::<PreparedMaterial<M>>`, and have also been moved to
QueueMeshes.

```rust
schedule.configure_sets(
    (
        ExtractCommands,
        ManageViews,
        Queue,
        PhaseSort,
        Prepare,
        Render,
        Cleanup,
    )
        .chain(),
);

schedule.configure_sets((ExtractCommands, PrepareAssets, Prepare).chain());
schedule.configure_sets(QueueMeshes.in_set(Queue).after(prepare_assets::<GpuMesh>));
schedule.configure_sets(
    (PrepareResources, PrepareResourcesFlush, PrepareBindGroups)
        .chain()
        .in_set(Prepare),
);
```

## Testing
- Ambiguity checker to make sure I don't have ambiguous system ordering
2024-06-26 00:16:22 +02:00
Alice Cecile
9eb547ec1f
Don't show .to_bits in Display impl for Entity (#14011)
accurate for debugging. To ensure that its can still be readily logged
in error messages and inspectors, this PR added a more concise and
human-friendly `Display` impl.

However, users found this form too verbose: the `to_bits` information
was unhelpful and too long. Fixes #13980.

- Don't include `Entity::to_bits` in the `Display` implementation for
`Entity`. This information can readily be accessed and logged for users
who need it.
- Also clean up the implementation of `Display` for `DebugName`,
introduced in https://github.com/bevyengine/bevy/pull/13760, to simply
use the new `Display` impl (since this was the desired format there).

I've updated an existing test to verify the output of `Entity::display`.

---------

Co-authored-by: Kristoffer Søholm <k.soeholm@gmail.com>
2024-06-26 00:16:22 +02:00
Sou1gh0st
8c1b9a6c18
feat(bevy_app): expose an API to perform updates for a specific sub-app. (#14009)
# Objective

- Fixes https://github.com/bevyengine/bevy/issues/14003

## Solution

- Expose an API to perform updates for a specific sub-app, so we can
avoid mutable borrow the app twice.

## Testing

- I have tested the API by modifying the code in the `many_lights`
example with the following changes:
```rust
impl Plugin for LogVisibleLights {
    fn build(&self, app: &mut App) {
        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app.add_systems(Render, print_visible_light_count.in_set(RenderSet::Prepare));
    }

    fn finish(&self, app: &mut App) {
        app.update_sub_app_by_label(RenderApp);
    }
}
```

---

## Changelog
- add the `update_sub_app_by_label` API to `App` and `SubApps`.

---------

Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
2024-06-26 00:13:24 +02:00
Tamás Kiss
a0e7429363
fix panic: invalid SlotMap key used (#13990)
# Objective

Tight, in-frame generation, re-parenting, despawning, etc., UI
operations could sometime lead taffy to panic (invalid SlotMap key used)
when an entity with an invalid state later despawned.

Fixes #12403 

## Solution

Move the `remove_entities` call after children updates.

## Testing

`sickle_ui` had a case that always caused the panic. Tested before this
change, after this change, and before the change again to make sure the
error is there without the fix. The fix worked. Test steps and used
commit described in issue #12403.

I have also ran every bevy UI example, though none of them deal with
entity re-parenting or removal. No regression detected on them.

Tested on Windows only.
2024-06-26 00:13:24 +02:00
Bob Gardner
a41ed7822f
Fixing par_read targetting 0.14 branch (#14014)
Fix for par_read bugs retargetting 0.14.

Remakes https://github.com/bevyengine/bevy/pull/13836.
2024-06-26 00:09:00 +02:00
JMS55
b56a693c34
Fix meshlet interactions with regular shading passes (#13816)
* Fixes https://github.com/bevyengine/bevy/issues/13813
* Fixes https://github.com/bevyengine/bevy/issues/13810

Tested a combined scene with both regular meshes and meshlet meshes
with:
* Regular forward setup
* Forward + normal/motion vector prepasses
* Deferred (with depth prepass since that's required) 
* Deferred + depth/normal/motion vector prepasses

Still broken:
* Using meshlet meshes rendering in deferred and regular meshes
rendering in forward + depth/normal prepass. I don't know how to fix
this at the moment, so for now I've just add instructions to not mix
them.
2024-06-21 21:32:33 +02:00
Carter Anderson
073db8cf36
Make Observer::with_event (and other variants) unsafe (#13954)
# Objective

`with_event` will result in unsafe casting of event data of the given
type to the type expected by the Observer system. This is inherently
unsafe.

## Solution

Flag `Observer::with_event` and `ObserverDescriptor::with_events` as
unsafe. This will not affect normal workflows as `with_event` is
intended for very specific (largely internal) use cases.

This _should_ be backported to 0.14 before release.

---

## Changelog

- `Observer::with_event` is now unsafe.
- Rename `ObserverDescriptor::with_triggers` to
`ObserverDescriptor::with_events` and make it unsafe.
2024-06-21 20:53:13 +02:00
François Mockers
8af12c8775
apply window scale to window size when creating it (#13967)
# Objective

- Fixes #13702
- When creating a new window, its scale was changed to match the one
returned by winit, but its size was not which resulted in an incorrect
size until the event with the correct size was received, at least 1
frame later

## Solution

- Apply the window scale to its size when creating it
2024-06-21 20:27:55 +02:00
charlotte
e73063e1d5
Correctly check physical size when updating winit (#13942)
Fixes #13701

After `winit` upgrade to `0.31`, windows were no longer correctly
resizing. This appears to just have been a simple mistake, where the new
physical size was being sourced from the `winit` window rather than on
the incoming `Window` component.

## Testing

Tested on macOS, but I'm curious whether this was also broken on other
platforms.
2024-06-21 19:36:48 +02:00
Chris Biscardi
cf66df1d0d
Use a ship in Transform::align example (#13935)
# Objective

The documentation for
[`Transform::align`](https://docs.rs/bevy/0.14.0-rc.3/bevy/transform/components/struct.Transform.html#method.align)
mentions a hypothetical ship model. Showing this concretely would be a
nice improvement over using a cube.

> For example, if a spaceship model has its nose pointing in the
X-direction in its own local coordinates and its dorsal fin pointing in
the Y-direction, then align(Dir3::X, v, Dir3::Y, w) will make the
spaceship’s nose point in the direction of v, while the dorsal fin does
its best to point in the direction w.


## Solution

This commit makes the ship less hypothetical by using a kenney ship
model in the example.

The local axes for the ship needed to change to accommodate the gltf, so
the hypothetical in the documentation and this example's local axes
don't necessarily match. Docs use `align(Dir3::X, v, Dir3::Y, w)` and
this example now uses `(Vec3::NEG_Z, *first, Vec3::X, *second)`.

I manually modified the `craft_speederD` Node's `translation` to be
0,0,0 in the gltf file, which means it now differs from kenney's
original model.

Original ship from: https://kenney.nl/assets/space-kit

## Testing

```
cargo run --example align
```

![screenshot-2024-06-19-at-14 27
05@2x](https://github.com/bevyengine/bevy/assets/551247/ab1afc8f-76b2-42b6-b455-f0d1c77cfed7)
![screenshot-2024-06-19-at-14 27
12@2x](https://github.com/bevyengine/bevy/assets/551247/4a01031c-4ea1-43ab-8078-3656db67efe0)
![screenshot-2024-06-19-at-14 27
20@2x](https://github.com/bevyengine/bevy/assets/551247/06830f38-ba2b-4e3a-a265-2d10f9ea9de9)
2024-06-21 19:36:48 +02:00
MiniaczQ
783fc29cd3
Move StateTransitionSteps registration to states plugin (#13939)
# Objective

Fixes #13920

## Solution

As described in the issue.

## Testing

Moved a custom transition plugin in example before any of the app-state
methods.
2024-06-21 19:36:48 +02:00
Shane Celis
47ad37ec84
bug: Fix 9-slice textures with asymmetric borders. (#13921)
# Objective

Fix a 9-slice asymmetric border issue that
[QueenOfSquiggles](https://blobfox.coffee/@queenofsquiggles/112639035165575222)
found. Here's the behavior before:

<img width="340" alt="the-bug"
src="https://github.com/bevyengine/bevy/assets/54390/81ff1847-b2ea-4578-9fd0-af6ee96c5438">

## Solution

Here's the behavior with the fix.

<img width="327" alt="the-fix"
src="https://github.com/bevyengine/bevy/assets/54390/33a4e3f0-b6a8-448e-9654-1197218ea11d">


## Testing

I used QueenOfSquiggles
[repo](https://github.com/QueenOfSquiggles/my-bevy-learning-project) to
exercise the code. I manually went through a number of variations of the
border and caught a few other issues after the first pass. I added some
code to create random borders and though they often looked funny there
weren't any gaps like before.

### Unit Tests

I did add some tests to `slicer.rs` mostly as an exploratory programming
exercise. So they currently act as a limited, incomplete,
"golden-file"-ish approach. Perhaps they're not worth keeping.

In order to write the tests, I did add a `PartialEq` derive for
`TextureSlice`.

I only tested these changes on macOS.

---

## Changelog

Make 9-slice textures work with asymmetric borders.
2024-06-21 19:36:48 +02:00
AndrewDanial
472a6e8283
Shader code paths (#13908)
# Objective

Add extra metadata for the shader examples that contains the location of
their associated shader file(s). This is to be used for the bevy website
shader examples so that the shader code is underneath the rust code.

## Solution

Parse the example rust files for mentions of `.wgsl`, `.frag`, and
`.vert`, then append the found paths to a field called
`shader_code_paths` in the generated `index.md`s for each shader
example.
2024-06-19 18:54:28 +02:00
James O'Brien
5d5e67fa35
Update observer archetype flags for sparse components (#13886)
# Objective

- Fixes #13885 

## Solution

- Update the flags correctly on archetype creation

## Testing

- Added `observer_order_insert_remove_sparse` to catch regressions.
2024-06-19 04:14:58 +02:00
Jan Hohenheim
b74b3b2123
Add first person view model example (#13828)
# Objective

A very common way to organize a first-person view is to split it into
two kinds of models:

 - The *view model* is the model that represents the player's body.
 - The *world model* is everything else.

The reason for this distinction is that these two models should be
rendered with different FOVs.
The view model is typically designed and animated with a very specific
FOV in mind, so it is
generally *fixed* and cannot be changed by a player. The world model, on
the other hand, should
be able to change its FOV to accommodate the player's preferences for
the following reasons:
- *Accessibility*: How prone is the player to motion sickness? A wider
FOV can help.
- *Tactical preference*: Does the player want to see more of the
battlefield?
 Or have a more zoomed-in view for precision aiming?
- *Physical considerations*: How well does the in-game FOV match the
player's real-world FOV?
Are they sitting in front of a monitor or playing on a TV in the living
room? How big is the screen?

## Solution

I've added an example implementing the described setup as follows.

The `Player` is an entity holding two cameras, one for each model. The
view model camera has a fixed
FOV of 70 degrees, while the world model camera has a variable FOV that
can be changed by the player.

 I use different `RenderLayers` to select what to render.

- The world model camera has no explicit `RenderLayers` component, so it
uses the layer 0.
All static objects in the scene are also on layer 0 for the same reason.
- The view model camera has a `RenderLayers` component with layer 1, so
it only renders objects
explicitly assigned to layer 1. The arm of the player is one such
object.
The order of the view model camera is additionally bumped to 1 to ensure
it renders on top of the world model.
- The light source in the scene must illuminate both the view model and
the world model, so it is
 assigned to both layers 0 and 1.

To better see the effect, the player can move the camera by dragging
their mouse and change the world model's FOV with the arrow keys. The
arrow up key maps to "decrease FOV" and the arrow down key maps to
"increase FOV". This sounds backwards on paper, but is more intuitive
when actually changing the FOV in-game since a decrease in FOV looks
like a zoom-in.
I intentionally do not allow changing the view model's FOV even though
it would be illustrative because that would be an anti-pattern and bloat
the code a bit.

The example is called `first_person_view_model` and not just
`first_person` because I want to highlight that this is not a simple
flycam, but actually renders the player.

## Testing

Default FOV:
<img width="1392" alt="image"
src="https://github.com/bevyengine/bevy/assets/9047632/8c2e804f-fac2-48c7-8a22-d85af999dfb2">

Decreased FOV:
<img width="1392" alt="image"
src="https://github.com/bevyengine/bevy/assets/9047632/1733b3e5-f583-4214-a454-3554e3cbd066">

Increased FOV:
<img width="1392" alt="image"
src="https://github.com/bevyengine/bevy/assets/9047632/0b0640e6-5743-46f6-a79a-7181ba9678e8">

Note that the white bar on the right represents the player's arm, which
is more obvious in-game because you can move the camera around.
The box on top is there to make sure that the view model is receiving
shadows.

I tested only on macOS.

---

## Changelog

I don't think new examples go in here, do they?

## Caveat

The solution used here was implemented with help by @robtfm on
[Discord](https://discord.com/channels/691052431525675048/866787577687310356/1241019224491561000):
> shadow maps are specific to lights, not to layers
> if you want shadows from some meshes that are not visible, you could
have light on layer 1+2, meshes on layer 2, camera on layer 1 (for
example)
> but this might change in future, it's not exactly an intended feature

In other words, the example code as-is is not guaranteed to work in the
future. I want to bring this up because the use-case presented here is
extremely common in first-person games and important for accessibility.
It would be good to have a blessed and easy way of how to achieve it.

I'm also not happy about how I get the `perspective` variable in
`change_fov`. Very open to suggestions :)

## Related issues

- Addresses parts of #12658
- Addresses parts of #12588

---------

Co-authored-by: Pascal Hertleif <killercup@gmail.com>
2024-06-19 04:14:58 +02:00
hut
4608708d8d
Fix phantom key presses in winit on focus change (#13299) (#13696)
# Objective

Fixes #13299

On Linux/X11, changing focus into a winit window will produce winit
KeyboardInput events with a "is_synthetic=true" flag that are not
intended to be used. Bevy erroneously passes them on to the user,
resulting in phantom key presses.

## Solution

This patch properly filters out winit KeyboardInput events with
"is_synthetic=true".

For example, pressing Alt+Tab to focus a bevy winit window results in a
permanently stuck Tab key until the user presses Tab once again to
produce a winit KeyboardInput release event. The Tab key press event
that causes this problem is "synthetic", should not be used according to
the winit devs, and simply ignoring it fixes this problem.

Synthetic key **releases** are still evaluated though, as they are
essential for correct release key handling. For example, if the user
binds the key combination Alt+1 to the action "move the window to
workspace 1", places the bevy game in workspace 2, focuses the game and
presses Alt+1, then the key release event for the "1" key will be
synthetic. If we would filter out all synthetic keys, the bevy game
would think that the 1 key remains pressed forever, until the user
manually presses+releases the key again inside bevy.

Reference:
https://docs.rs/winit/0.30.0/winit/event/enum.WindowEvent.html#variant.KeyboardInput.field.is_synthetic
Relevant discussion: https://github.com/rust-windowing/winit/issues/3543

## Testing

Tested with the "keyboard_input_events" example. Entering/exiting the
window with various keys, as well as changing its workspace, produces
the correct press/release events.
2024-06-19 04:14:58 +02:00
François Mockers
f2e66726b6
observers example doesn't follow standards (#13884)
# Objective

- Observers example is using an unseeded random, prints text to console
and doesn't display text as other examples

## Solution

- use seeded random
- log instead of printing
- use common settings for UI text
2024-06-19 04:14:58 +02:00
Kristoffer Søholm
165f399489
Make time_system public (#13879)
# Objective

If `time_system` isn't public you cannot order systems relative to it in
the `TimeSystem` set.

## Solution

Make it public
2024-06-19 04:14:58 +02:00
MiniaczQ
6cf04c213b
Warn about missing StatesPlugin when installing states (#13877)
# Objective

- Fixes #13874

## Solution

- Confirm that the `StatesPlugin` is installed when trying to add
states.
- Skipped for state scoped entities, since those will warn about missing
states.
2024-06-19 04:14:58 +02:00
Lee-Orr
1c838ff6b3
remove inaccurate warning from in_state (#13862)
# Objective
Fixes #13854

## Solution
Removed the inaccurate warning. This was done for a few reasons:

- States not existing is now a valid "state" (for lack of a better term)
- Other run conditions don't provide an equivalent warning
2024-06-19 04:14:58 +02:00
François
03f63e72cf
Release Candidate 0.14.0-rc.3 2024-06-16 17:50:19 +02:00
François Mockers
55ee49b7ad
text position: use size instead of bounds (#13858)
# Objective

- #13846 introduced a bug where text not bound was not displayed

## Solution

- bounds are infinite
- use computed size instead, that already should be using the available
bounds
2024-06-16 17:45:07 +02:00
Martín Maita
eca8220761
Generalised ECS reactivity with Observers (#10839) (#13873)
# Objective

- Fixes #13825 

## Solution

- Cherry picked and fixed non-trivial conflicts to be able to merge
#10839 into the 0.14 release branch.

Link to PR: https://github.com/bevyengine/bevy/pull/10839

Co-authored-by: James O'Brien <james.obrien@drafly.net>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2024-06-16 11:44:08 -04:00
MiniaczQ
5ed296ff03
Restore overwrite capabilities of insert_state (#13848)
# Objective

- Fixes #13844
- Warn user when initializing state multiple times

## Solution

- `insert_state` will overwrite previously initialized state value,
reset transition events and re-insert it's own transition event.
- `init_state`, `add_sub_state`, `add_computed_state` are idempotent, so
calling them multiple times will emit a warning.

## Testing

- 2 tests confirming overwrite works.
- Given the example from #13844
```rs
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_state(AppState::A)
        .insert_state(AppState::B)
        .add_systems(OnEnter(AppState::A), setup_a)
        .add_systems(OnEnter(AppState::B), setup_b)
        .add_systems(OnExit(AppState::A), cleanup_a)
        .add_systems(OnExit(AppState::B), cleanup_b)
        .run();
}

#[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
enum AppState {
    A,
    B,
}

fn setup_a() {
    info!("setting up A");
}

fn setup_b() {
    info!("setting up B");
}

fn cleanup_a() {
    info!("cleaning up A");
}

fn cleanup_b() {
    info!("cleaning up B");
}
```

We get the following result:
```
INFO states: setting up B
```
which matches our expectations.
2024-06-15 11:02:44 +02:00
Kristoffer Søholm
cc4681fc44
Fix is_plugin_added::<Self>() being true during build (#13817)
# Objective

Fixes #13815 

## Solution

Move insertion of the plugin name to after build is called.

## Testing

I added a regression test

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
Co-authored-by: François Mockers <francois.mockers@vleue.com>
2024-06-14 21:52:29 +02:00
robtfm
ccfae7ebe7
fix non-exact text h-alignment (#13846)
# Objective

when a parent container is auto-sized, text alignments `Center` and
`Right` don't align to the center and right properly. fix it

## Solution

ab_glyph positions return +/- values from an anchor point. we currently
transform them to positive values from the min-x of the glyphs, and then
offset from the left of the bounds. instead, we can keep the negative
values as ab_glyph intended and offset from the left/middle/right of the
bounds as appropriate.

## Testing

texts with align left, center, right, all contained in the purple boxes:
before (0.14.0-rc.2):
![Screenshot 2024-06-14
165456](https://github.com/bevyengine/bevy/assets/50659922/90fb73b0-d8bd-4ae8-abf3-7106eafc93ba)

after:

![Screenshot 2024-06-14
164449](https://github.com/bevyengine/bevy/assets/50659922/0a75ff09-b51d-4fbe-a491-b655a145c08b)

code:
```rs
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2dBundle::default());

    for (left, justify) in [
        (100.0, JustifyText::Left),
        (500.0, JustifyText::Center),
        (900.0, JustifyText::Right),
    ] {
        commands
        // container
        .spawn(NodeBundle {
            style: Style {
                flex_direction: FlexDirection::Column,
                position_type: PositionType::Absolute,
                left: Val::Px(left),
                top: Val::Px(100.0),
                width: Val::Px(300.0),
                ..Default::default()
            },
            ..Default::default()
        })
        .with_children(|commands| {
            commands.spawn(NodeBundle{
                style: Style {
                    flex_direction: FlexDirection::Row,
                    height: Val::Px(75.0),
                    ..Default::default()
                },
                background_color: Color::srgb(1.0, 0.0, 1.0).into(),
                ..Default::default()
            }).with_children(|commands| {
                // a div that reduces the available size
                commands.spawn(NodeBundle {
                    style: Style {
                        width: Val::Px(75.0),
                        ..Default::default()
                    },
                    background_color: Color::srgb(0.0, 1.0, 0.0).into(),
                    ..Default::default()
                });

                // text with width=auto, but actual size will not be what it expcets due to the sibling div above
                commands.spawn(TextBundle {
                    text: Text::from_section("Some text that wraps onto a second line", Default::default()).with_justify(justify),
                    style: Style {
                        align_self: AlignSelf::Center,
                        ..Default::default()
                    },
                    ..Default::default()
                });
            });
        });
    }
}
```
2024-06-14 21:47:43 +02:00
Elabajaba
1ea1b76c75
Wgpu 0.20 (#13186)
Currently blocked on https://github.com/gfx-rs/wgpu/issues/5774

# Objective

Update to wgpu 0.20

## Solution

Update to wgpu 0.20 and naga_oil 0.14.

## Testing

Tested a few different examples on linux (vulkan, webgl2, webgpu) and
windows (dx12 + vulkan) and they worked.

---

## Changelog

- Updated to wgpu 0.20. Note that we don't currently support wgpu's new
pipeline overridable constants, as they don't work on web currently and
need some more changes to naga_oil (and are somewhat redundant with
naga_oil's shader defs). See wgpu's changelog for more
https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#v0200-2024-04-28

## Migration Guide

TODO

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
2024-06-14 20:55:42 +02:00
Mike
ac50539aab
reduce the antialias strength (#13814)
# Objective

- Fixes #13807

## Solution

- Before this pr we antialiased between 0.5 and -0.5. This pr changes
things to antialias between 0.25 and -0.25. I tried slightly larger
ranges, but the edge between the boxes still showed. I'm not 100% sure
this is the correct solution, but from what I could find the range you
use is more art than science.

## Testing

- Ran rounded_borders example, the code in the linked issue, and the
testing example from #12702.

---

## Changelog

- reduce antialiasing in ui shader.
2024-06-14 20:55:41 +02:00
James Gayfer
727b0f6e27
Use dynamic uniform buffer in post processing example (#13540)
# Objective

While learning about shaders and pipelines, I found this example to be
misleading; it wasn't clear to me how the node knew what the correct
"instance" of `PostProcessSettings` we should send to the shader (as the
combination of `ExtractComponentPlugin` and `UniformComponentPlugin`
extracts + sends _all_ of our `PostProcessSetting` components to the
GPU).

The goal of this PR is to clarify how to target the view specific
`PostProcessSettings` in the shader when there are multiple cameras.

## Solution

To accomplish this, we can use a dynamic uniform buffer for
`PostProcessSettings`, querying for the relevant `DynamicUniformIndex`
in the `PostProcessNode` to get the relevant index to use with the bind
group.

While the example in its current state is _correct_, I believe that fact
that it's intended to showcase a per camera post processing effect
warrants a dynamic uniform buffer (even though in the context of this
example we have only one camera, and therefore no adverse behaviour).

## Testing

- Run the `post_processing` example before and after this change,
verifying they behave the same.

## Reviewer notes

This is my first PR to Bevy, and I'm by no means an expert in the world
of rendering (though I'm trying to learn all I can). If there's a better
way to do this / a reason not to take this route, I'd love to hear it!

Thanks in advance.
2024-06-14 20:55:41 +02:00
Nionidh
7c0b1b9029
Add missing plugins to doc of DefaultPlugins (#13833)
StatesPlugin and GizmoPlugin were missing from the doc comment of
DefaultPlugins. I am not sure whether this was for a reason, but i just
stumbled over it and it seemed off...

## Testing

I'm not sure how to test these changes?
2024-06-14 20:55:41 +02:00
Martín Maita
84be2b3f1e
Ensure that events are updated even when using a bare-bones Bevy App (#13808) (#13842)
# Objective

- Related to https://github.com/bevyengine/bevy/issues/13825

## Solution

- Cherry picked the merged PR and performed the necessary changes to
adapt it to the 0.14 release branch.

---------

As discovered in
https://github.com/Leafwing-Studios/leafwing-input-manager/issues/538,
there appears to be some real weirdness going on in how event updates
are processed between Bevy 0.13 and Bevy 0.14.

To identify the cause and prevent regression, I've added tests to
validate the intended behavior.
My initial suspicion was that this would be fixed by
https://github.com/bevyengine/bevy/pull/13762, but that doesn't seem to
be the case.

Instead, events appear to never be updated at all when using `bevy_app`
by itself. This is part of the problem resolved by
https://github.com/bevyengine/bevy/pull/11528, and introduced by
https://github.com/bevyengine/bevy/pull/10077.

After some investigation, it appears that `signal_event_update_system`
is never added using a bare-bones `App`, and so event updates are always
skipped.

This can be worked around by adding your own copy to a
later-in-the-frame schedule, but that's not a very good fix.

Ensure that if we're not using a `FixedUpdate` schedule, events are
always updated every frame.

To do this, I've modified the logic of `event_update_condition` and
`event_update_system` to clearly and correctly differentiate between the
two cases: where we're waiting for a "you should update now" signal and
where we simply don't care.

To encode this, I've added the `ShouldUpdateEvents` enum, replacing a
simple `bool` in `EventRegistry`'s `needs_update` field.

Now, both tests pass as expected, without having to manually add a
system!

I've written two parallel unit tests to cover the intended behavior:

1. Test that `iter_current_update_events` works as expected in
`bevy_ecs`.
2. Test that `iter_current_update_events` works as expected in
`bevy_app`

I've also added a test to verify that event updating works correctly in
the presence of a fixed main schedule, and a second test to verify that
fixed updating works at all to help future authors narrow down failures.

- [x] figure out why the `bevy_app` version of this test fails but the
`bevy_ecs` version does not
- [x] figure out why `EventRegistry::run_updates` isn't working properly
- [x] figure out why `EventRegistry::run_updates` is never getting
called
- [x] figure out why `event_update_condition` is always returning false
- [x] figure out why `EventRegistry::needs_update` is always false
- [x] verify that the problem is a missing `signal_events_update_system`

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Mike <mike.hsu@gmail.com>
2024-06-14 20:53:37 +02:00
CatThingy
eca7e87d47
Fix minor typos in query join docs (#13812)
# Objective

- Correct typos in docs for `Query::join`'s docs

## Solution

- Fix them

Co-authored-by: François Mockers <mockersf@gmail.com>
2024-06-12 01:47:01 +02:00
Mike
cdd1f71596
Revert "constrain WorldQuery::init_state argument to ComponentInitial… (#13804)
…izer (#13442)"

This reverts commit 5cfb063d4a.

- This PR broke bevy-trait-query, which needs to be able to write a
resource in init_state. See #13798 for more details.
- Note this doesn't fix everything as transmutes for bevy-trait-query
will still be broken,. But the current usage in that crate is UB, so we
need to find another solution.
2024-06-12 01:20:47 +02:00
Alice Cecile
92176ce576
Revert "Make FOG_ENABLED a shader_def instead of material flag (#13783)" (#13803)
This reverts commit 3ced49f672.

Relevant to https://github.com/bevyengine/bevy/issues/13802. This wasn't
done quite right and partially broke fog.

Co-authored-by: Alice Cecile <alice.i.cecil@gmail.com>
2024-06-11 02:01:14 +02:00
Brezak
e14f3ba1aa
Add from_color to StandardMaterial and ColorMaterial (#13791)
# Objective

Closes #13738

## Solution

Added `from_color` to materials that would support it. Didn't add
`from_color` to `WireframeMaterial` as it doesn't seem we expect users
to be constructing them themselves.

## Testing

None

---

## Changelog

### Added

- `from_color` to `StandardMaterial` so you can construct this material
from any color type.
- `from_color` to `ColorMaterial` so you can construct this material
from any color type.
2024-06-11 02:01:14 +02:00
Lynn
9a82ecfcba
Custom primitives example (#13795)
# Objective

- Add a new example showcasing how to add custom primitives and what you
can do with them.

## Solution

- Added a new example `custom_primitives` with a 2D heart shape
primitive highlighting
  - `Bounded2d` by implementing and visualising bounding shapes,
  - `Measured2d` by implementing it,
  - `Meshable` to show the shape on the screen
- The example also includes an `Extrusion<Heart>` implementing
  - `Measured3d`,
  - `Bounded3d` using the `BoundedExtrusion` trait and
  - meshing using the `Extrudable` trait.

## Additional information

Here are two images of the heart and its extrusion:

![image_2024-06-10_194631194](https://github.com/bevyengine/bevy/assets/62256001/53f1836c-df74-4ba6-85e9-fabdafa94c66)
![Screenshot 2024-06-10
194609](https://github.com/bevyengine/bevy/assets/62256001/b1630e71-6e94-4293-b7b5-da8d9cc98faf)

---------

Co-authored-by: Jakub Marcowski <37378746+Chubercik@users.noreply.github.com>
2024-06-11 02:01:14 +02:00
JMS55
93f48edbc3
Fix meshlet vertex attribute interpolation (#13775)
# Objective

- Mikktspace requires that we normalize world normals/tangents _before_
interpolation across vertices, and then do _not_ normalize after. I had
it backwards.
- We do not (am not supposed to?) need a second set of barycentrics for
motion vectors. If you think about the typical raster pipeline, in the
vertex shader we calculate previous_world_position, and then it gets
interpolated using the current triangle's barycentrics.

## Solution

- Fix normal/tangent processing 
- Reuse barycentrics for motion vector calculations
- Not implementing this for 0.14, but long term I aim to remove explicit
vertex tangents and calculate them in the shader on the fly.

## Testing

- I tested out some of the normal maps we have in repo. Didn't seem to
make a difference, but mikktspace is all about correctness across
various baking tools. I probably just didn't have any of the ones that
would cause it to break.
- Didn't test motion vectors as there's a known bug with the depth
buffer and meshlets that I'm waiting on the render graph rewrite to fix.
2024-06-10 22:49:04 +02:00
Brezak
7cd90990f9
Poll system information in separate tasks (#13693)
Reading system information severely slows down the update loop.
Fixes #12848.

Read system info in a separate thread.

- Open the scene 3d example
- Add `FrameTimeDiagnosticsPlugin`, `SystemInformationDiagnosticsPlugin`
and `LogDiagnosticsPlugin` to the app.
- Add this system to the update schedule to disable Vsync on the main
window
```rust
fn change_window_mode(mut windows: Query<&mut Window, Added<Window>>) {
    for mut window in &mut windows {
        window.present_mode = PresentMode::AutoNoVsync;
    }
}
```
- Read the fps values in the console before and after this PR.

On my PC I went from around 50 fps to around 1150 fps.

---

- The `SystemInformationDiagnosticsPlugin` now reads system data
separate of the update cycle.

- The `EXPECTED_SYSTEM_INFORMATION_INTERVAL` constant which defines how
often we read system diagnostic data.

---------

Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
2024-06-10 22:30:56 +02:00
Chris Juchem
65dbfe249b
Remove extra call to clear_trackers (#13762)
Fixes #13758.

# Objective

Calling `update` on the main app already calls `clear_trackers`. Calling
it again in `SubApps::update` caused RemovedCompenet Events to be
cleared earlier than they should be.

## Solution

- Don't call clear_trackers an extra time.

## Testing

I manually tested the fix with this unit test: 
```
#[cfg(test)]
mod test {
    use crate::core::{FrameCount, FrameCountPlugin};
    use crate::prelude::*;

    #[test]
    fn test_next_frame_removal() {
        #[derive(Component)]
        struct Foo;

        #[derive(Resource)]
        struct RemovedCount(usize);

        let mut app = App::new();
        app.add_plugins(FrameCountPlugin);
        app.add_systems(Startup, |mut commands: Commands| {
            for _ in 0..100 {
                commands.spawn(Foo);
            }
            commands.insert_resource(RemovedCount(0));
        });

        app.add_systems(First, |counter: Res<FrameCount>| {
            println!("Frame {}:", counter.0)
        });

        fn detector_system(
            mut removals: RemovedComponents<Foo>,
            foos: Query<Entity, With<Foo>>,
            mut removed_c: ResMut<RemovedCount>,
        ) {
            for e in removals.read() {
                println!("  Detected removed Foo component for {e:?}");
                removed_c.0 += 1;
            }
            let c = foos.iter().count();
            println!("  Total Foos: {}", c);
            assert_eq!(c + removed_c.0, 100);
        }
        fn deleter_system(foos: Query<Entity, With<Foo>>, mut commands: Commands) {
            foos.iter().next().map(|e| {
                commands.entity(e).remove::<Foo>();
            });
        }
        app.add_systems(Update, (detector_system, deleter_system).chain());

        app.update();
        app.update();
        app.update();
        app.update();
    }
}
```
2024-06-10 20:23:06 +02:00
Periwink
effbcdfc92
Update serialize flag for bevy_ecs (#13740)
# Objective

There were some issues with the `serialize` feature:
- `bevy_app` had a `serialize` feature and a dependency on `serde` even
there is no usage of serde at all inside `bevy_app`
- the `bevy_app/serialize` feature enabled `bevy_ecs/serde`, which is
strange
- `bevy_internal/serialize` did not enable `bevy_app/serialize` so there
was no way of serializing an Entity in bevy 0.14

## Solution

- Remove `serde` and `bevy_app/serialize` 
- Add a `serialize` flag on `bevy_ecs` that enables `serde`
- ` bevy_internal/serialize` now enables `bevy_ecs/serialize`
2024-06-10 19:31:41 +02:00
IceSentry
3ced49f672
Make FOG_ENABLED a shader_def instead of material flag (#13783)
# Objective

- If the fog is disabled it still generates a useless branch which can
hurt performance

## Solution

- Make the flag a shader_def instead

## Testing

- I tested enabling/disabling fog works as expected per-material in the
fog example
- I also tested that scenes that don't add the FogSettings resource
still work correctly

## Review notes

I'm not sure how to handle the removed material flag. Right now I just
commented it out and added a not to reuse it instead of creating a new
one.
2024-06-10 19:31:41 +02:00
Lee-Orr
a3916b4af4
fix docs around StateTransition and remove references to `apply_sta… (#13772)
The documentation for the `State` resource still referenced the use of
`apply_state_transition` to manually force a state transition to occur,
and the question around how to force transitions had come up a few times
on discord.

This is a docs-only change, that does the following:
- Properly references `StateTransition` in the `MainSchedule` docs
- replace the explanations for applying `NextState` with ones that
explain the `StateTransition` schedule, and mentions the possibility of
calling it manually
- Add an example of calling `StateTransition` manually in the docs for
the state transition schedule itself.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-06-10 19:31:41 +02:00
Isaïe
c060e3e1fe
Clarify error message due to missing shader file (#13766)
# Objective

The error printed-out due to a missing shader file was confusing; This
PR changes the error message.

Fixes #13644 

## Solution

I replaced the confusing wording (`... shader is not loaded yet`) with a
clear explanation (`... shader could not be loaded`)

## Testing

> Did you test these changes? If so, how?

removing `assets/shaders/game_of_life.wgsl` & running its associated
example now produces the following error:

```
thread '<unnamed>' panicked at examples/shader/compute_shader_game_of_life.rs:233:25:
Initializing assets/shaders/game_of_life.wgsl:
Pipeline could not be compiled because the following shader could not be loaded: AssetId<bevy_render::render_resource::shader::Shader>{ index: 0, generation: 0}
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_render::renderer::render_system`!
```

I don't think there are any tests expecting the previous error message,
so this change should not break anything.

> Are there any parts that need more testing?

If there was an intent behind the original message, this might need more
attention.

> How can other people (reviewers) test your changes? Is there anything
specific they need to know?

One should be able to preview the changes by running any example after
deleting/renaming their associated shader(s).

> If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?

N/A
2024-06-10 19:31:41 +02:00
MiniaczQ
854983dc7e
Add more granular system sets for state transition schedule ordering (#13763)
# Objective

Fixes #13711 

## Solution

Introduce smaller, generic system sets for each schedule variant, which
are ordered against other generic variants:
- `ExitSchedules<S>` - For `OnExit` schedules, runs from leaf states to
root states.
- `TransitionSchedules<S>` - For `OnTransition` schedules, runs in
arbitrary order.
- `EnterSchedules<S>` - For `OnEnter` schedules, runs from root states
to leaf states.

Also unified `ApplyStateTransition<S>` schedule which works in basically
the same way, just for internals.

## Testing

- One test that tests schedule execution order

---------

Co-authored-by: Lee-Orr <lee-orr@users.noreply.github.com>
2024-06-10 19:31:41 +02:00
JMS55
a944598812
Meshlet misc (#13761)
- Copy module docs so that they show up in the re-export
- Change meshlet_id to cluster_id in the debug visualization
- Small doc tweaks
2024-06-10 19:31:41 +02:00
Gagnus
cbebcb0d3f
Adds back in way to convert color to u8 array, implemented for the two RGB color types, also renames Color::linear to Color::to_linear. (#13759)
# Objective

One thing missing from the new Color implementation in 0.14 is the
ability to easily convert to a u8 representation of the rgb color.

(note this is a redo of PR https://github.com/bevyengine/bevy/pull/13739
as I needed to move the source branch

## Solution

I have added to_u8_array and to_u8_array_no_alpha to a new trait called
ColorToPacked to mirror the f32 conversions in ColorToComponents and
implemented the new trait for Srgba and LinearRgba.
To go with those I also added matching from_u8... functions and
converted a couple of cases that used ad-hoc implementations of that
conversion to use these.
After discussion on Discord of the experience of using the API I renamed
Color::linear to Color::to_linear, as without that it looks like a
constructor (like Color::rgb).
I also added to_srgba which is the other commonly converted to type of
color (for UI and 2D) to match to_linear.
Removed a redundant extra implementation of to_f32_array for LinearColor
as it is also supplied in ColorToComponents (I'm surprised that's
allowed?)

## Testing

Ran all tests and manually tested.
Added to_and_from_u8 to linear_rgba::tests

## Changelog

visible change is Color::linear becomes Color::to_linear.

---------

Co-authored-by: John Payne <20407779+johngpayne@users.noreply.github.com>
2024-06-10 19:31:41 +02:00
JMS55
8c5769ab92
Remove unused mip_bias parameter from apply_normal_mapping (#13752)
Mip bias is no longer used here
2024-06-10 19:31:41 +02:00
Joseph
1ae616fef1
Improve error handling for AssetServer::add_async (#13745)
# Objective

The method `AssetServer::add_async` (added in
https://github.com/bevyengine/bevy/pull/13700) requires a future that
returns an `AssetLoadError` error, which was a bit of an oversight on my
part, as that type of error only really makes sense in the context of
bevy's own asset loader -- returning it from user-defined futures isn't
very useful.

## Solution

Allow passing custom error types to `add_async`, which get cast into a
trait object matching the form of `AssetLoader::load`. If merged before
the next release this will not be a breaking change
2024-06-10 19:31:41 +02:00
Lynn
a2c5b0d415
Add segments to ExtrusionBuilder (#13719)
# Objective

- Add support for `segments` for extrusion-meshes, akin to what is
possible with cylinders

## Solution

- Added a `.segments(segments: usize)` function to `ExtrusionBuilder`.
- Implemented support for segments in the meshing algorithm.
- If you set `.segments(0)`, the meshing will fail, just like it does
with cylinders.

## Additional information

Here is a wireframe of some extrusions with 1, 2, 3, etc. segments:

![image_2024-06-06_233205114](https://github.com/bevyengine/bevy/assets/62256001/358081e2-172d-407b-8bdb-9cda88eb4664)

---------

Co-authored-by: Lynn Büttgenbach <62256001+solis-lumine-vorago@users.noreply.github.com>
2024-06-10 19:31:41 +02:00
Julian
be65fbb691
2D top-down camera example (#12720)
# Objective

This PR addresses the 2D part of #12658. I plan to separate the examples
and make one PR per camera example.

## Solution

Added a new top-down example composed of:

- [x] Player keyboard movements
- [x] UI for keyboard instructions
- [x] Colors and bloom effect to see the movement of the player
- [x] Camera smooth movement towards the player (lerp)

## Testing

```bash
cargo run --features="wayland,bevy/dynamic_linking" --example 2d_top_down_camera
```



https://github.com/bevyengine/bevy/assets/10638479/95db0587-e5e0-4f55-be11-97444b795793
2024-06-10 19:31:41 +02:00
Niklas Eicker
91db570d6e
Let init_non_send_resource require FromWorld instead of Default (#13779)
# Objective

- Let `init_non_send_resource` take `FromWorld` values again, not only
`Default`
- This reverts an unintended breaking change introduced in #9202

## Solution

- The resource initialized with `init_non_send_resource` requires
`FromWorld` again
2024-06-10 19:31:41 +02:00
thebluefish
621cd23ffc
Fix EntityCommands::despawn docs (#13774)
# Objective

The `EntityCommands::despawn` method was previously changed from
panicking behavior to a warning, but the docs continue to state that it
panics.

## Solution

- Removed panic section, copied warning blurb from `World::despawn`
- Adds a similar warning blurb to
`DespawnRecursiveExt::despawn_recursive` and
`DespawnRecursiveExt::despawn_descendants`
2024-06-09 20:52:52 +02:00
Chris Biscardi
7ae3c94b0f
view.inverse_clip_from_world should be world_from_clip (#13756)
As per the other changes in
https://github.com/bevyengine/bevy/pull/13489
`view.inverse_clip_from_world` should be `world_from_clip`.



# Objective

fixes #13749

## Solution

Modified lines.wgsl to use the right name as the current name does not
exist.

## Testing

I ran the 3d_gizmos example and pressed "p".

![screenshot-2024-06-08-at-13 21
22@2x](https://github.com/bevyengine/bevy/assets/551247/b8bfd3db-8273-4606-9dae-040764339883)

![screenshot-2024-06-08-at-13 21
26@2x](https://github.com/bevyengine/bevy/assets/551247/2619f1ae-ce83-44d7-a9fc-07e686950887)
2024-06-09 16:55:22 +02:00
Vitaliy Sapronenko
b14684ee12
Clear messed up feature flag on AppExitStates impl (#13737)
# Objective

- In #13649 additional method had been added to AppExitStates, but there
feature gate left for method in implementation for App at refactoring
stage.
- Fixes #13733 .

## Solution

- Removed the feature gate.

## Testing

- Ran reproducing example from #13733 with no compilation errors
2024-06-09 16:22:50 +02:00
charlotte
971723e4b4
13743 app exit hang (#13744)
Fixes #13743.

---------

Co-authored-by: Brezak <bezak.adam@proton.me>
2024-06-09 01:18:07 +02:00
Mincong Lu
4df95384ba
Make the component types of the new animation players clonable. (#13736)
# Objective

Some use cases might require holding onto the previous state of the
animation player for change detection.

## Solution

Added `clone` and `copy` implementation to most animation types. 
Added optimized `clone_from` implementations for the specific use case
of holding a `PreviousAnimationPlayer` component.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-06-09 01:18:07 +02:00
François Mockers
c71fa76f12
rename the crate bevy_state_macros_official back to its original name (#13732)
- Thanks to the original author we can now use the original name

- Use it
2024-06-09 01:18:07 +02:00
François Mockers
25306e7532
Document the Release Candidate process (#13718)
# Objective

- Document how to release a RC

## Solution

- Also allow CI to trigger on release branches
2024-06-09 01:18:07 +02:00
François Mockers
ce19b0f63f
Update crate metadata for bevy state (#13722)
- crate metadata for bevy_state has been copied from bevy_ecs

- Update it
2024-06-09 01:18:07 +02:00
François
79a93de606
Release Candidate 0.14.0-rc.2 2024-06-06 23:56:56 +02:00
François
5f2e927ae5
rename bevy_state_macros to bevy_state_macros_official 2024-06-06 23:55:47 +02:00
charlotte
43204447e8
Allow mix of hdr and non-hdr cameras to same render target (#13419)
Changes:
- Track whether an output texture has been written to yet and only clear
it on the first write.
- Use `ClearColorConfig` on `CameraOutputMode` instead of a raw
`LoadOp`.
- Track whether a output texture has been seen when specializing the
upscaling pipeline and use alpha blending for extra cameras rendering to
that texture that do not specify an explicit blend mode.

Fixes #6754

## Testing

Tested against provided test case in issue:

![image](https://github.com/bevyengine/bevy/assets/10366310/d066f069-87fb-4249-a4d9-b6cb1751971b)

---

## Changelog

- Allow cameras rendering to the same output texture with mixed hdr to
work correctly.

## Migration Guide

- - Change `CameraOutputMode` to use `ClearColorConfig` instead of
`LoadOp`.
2024-06-06 23:12:32 +02:00
François
2d11d9a48d
Release Candidate 0.14.0-rc.1 2024-06-06 22:34:20 +02:00
208 changed files with 7381 additions and 1707 deletions

View File

@ -6,6 +6,7 @@ on:
push:
branches:
- main
- release-*
env:
CARGO_TERM_COLOR: always

View File

@ -6,6 +6,7 @@ on:
push:
branches:
- main
- release-*
concurrency:
group: ${{github.workflow}}-${{github.ref}}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
categories = ["game-engines", "graphics", "gui", "rendering"]
description = "A refreshingly simple data-driven game engine and app framework"
@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
documentation = "https://docs.rs/bevy"
rust-version = "1.78.0"
rust-version = "1.79.0"
[workspace]
exclude = [
@ -76,6 +76,7 @@ default = [
"bevy_gizmos",
"android_shared_stdcxx",
"tonemapping_luts",
"smaa_luts",
"default_font",
"webgl2",
"sysinfo_plugin",
@ -282,6 +283,9 @@ detailed_trace = ["bevy_internal/detailed_trace"]
# Include tonemapping Look Up Tables KTX2 files. If everything is pink, you need to enable this feature or change the `Tonemapping` method on your `Camera2dBundle` or `Camera3dBundle`.
tonemapping_luts = ["bevy_internal/tonemapping_luts", "ktx2", "zstd"]
# Include SMAA Look Up Tables KTX2 Files
smaa_luts = ["bevy_internal/smaa_luts"]
# Enable AccessKit on Unix backends (currently only works with experimental screen readers and forks.)
accesskit_unix = ["bevy_internal/accesskit_unix"]
@ -308,6 +312,9 @@ pbr_multi_layer_material_textures = [
"bevy_internal/pbr_multi_layer_material_textures",
]
# Enable support for anisotropy texture in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs
pbr_anisotropy_texture = ["bevy_internal/pbr_anisotropy_texture"]
# Enable some limitations to be able to use WebGL2. Please refer to the [WebGL2 and WebGPU](https://github.com/bevyengine/bevy/tree/latest/examples#webgl2-and-webgpu) section of the examples README for more information on how to run Wasm builds with WebGPU.
webgl2 = ["bevy_internal/webgl"]
@ -339,11 +346,11 @@ ios_simulator = ["bevy_internal/ios_simulator"]
bevy_state = ["bevy_internal/bevy_state"]
[dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.14.0-dev", default-features = false }
bevy_internal = { path = "crates/bevy_internal", version = "0.14.0", default-features = false }
# WASM does not support dynamic linking.
[target.'cfg(not(target_family = "wasm"))'.dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.14.0-dev", default-features = false, optional = true }
bevy_dylib = { path = "crates/bevy_dylib", version = "0.14.0", default-features = false, optional = true }
[dev-dependencies]
rand = "0.8.0"
@ -2534,6 +2541,17 @@ description = "Systems run in parallel, but their order isn't always determinist
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "observers"
path = "examples/ecs/observers.rs"
doc-scrape-examples = true
[package.metadata.example.observers]
name = "Observers"
description = "Demonstrates observers that react to events (both built-in life-cycle events and custom events)"
category = "ECS (Entity Component System)"
wasm = true
[[example]]
name = "3d_rotation"
path = "examples/transforms/3d_rotation.rs"
@ -3024,6 +3042,17 @@ description = "Demonstrates all the primitives which can be sampled."
category = "Math"
wasm = true
[[example]]
name = "custom_primitives"
path = "examples/math/custom_primitives.rs"
doc-scrape-examples = true
[package.metadata.example.custom_primitives]
name = "Custom Primitives"
description = "Demonstrates how to add custom primitives and useful traits for them."
category = "Math"
wasm = true
[[example]]
name = "random_sampling"
path = "examples/math/random_sampling.rs"
@ -3086,6 +3115,28 @@ path = "examples/dev_tools/fps_overlay.rs"
doc-scrape-examples = true
required-features = ["bevy_dev_tools"]
[[example]]
name = "2d_top_down_camera"
path = "examples/camera/2d_top_down_camera.rs"
doc-scrape-examples = true
[package.metadata.example.2d_top_down_camera]
name = "2D top-down camera"
description = "A 2D top-down camera smoothly following player movements"
category = "Camera"
wasm = true
[[example]]
name = "first_person_view_model"
path = "examples/camera/first_person_view_model.rs"
doc-scrape-examples = true
[package.metadata.example.first_person_view_model]
name = "First person view model"
description = "A first-person camera that uses a world model and a view model with different field of views (FOV)"
category = "Camera"
wasm = true
[package.metadata.example.fps_overlay]
name = "FPS overlay"
description = "Demonstrates FPS overlay"
@ -3163,7 +3214,7 @@ wasm = true
name = "anisotropy"
path = "examples/3d/anisotropy.rs"
doc-scrape-examples = true
required-features = ["jpeg"]
required-features = ["jpeg", "pbr_anisotropy_texture"]
[package.metadata.example.anisotropy]
name = "Anisotropy"
@ -3171,6 +3222,17 @@ description = "Displays an example model with anisotropy"
category = "3D Rendering"
wasm = false
[[example]]
name = "custom_phase_item"
path = "examples/shader/custom_phase_item.rs"
doc-scrape-examples = true
[package.metadata.example.custom_phase_item]
name = "Custom phase item"
description = "Demonstrates how to enqueue custom draw commands in a render phase"
category = "Shaders"
wasm = true
[profile.wasm-release]
inherits = "release"
opt-level = "z"

View File

@ -0,0 +1,288 @@
{
"extensionsUsed": [
"KHR_materials_unlit"
],
"asset": {
"generator": "UniGLTF-1.27",
"version": "2.0"
},
"buffers": [
{
"uri": "craft_speederD_data.bin",
"byteLength": 20120
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 6096,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 6096,
"byteLength": 6096,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 12192,
"byteLength": 4064,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 16256,
"byteLength": 732,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 16988,
"byteLength": 1368,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 18356,
"byteLength": 456,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 18812,
"byteLength": 1308,
"target": 34963
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 508,
"max": [
1.4,
0.9,
1.11283529
],
"min": [
-1.4,
0,
-1.11283529
],
"normalized": false
},
{
"bufferView": 1,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 508,
"normalized": false
},
{
"bufferView": 2,
"byteOffset": 0,
"type": "VEC2",
"componentType": 5126,
"count": 508,
"normalized": false
},
{
"bufferView": 3,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 183,
"normalized": false
},
{
"bufferView": 4,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 342,
"normalized": false
},
{
"bufferView": 5,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 114,
"normalized": false
},
{
"bufferView": 6,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 327,
"normalized": false
}
],
"materials": [
{
"name": "metal",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.843137264,
0.870588243,
0.9098039,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
},
{
"name": "metalDark",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.6750623,
0.7100219,
0.7735849,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
},
{
"name": "dark",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.274509817,
0.298039228,
0.34117648,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
},
{
"name": "metalRed",
"pbrMetallicRoughness": {
"baseColorFactor": [
1,
0.628524244,
0.2028302,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
}
],
"meshes": [
{
"name": "Mesh craft_speederD",
"primitives": [
{
"mode": 4,
"indices": 3,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 0
},
{
"mode": 4,
"indices": 4,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 1
},
{
"mode": 4,
"indices": 5,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 2
},
{
"mode": 4,
"indices": 6,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 3
}
]
}
],
"nodes": [
{
"children": [
1
],
"name": "tmpParent",
"translation": [
0,
0,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
]
},
{
"name": "craft_speederD",
"translation": [
0,
0,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
],
"mesh": 0
}
],
"scenes": [
{
"nodes": [
1
]
}
],
"scene": 0
}

Binary file not shown.

View File

@ -49,7 +49,6 @@ fn fragment(
double_sided,
is_front,
Nt,
view.mip_bias,
);
#endif

View File

@ -0,0 +1,36 @@
// `custom_phase_item.wgsl`
//
// This shader goes with the `custom_phase_item` example. It demonstrates how to
// enqueue custom rendering logic in a `RenderPhase`.
// The GPU-side vertex structure.
struct Vertex {
// The world-space position of the vertex.
@location(0) position: vec3<f32>,
// The color of the vertex.
@location(1) color: vec3<f32>,
};
// Information passed from the vertex shader to the fragment shader.
struct VertexOutput {
// The clip-space position of the vertex.
@builtin(position) clip_position: vec4<f32>,
// The color of the vertex.
@location(0) color: vec3<f32>,
};
// The vertex shader entry point.
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
// Use an orthographic projection.
var vertex_output: VertexOutput;
vertex_output.clip_position = vec4(vertex.position.xyz, 1.0);
vertex_output.color = vertex.color;
return vertex_output;
}
// The fragment shader entry point.
@fragment
fn fragment(vertex_output: VertexOutput) -> @location(0) vec4<f32> {
return vec4(vertex_output.color, 1.0);
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_a11y"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides accessibility support for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,9 +10,9 @@ keywords = ["bevy", "accessibility", "a11y"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
accesskit = "0.14"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_animation"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides animation functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,23 +10,23 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0" }
bevy_color = { path = "../bevy_color", version = "0.14.1" }
bevy_core = { path = "../bevy_core", version = "0.14.0" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0" }
bevy_log = { path = "../bevy_log", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
"petgraph",
] }
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.14.0" }
bevy_time = { path = "../bevy_time", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0" }
# other
fixedbitset = "0.5"

View File

@ -236,7 +236,7 @@ impl Hash for AnimationTargetId {
/// Note that each entity can only be animated by one animation player at a
/// time. However, you can change [`AnimationTarget`]'s `player` property at
/// runtime to change which player is responsible for animating the entity.
#[derive(Clone, Component, Reflect)]
#[derive(Clone, Copy, Component, Reflect)]
#[reflect(Component, MapEntities)]
pub struct AnimationTarget {
/// The ID of this animation target.
@ -326,7 +326,7 @@ pub enum RepeatAnimation {
/// playing, but is presently paused.
///
/// An stopped animation is considered no longer active.
#[derive(Debug, Reflect)]
#[derive(Debug, Clone, Copy, Reflect)]
pub struct ActiveAnimation {
/// The factor by which the weight from the [`AnimationGraph`] is multiplied.
weight: f32,
@ -515,6 +515,21 @@ pub struct AnimationPlayer {
blend_weights: HashMap<AnimationNodeIndex, f32>,
}
// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`.
impl Clone for AnimationPlayer {
fn clone(&self) -> Self {
Self {
active_animations: self.active_animations.clone(),
blend_weights: self.blend_weights.clone(),
}
}
fn clone_from(&mut self, source: &Self) {
self.active_animations.clone_from(&source.active_animations);
self.blend_weights.clone_from(&source.blend_weights);
}
}
/// The components that we might need to read or write during animation of each
/// animation target.
struct AnimationTargetContext<'a> {

View File

@ -33,8 +33,23 @@ pub struct AnimationTransitions {
transitions: Vec<AnimationTransition>,
}
// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`.
impl Clone for AnimationTransitions {
fn clone(&self) -> Self {
Self {
main_animation: self.main_animation,
transitions: self.transitions.clone(),
}
}
fn clone_from(&mut self, source: &Self) {
self.main_animation = source.main_animation;
self.transitions.clone_from(&source.transitions);
}
}
/// An animation that is being faded out as part of a transition
#[derive(Debug, Reflect)]
#[derive(Debug, Clone, Copy, Reflect)]
pub struct AnimationTransition {
/// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out.
current_weight: f32,
@ -78,6 +93,11 @@ impl AnimationTransitions {
self.main_animation = Some(new_animation);
player.start(new_animation)
}
/// Obtain the currently playing main animation.
pub fn get_main_animation(&self) -> Option<AnimationNodeIndex> {
self.main_animation
}
}
/// A system that alters the weight of currently-playing transitions based on

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_app"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides core App functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -13,18 +13,16 @@ trace = []
bevy_debug_stepping = []
default = ["bevy_reflect"]
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
serialize = ["bevy_ecs/serde"]
[dependencies]
# bevy
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0" }
# other
serde = { version = "1.0", features = ["derive"], optional = true }
downcast-rs = "1.2.0"
thiserror = "1.0"

View File

@ -8,7 +8,7 @@ use bevy_ecs::{
intern::Interned,
prelude::*,
schedule::{ScheduleBuildSettings, ScheduleLabel},
system::SystemId,
system::{IntoObserverSystem, SystemId},
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
@ -424,9 +424,12 @@ impl App {
self
}
/// Inserts the [`!Send`](Send) resource into the app, initialized with its default value,
/// if there is no existing instance of `R`.
pub fn init_non_send_resource<R: 'static + Default>(&mut self) -> &mut Self {
/// Inserts the [`!Send`](Send) resource into the app if there is no existing instance of `R`.
///
/// `R` must implement [`FromWorld`].
/// If `R` implements [`Default`], [`FromWorld`] will be automatically implemented and
/// initialize the [`Resource`] with [`Default::default`].
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) -> &mut Self {
self.world_mut().init_non_send_resource::<R>();
self
}
@ -436,12 +439,7 @@ impl App {
plugin: Box<dyn Plugin>,
) -> Result<&mut Self, AppError> {
debug!("added plugin: {}", plugin.name());
if plugin.is_unique()
&& !self
.main_mut()
.plugin_names
.insert(plugin.name().to_string())
{
if plugin.is_unique() && self.main_mut().plugin_names.contains(plugin.name()) {
Err(AppError::DuplicatePlugin {
plugin_name: plugin.name().to_string(),
})?;
@ -456,6 +454,9 @@ impl App {
self.main_mut().plugin_build_depth += 1;
let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self)));
self.main_mut()
.plugin_names
.insert(plugin.name().to_string());
self.main_mut().plugin_build_depth -= 1;
if let Err(payload) = result {
@ -662,6 +663,11 @@ impl App {
self.sub_apps.sub_apps.remove(&label.intern())
}
/// Extract data from the main world into the [`SubApp`] with the given label and perform an update if it exists.
pub fn update_sub_app_by_label(&mut self, label: impl AppLabel) {
self.sub_apps.update_subapp_by_label(label);
}
/// Inserts a new `schedule` under the provided `label`, overwriting any existing
/// schedule with the same label.
pub fn add_schedule(&mut self, schedule: Schedule) -> &mut Self {
@ -828,6 +834,15 @@ impl App {
None
}
/// Spawns an [`Observer`] entity, which will watch for and respond to the given event.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.world_mut().observe(observer);
self
}
}
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
@ -916,11 +931,21 @@ impl Termination for AppExit {
#[cfg(test)]
mod tests {
use std::{marker::PhantomData, mem};
use std::{iter, marker::PhantomData, mem, sync::Mutex};
use bevy_ecs::{event::EventWriter, schedule::ScheduleLabel, system::Commands};
use bevy_ecs::{
change_detection::{DetectChanges, ResMut},
component::Component,
entity::Entity,
event::{Event, EventWriter, Events},
query::With,
removal_detection::RemovedComponents,
schedule::{IntoSystemConfigs, ScheduleLabel},
system::{Commands, Query, Resource},
world::{FromWorld, World},
};
use crate::{App, AppExit, Plugin, Update};
use crate::{App, AppExit, Plugin, SubApp, Update};
struct PluginA;
impl Plugin for PluginA {
@ -1123,6 +1148,62 @@ mod tests {
);
}
#[test]
fn test_update_clears_trackers_once() {
#[derive(Component, Copy, Clone)]
struct Foo;
let mut app = App::new();
app.world_mut().spawn_batch(iter::repeat(Foo).take(5));
fn despawn_one_foo(mut commands: Commands, foos: Query<Entity, With<Foo>>) {
if let Some(e) = foos.iter().next() {
commands.entity(e).despawn();
};
}
fn check_despawns(mut removed_foos: RemovedComponents<Foo>) {
let mut despawn_count = 0;
for _ in removed_foos.read() {
despawn_count += 1;
}
assert_eq!(despawn_count, 2);
}
app.add_systems(Update, despawn_one_foo);
app.update(); // Frame 0
app.update(); // Frame 1
app.add_systems(Update, check_despawns.after(despawn_one_foo));
app.update(); // Should see despawns from frames 1 & 2, but not frame 0
}
#[test]
fn test_extract_sees_changes() {
use super::AppLabel;
use crate::{self as bevy_app};
#[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)]
struct MySubApp;
#[derive(Resource)]
struct Foo(usize);
let mut app = App::new();
app.world_mut().insert_resource(Foo(0));
app.add_systems(Update, |mut foo: ResMut<Foo>| {
foo.0 += 1;
});
let mut sub_app = SubApp::new();
sub_app.set_extract(|main_world, _sub_world| {
assert!(main_world.get_resource_ref::<Foo>().unwrap().is_changed());
});
app.insert_sub_app(MySubApp, sub_app);
app.update();
}
#[test]
fn runner_returns_correct_exit_code() {
fn raise_exits(mut exits: EventWriter<AppExit>) {
@ -1178,4 +1259,83 @@ mod tests {
// it's nice they're so small let's keep it that way.
assert_eq!(mem::size_of::<AppExit>(), mem::size_of::<u8>());
}
#[test]
fn initializing_resources_from_world() {
#[derive(Resource)]
struct TestResource;
impl FromWorld for TestResource {
fn from_world(_world: &mut World) -> Self {
TestResource
}
}
#[derive(Resource)]
struct NonSendTestResource {
_marker: PhantomData<Mutex<()>>,
}
impl FromWorld for NonSendTestResource {
fn from_world(_world: &mut World) -> Self {
NonSendTestResource {
_marker: PhantomData,
}
}
}
App::new()
.init_non_send_resource::<NonSendTestResource>()
.init_resource::<TestResource>();
}
#[test]
/// Plugin should not be considered inserted while it's being built
///
/// bug: <https://github.com/bevyengine/bevy/issues/13815>
fn plugin_should_not_be_added_during_build_time() {
pub struct Foo;
impl Plugin for Foo {
fn build(&self, app: &mut App) {
assert!(!app.is_plugin_added::<Self>());
}
}
App::new().add_plugins(Foo);
}
#[test]
fn events_should_be_updated_once_per_update() {
#[derive(Event, Clone)]
struct TestEvent;
let mut app = App::new();
app.add_event::<TestEvent>();
// Starts empty
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 0);
assert_eq!(test_events.iter_current_update_events().count(), 0);
app.update();
// Sending one event
app.world_mut().send_event(TestEvent);
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 1);
assert_eq!(test_events.iter_current_update_events().count(), 1);
app.update();
// Sending two events on the next frame
app.world_mut().send_event(TestEvent);
app.world_mut().send_event(TestEvent);
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
assert_eq!(test_events.iter_current_update_events().count(), 2);
app.update();
// Sending zero events
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
assert_eq!(test_events.iter_current_update_events().count(), 0);
}
}

View File

@ -17,7 +17,7 @@ use bevy_ecs::{
/// Then it will run:
/// * [`First`]
/// * [`PreUpdate`]
/// * [`StateTransition`]
/// * [`StateTransition`](bevy_state::transition::StateTransition)
/// * [`RunFixedMainLoop`]
/// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
/// * [`Update`]

View File

@ -1,4 +1,4 @@
use crate::{App, InternedAppLabel, Plugin, Plugins, PluginsState};
use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState};
use bevy_ecs::{
event::EventRegistry,
prelude::*,
@ -125,7 +125,9 @@ impl SubApp {
}
/// Runs the default schedule.
pub fn update(&mut self) {
///
/// Does not clear internal trackers used for change detection.
pub fn run_default_schedule(&mut self) {
if self.is_building_plugins() {
panic!("SubApp::update() was called while a plugin was building.");
}
@ -133,6 +135,11 @@ impl SubApp {
if let Some(label) = self.update_schedule {
self.world.run_schedule(label);
}
}
/// Runs the default schedule and updates internal component trackers.
pub fn update(&mut self) {
self.run_default_schedule();
self.world.clear_trackers();
}
@ -421,7 +428,7 @@ impl SubApps {
{
#[cfg(feature = "trace")]
let _bevy_frame_update_span = info_span!("main app").entered();
self.main.update();
self.main.run_default_schedule();
}
for (_label, sub_app) in self.sub_apps.iter_mut() {
#[cfg(feature = "trace")]
@ -442,4 +449,12 @@ impl SubApps {
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut SubApp> + '_ {
std::iter::once(&mut self.main).chain(self.sub_apps.values_mut())
}
/// Extract data from the main world into the [`SubApp`] with the given label and perform an update if it exists.
pub fn update_subapp_by_label(&mut self, label: impl AppLabel) {
if let Some(sub_app) = self.sub_apps.get_mut(&label.intern()) {
sub_app.extract(&mut self.main.world);
sub_app.update();
}
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_asset"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides asset functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -19,14 +19,14 @@ watch = []
trace = []
[dependencies]
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset_macros = { path = "macros", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_asset_macros = { path = "macros", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"uuid",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
async-broadcast = "0.5"
async-fs = "2.0"
@ -43,7 +43,7 @@ thiserror = "1.0"
uuid = { version = "1.0", features = ["v4"] }
[target.'cfg(target_os = "android")'.dependencies]
bevy_winit = { path = "../bevy_winit", version = "0.14.0-dev" }
bevy_winit = { path = "../bevy_winit", version = "0.14.0" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
@ -59,8 +59,8 @@ js-sys = "0.3"
notify-debouncer-full = { version = "0.3.1", optional = true }
[dev-dependencies]
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0" }
bevy_log = { path = "../bevy_log", version = "0.14.0" }
[lints]
workspace = true

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_asset_macros"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Derive implementations for bevy_asset"
homepage = "https://bevyengine.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.14.0-dev" }
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.14.0" }
syn = "2.0"
proc-macro2 = "1.0"

View File

@ -288,13 +288,17 @@ impl Hash for UntypedAssetId {
}
}
impl Ord for UntypedAssetId {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.type_id()
.cmp(&other.type_id())
.then_with(|| self.internal().cmp(&other.internal()))
}
}
impl PartialOrd for UntypedAssetId {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.type_id() != other.type_id() {
None
} else {
Some(self.internal().cmp(&other.internal()))
}
Some(self.cmp(other))
}
}

View File

@ -718,9 +718,9 @@ impl AssetServer {
///
/// After the asset has been fully loaded, it will show up in the relevant [`Assets`] storage.
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
pub fn add_async<A: Asset>(
pub fn add_async<A: Asset, E: std::error::Error + Send + Sync + 'static>(
&self,
future: impl Future<Output = Result<A, AssetLoadError>> + Send + 'static,
future: impl Future<Output = Result<A, E>> + Send + 'static,
) -> Handle<A> {
let handle = self
.data
@ -741,12 +741,15 @@ impl AssetServer {
.unwrap();
}
Err(error) => {
let error = AddAsyncError {
error: Arc::new(error),
};
error!("{error}");
event_sender
.send(InternalAssetEvent::Failed {
id,
path: Default::default(),
error,
error: AssetLoadError::AddAsyncError(error),
})
.unwrap();
}
@ -1403,6 +1406,8 @@ pub enum AssetLoadError {
CannotLoadIgnoredAsset { path: AssetPath<'static> },
#[error(transparent)]
AssetLoaderError(#[from] AssetLoaderError),
#[error(transparent)]
AddAsyncError(#[from] AddAsyncError),
#[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
base_path,
label,
@ -1441,6 +1446,22 @@ impl AssetLoaderError {
}
}
#[derive(Error, Debug, Clone)]
#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
pub struct AddAsyncError {
error: Arc<dyn std::error::Error + Send + Sync + 'static>,
}
impl PartialEq for AddAsyncError {
/// Equality comparison is not full (only through `TypeId`)
#[inline]
fn eq(&self, other: &Self) -> bool {
self.error.type_id() == other.error.type_id()
}
}
impl Eq for AddAsyncError {}
/// An error that occurs when an [`AssetLoader`] is not registered for a given extension.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))]

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_audio"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides audio functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,17 +10,17 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
] }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
# other
rodio = { version = "0.18", default-features = false }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_color"
version = "0.14.0-dev"
version = "0.14.1"
edition = "2021"
description = "Types for representing and manipulating color values"
homepage = "https://bevyengine.org"
@ -10,14 +10,14 @@ keywords = ["bevy", "color"]
rust-version = "1.76.0"
[dependencies]
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
] }
bytemuck = { version = "1", features = ["derive"] }
serde = { version = "1.0", features = ["derive"], optional = true }
thiserror = "1.0"
wgpu-types = { version = "0.19", default-features = false, optional = true }
wgpu-types = { version = "0.20", default-features = false, optional = true }
encase = { version = "0.8", default-features = false }
[features]

View File

@ -73,7 +73,12 @@ impl StandardColor for Color {}
impl Color {
/// Return the color as a linear RGBA color.
pub fn linear(&self) -> LinearRgba {
pub fn to_linear(&self) -> LinearRgba {
(*self).into()
}
/// Return the color as an SRGBA color.
pub fn to_srgba(&self) -> Srgba {
(*self).into()
}

View File

@ -115,6 +115,18 @@ pub trait ColorToComponents {
fn from_vec3(color: Vec3) -> Self;
}
/// Trait with methods for converting colors to packed non-color types
pub trait ColorToPacked {
/// Convert to [u8; 4] where that makes sense (Srgba is most relevant)
fn to_u8_array(self) -> [u8; 4];
/// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
fn to_u8_array_no_alpha(self) -> [u8; 3];
/// Convert from [u8; 4] where that makes sense (Srgba is most relevant)
fn from_u8_array(color: [u8; 4]) -> Self;
/// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
fn from_u8_array_no_alpha(color: [u8; 3]) -> Self;
}
/// Utility function for interpolating hue values. This ensures that the interpolation
/// takes the shortest path around the color wheel, and that the result is always between
/// 0 and 360.

View File

@ -1,6 +1,6 @@
use crate::{
color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents,
Gray, Luminance, Mix, StandardColor,
ColorToPacked, Gray, Luminance, Mix, StandardColor,
};
use bevy_math::{Vec3, Vec4};
use bevy_reflect::prelude::*;
@ -149,24 +149,12 @@ impl LinearRgba {
}
}
/// Converts the color into a [f32; 4] array in RGBA order.
///
/// This is useful for passing the color to a shader.
pub fn to_f32_array(&self) -> [f32; 4] {
[self.red, self.green, self.blue, self.alpha]
}
/// Converts this color to a u32.
///
/// Maps the RGBA channels in RGBA order to a little-endian byte array (GPUs are little-endian).
/// `A` will be the most significant byte and `R` the least significant.
pub fn as_u32(&self) -> u32 {
u32::from_le_bytes([
(self.red * 255.0) as u8,
(self.green * 255.0) as u8,
(self.blue * 255.0) as u8,
(self.alpha * 255.0) as u8,
])
u32::from_le_bytes(self.to_u8_array())
}
}
@ -310,6 +298,25 @@ impl ColorToComponents for LinearRgba {
}
}
impl ColorToPacked for LinearRgba {
fn to_u8_array(self) -> [u8; 4] {
[self.red, self.green, self.blue, self.alpha]
.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn to_u8_array_no_alpha(self) -> [u8; 3] {
[self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn from_u8_array(color: [u8; 4]) -> Self {
Self::from_f32_array(color.map(|u| u as f32 / 255.0))
}
fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
}
}
#[cfg(feature = "wgpu-types")]
impl From<LinearRgba> for wgpu_types::Color {
fn from(color: LinearRgba) -> Self {
@ -416,6 +423,34 @@ mod tests {
assert_eq!(a.distance_squared(&b), 1.0);
}
#[test]
fn to_and_from_u8() {
// from_u8_array
let a = LinearRgba::from_u8_array([255, 0, 0, 255]);
let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
assert_eq!(a, b);
// from_u8_array_no_alpha
let a = LinearRgba::from_u8_array_no_alpha([255, 255, 0]);
let b = LinearRgba::rgb(1.0, 1.0, 0.0);
assert_eq!(a, b);
// to_u8_array
let a = LinearRgba::new(0.0, 0.0, 1.0, 1.0).to_u8_array();
let b = [0, 0, 255, 255];
assert_eq!(a, b);
// to_u8_array_no_alpha
let a = LinearRgba::rgb(0.0, 1.0, 1.0).to_u8_array_no_alpha();
let b = [0, 255, 255];
assert_eq!(a, b);
// clamping
let a = LinearRgba::rgb(0.0, 100.0, -100.0).to_u8_array_no_alpha();
let b = [0, 255, 0];
assert_eq!(a, b);
}
#[test]
fn darker_lighter() {
// Darker and lighter should be commutative.

View File

@ -1,7 +1,7 @@
use crate::color_difference::EuclideanDistance;
use crate::{
impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, LinearRgba, Luminance, Mix,
StandardColor, Xyza,
impl_componentwise_vector_space, Alpha, ColorToComponents, ColorToPacked, Gray, LinearRgba,
Luminance, Mix, StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
use bevy_reflect::prelude::*;
@ -168,10 +168,7 @@ impl Srgba {
/// Convert this color to CSS-style hexadecimal notation.
pub fn to_hex(&self) -> String {
let r = (self.red * 255.0).round() as u8;
let g = (self.green * 255.0).round() as u8;
let b = (self.blue * 255.0).round() as u8;
let a = (self.alpha * 255.0).round() as u8;
let [r, g, b, a] = self.to_u8_array();
match a {
255 => format!("#{:02X}{:02X}{:02X}", r, g, b),
_ => format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a),
@ -189,7 +186,7 @@ impl Srgba {
/// See also [`Srgba::new`], [`Srgba::rgba_u8`], [`Srgba::hex`].
///
pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self {
Self::rgba_u8(r, g, b, u8::MAX)
Self::from_u8_array_no_alpha([r, g, b])
}
// Float operations in const fn are not stable yet
@ -206,12 +203,7 @@ impl Srgba {
/// See also [`Srgba::new`], [`Srgba::rgb_u8`], [`Srgba::hex`].
///
pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::new(
r as f32 / u8::MAX as f32,
g as f32 / u8::MAX as f32,
b as f32 / u8::MAX as f32,
a as f32 / u8::MAX as f32,
)
Self::from_u8_array([r, g, b, a])
}
/// Converts a non-linear sRGB value to a linear one via [gamma correction](https://en.wikipedia.org/wiki/Gamma_correction).
@ -373,6 +365,25 @@ impl ColorToComponents for Srgba {
}
}
impl ColorToPacked for Srgba {
fn to_u8_array(self) -> [u8; 4] {
[self.red, self.green, self.blue, self.alpha]
.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn to_u8_array_no_alpha(self) -> [u8; 3] {
[self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn from_u8_array(color: [u8; 4]) -> Self {
Self::from_f32_array(color.map(|u| u as f32 / 255.0))
}
fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
}
}
impl From<LinearRgba> for Srgba {
#[inline]
fn from(value: LinearRgba) -> Self {

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_core"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides core functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,17 +10,17 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.14.0", features = [
"bevy_reflect",
] }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev", features = [
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0", features = [
"bevy_reflect",
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
# other
serde = { version = "1.0", optional = true }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_core_pipeline"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
authors = [
"Bevy Contributors <bevyengine@gmail.com>",
@ -18,20 +18,21 @@ trace = []
webgl = []
webgpu = []
tonemapping_luts = ["bevy_render/ktx2", "bevy_render/zstd"]
smaa_luts = ["bevy_render/ktx2", "bevy_render/zstd"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0" }
bevy_core = { path = "../bevy_core", version = "0.14.0" }
bevy_color = { path = "../bevy_color", version = "0.14.1" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0" }
bevy_render = { path = "../bevy_render", version = "0.14.0" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
serde = { version = "1", features = ["derive"] }
bitflags = "2.3"

View File

@ -49,7 +49,7 @@ pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
/// `sampler2DShadow` and will cheerfully generate invalid GLSL that tries to
/// perform non-percentage-closer-filtering with such a sampler. Therefore we
/// disable depth of field and screen space reflections entirely on WebGL 2.
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
#[cfg(not(any(feature = "webgpu", not(target_arch = "wasm32"))))]
pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = false;
/// True if multisampled depth textures are supported on this platform.
@ -64,7 +64,7 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true;
use std::ops::Range;
use bevy_asset::AssetId;
use bevy_asset::{AssetId, UntypedAssetId};
use bevy_color::LinearRgba;
pub use camera_3d::*;
pub use main_opaque_pass_3d_node::*;
@ -76,7 +76,6 @@ use bevy_math::FloatOrd;
use bevy_render::{
camera::{Camera, ExtractedCamera},
extract_component::ExtractComponentPlugin,
mesh::Mesh,
prelude::Msaa,
render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{
@ -136,6 +135,14 @@ impl Plugin for Core3dPlugin {
.init_resource::<DrawFunctions<AlphaMask3dPrepass>>()
.init_resource::<DrawFunctions<Opaque3dDeferred>>()
.init_resource::<DrawFunctions<AlphaMask3dDeferred>>()
.init_resource::<ViewBinnedRenderPhases<Opaque3d>>()
.init_resource::<ViewBinnedRenderPhases<AlphaMask3d>>()
.init_resource::<ViewBinnedRenderPhases<Opaque3dPrepass>>()
.init_resource::<ViewBinnedRenderPhases<AlphaMask3dPrepass>>()
.init_resource::<ViewBinnedRenderPhases<Opaque3dDeferred>>()
.init_resource::<ViewBinnedRenderPhases<AlphaMask3dDeferred>>()
.init_resource::<ViewSortedRenderPhases<Transmissive3d>>()
.init_resource::<ViewSortedRenderPhases<Transparent3d>>()
.add_systems(ExtractSchedule, extract_core_3d_camera_phases)
.add_systems(ExtractSchedule, extract_camera_prepass_phase)
.add_systems(
@ -213,7 +220,7 @@ pub struct Opaque3d {
pub extra_index: PhaseItemExtraIndex,
}
/// Data that must be identical in order to batch meshes together.
/// Data that must be identical in order to batch phase items together.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Opaque3dBinKey {
/// The identifier of the render pipeline.
@ -222,8 +229,11 @@ pub struct Opaque3dBinKey {
/// The function used to draw.
pub draw_function: DrawFunctionId,
/// The mesh.
pub asset_id: AssetId<Mesh>,
/// The asset that this phase item is associated with.
///
/// Normally, this is the ID of the mesh, but for non-mesh items it might be
/// the ID of another type of asset.
pub asset_id: UntypedAssetId,
/// The ID of a bind group specific to the material.
///

View File

@ -122,17 +122,17 @@ impl ViewNode for DeferredGBufferPrepassNode {
let view_entity = graph.view_entity();
render_context.add_command_buffer_generation_task(move |render_device| {
#[cfg(feature = "trace")]
let _deferred_span = info_span!("deferred").entered();
let _deferred_span = info_span!("deferred_prepass").entered();
// Command encoder setup
let mut command_encoder =
render_device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("deferred_command_encoder"),
label: Some("deferred_prepass_command_encoder"),
});
// Render pass setup
let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("deferred"),
label: Some("deferred_prepass"),
color_attachments: &color_attachments,
depth_stencil_attachment,
timestamp_writes: None,
@ -144,24 +144,24 @@ impl ViewNode for DeferredGBufferPrepassNode {
}
// Opaque draws
if !opaque_deferred_phase.batchable_keys.is_empty()
|| !opaque_deferred_phase.unbatchable_keys.is_empty()
if !opaque_deferred_phase.batchable_mesh_keys.is_empty()
|| !opaque_deferred_phase.unbatchable_mesh_keys.is_empty()
{
#[cfg(feature = "trace")]
let _opaque_prepass_span = info_span!("opaque_deferred").entered();
let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered();
opaque_deferred_phase.render(&mut render_pass, world, view_entity);
}
// Alpha masked draws
if !alpha_mask_deferred_phase.is_empty() {
#[cfg(feature = "trace")]
let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred").entered();
let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered();
alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity);
}
drop(render_pass);
// Copy prepass depth to the main depth texture
// After rendering to the view depth texture, copy it to the prepass depth texture
if let Some(prepass_depth_texture) = &view_prepass_textures.depth {
command_encoder.copy_texture_to_texture(
view_depth_texture.texture.as_image_copy(),

View File

@ -29,12 +29,11 @@ pub mod node;
use std::ops::Range;
use bevy_asset::AssetId;
use bevy_asset::UntypedAssetId;
use bevy_ecs::prelude::*;
use bevy_math::Mat4;
use bevy_reflect::Reflect;
use bevy_render::{
mesh::Mesh,
render_phase::{
BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem,
PhaseItemExtraIndex,
@ -147,7 +146,7 @@ pub struct Opaque3dPrepass {
}
// TODO: Try interning these.
/// The data used to bin each opaque 3D mesh in the prepass and deferred pass.
/// The data used to bin each opaque 3D object in the prepass and deferred pass.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OpaqueNoLightmap3dBinKey {
/// The ID of the GPU pipeline.
@ -156,8 +155,8 @@ pub struct OpaqueNoLightmap3dBinKey {
/// The function used to draw the mesh.
pub draw_function: DrawFunctionId,
/// The ID of the mesh.
pub asset_id: AssetId<Mesh>,
/// The ID of the asset.
pub asset_id: UntypedAssetId,
/// The ID of a bind group specific to the material.
///

View File

@ -120,8 +120,8 @@ impl ViewNode for PrepassNode {
}
// Opaque draws
if !opaque_prepass_phase.batchable_keys.is_empty()
|| !opaque_prepass_phase.unbatchable_keys.is_empty()
if !opaque_prepass_phase.batchable_mesh_keys.is_empty()
|| !opaque_prepass_phase.unbatchable_mesh_keys.is_empty()
{
#[cfg(feature = "trace")]
let _opaque_prepass_span = info_span!("opaque_prepass").entered();
@ -162,7 +162,7 @@ impl ViewNode for PrepassNode {
pass_span.end(&mut render_pass);
drop(render_pass);
// Copy prepass depth to the main depth texture if deferred isn't going to
// After rendering to the view depth texture, copy it to the prepass depth texture if deferred isn't going to
if deferred_prepass.is_none() {
if let Some(prepass_depth_texture) = &view_prepass_textures.depth {
command_encoder.copy_texture_to_texture(

View File

@ -24,7 +24,7 @@ use bevy_render::{
};
use prepass::{SkyboxPrepassPipeline, SKYBOX_PREPASS_SHADER_HANDLE};
use crate::core_3d::CORE_3D_DEPTH_FORMAT;
use crate::{core_3d::CORE_3D_DEPTH_FORMAT, prepass::PreviousViewUniforms};
const SKYBOX_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(55594763423201);
@ -53,6 +53,7 @@ impl Plugin for SkyboxPlugin {
render_app
.init_resource::<SpecializedRenderPipelines<SkyboxPipeline>>()
.init_resource::<SpecializedRenderPipelines<SkyboxPrepassPipeline>>()
.init_resource::<PreviousViewUniforms>()
.add_systems(
Render,
(

View File

@ -31,7 +31,9 @@
//! [SMAA]: https://www.iryoku.com/smaa/
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, load_internal_binary_asset, Handle};
#[cfg(feature = "smaa_luts")]
use bevy_asset::load_internal_binary_asset;
use bevy_asset::{load_internal_asset, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
@ -47,7 +49,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
camera::ExtractedCamera,
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_asset::{RenderAssetUsages, RenderAssets},
render_asset::RenderAssets,
render_graph::{
NodeRunError, RenderGraphApp as _, RenderGraphContext, ViewNode, ViewNodeRunner,
},
@ -65,15 +67,19 @@ use bevy_render::{
VertexState,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::{
BevyDefault, CachedTexture, CompressedImageFormats, GpuImage, Image, ImageFormat,
ImageSampler, ImageType, TextureCache,
},
texture::{BevyDefault, CachedTexture, GpuImage, Image, TextureCache},
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
};
#[cfg(feature = "smaa_luts")]
use bevy_render::{
render_asset::RenderAssetUsages,
texture::{CompressedImageFormats, ImageFormat, ImageSampler, ImageType},
};
use bevy_utils::prelude::default;
#[cfg(not(feature = "smaa_luts"))]
use crate::tonemapping::lut_placeholder;
use crate::{
core_2d::graph::{Core2d, Node2d},
core_3d::graph::{Core3d, Node3d},
@ -287,6 +293,7 @@ impl Plugin for SmaaPlugin {
// Load the two lookup textures. These are compressed textures in KTX2
// format.
#[cfg(feature = "smaa_luts")]
load_internal_binary_asset!(
app,
SMAA_AREA_LUT_TEXTURE_HANDLE,
@ -304,6 +311,7 @@ impl Plugin for SmaaPlugin {
.expect("Failed to load SMAA area LUT")
);
#[cfg(feature = "smaa_luts")]
load_internal_binary_asset!(
app,
SMAA_SEARCH_LUT_TEXTURE_HANDLE,
@ -321,6 +329,16 @@ impl Plugin for SmaaPlugin {
.expect("Failed to load SMAA search LUT")
);
#[cfg(not(feature = "smaa_luts"))]
app.world_mut()
.resource_mut::<bevy_asset::Assets<Image>>()
.insert(SMAA_AREA_LUT_TEXTURE_HANDLE.id(), lut_placeholder());
#[cfg(not(feature = "smaa_luts"))]
app.world_mut()
.resource_mut::<bevy_asset::Assets<Image>>()
.insert(SMAA_SEARCH_LUT_TEXTURE_HANDLE.id(), lut_placeholder());
app.add_plugins(ExtractComponentPlugin::<SmaaSettings>::default())
.register_type::<SmaaSettings>();

View File

@ -4,6 +4,7 @@ use bevy_ecs::prelude::*;
use bevy_render::camera::{CameraOutputMode, ExtractedCamera};
use bevy_render::view::ViewTarget;
use bevy_render::{render_resource::*, Render, RenderApp, RenderSet};
use bevy_utils::HashSet;
mod node;
@ -32,16 +33,32 @@ fn prepare_view_upscaling_pipelines(
blit_pipeline: Res<BlitPipeline>,
view_targets: Query<(Entity, &ViewTarget, Option<&ExtractedCamera>)>,
) {
let mut output_textures = HashSet::new();
for (entity, view_target, camera) in view_targets.iter() {
let out_texture_id = view_target.out_texture().id();
let blend_state = if let Some(ExtractedCamera {
output_mode: CameraOutputMode::Write { blend_state, .. },
..
}) = camera
{
*blend_state
match *blend_state {
None => {
// If we've already seen this output for a camera and it doesn't have a output blend
// mode configured, default to alpha blend so that we don't accidentally overwrite
// the output texture
if output_textures.contains(&out_texture_id) {
Some(BlendState::ALPHA_BLENDING)
} else {
None
}
}
_ => *blend_state,
}
} else {
None
};
output_textures.insert(out_texture_id);
let key = BlitPipelineKey {
texture_format: view_target.out_texture_format(),
blend_state,

View File

@ -1,11 +1,11 @@
use crate::{blit::BlitPipeline, upscaling::ViewUpscalingPipeline};
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::camera::{ClearColor, ClearColorConfig};
use bevy_render::{
camera::{CameraOutputMode, ExtractedCamera},
render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_resource::{
BindGroup, BindGroupEntries, LoadOp, Operations, PipelineCache, RenderPassColorAttachment,
RenderPassDescriptor, StoreOp, TextureViewId,
BindGroup, BindGroupEntries, PipelineCache, RenderPassDescriptor, TextureViewId,
},
renderer::RenderContext,
view::ViewTarget,
@ -33,19 +33,22 @@ impl ViewNode for UpscalingNode {
) -> Result<(), NodeRunError> {
let pipeline_cache = world.get_resource::<PipelineCache>().unwrap();
let blit_pipeline = world.get_resource::<BlitPipeline>().unwrap();
let clear_color_global = world.get_resource::<ClearColor>().unwrap();
let color_attachment_load_op = if let Some(camera) = camera {
let clear_color = if let Some(camera) = camera {
match camera.output_mode {
CameraOutputMode::Write {
color_attachment_load_op,
..
} => color_attachment_load_op,
CameraOutputMode::Write { clear_color, .. } => clear_color,
CameraOutputMode::Skip => return Ok(()),
}
} else {
LoadOp::Clear(Default::default())
ClearColorConfig::Default
};
let clear_color = match clear_color {
ClearColorConfig::Default => Some(clear_color_global.0),
ClearColorConfig::Custom(color) => Some(color),
ClearColorConfig::None => None,
};
let converted_clear_color = clear_color.map(|color| color.into());
let upscaled_texture = target.main_texture_view();
let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap();
@ -69,14 +72,9 @@ impl ViewNode for UpscalingNode {
let pass_descriptor = RenderPassDescriptor {
label: Some("upscaling_pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view: target.out_texture(),
resolve_target: None,
ops: Operations {
load: color_attachment_load_op,
store: StoreOp::Store,
},
})],
color_attachments: &[Some(
target.out_texture_color_attachment(converted_clear_color),
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_derive"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides derive implementations for Bevy Engine"
homepage = "https://bevyengine.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.14.0-dev" }
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.14.0" }
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_dev_tools"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Collection of developer tools for the Bevy Engine"
homepage = "https://bevyengine.org"
@ -15,28 +15,26 @@ bevy_ui_debug = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_gizmos = { path = "../bevy_gizmos", version = "0.14.0-dev" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.14.0-dev", features = [
"bevy_text",
] }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.14.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.14.0-dev" }
bevy_state = { path = "../bevy_state", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0" }
bevy_color = { path = "../bevy_color", version = "0.14.1" }
bevy_core = { path = "../bevy_core", version = "0.14.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_gizmos = { path = "../bevy_gizmos", version = "0.14.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0" }
bevy_input = { path = "../bevy_input", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0" }
bevy_render = { path = "../bevy_render", version = "0.14.0" }
bevy_time = { path = "../bevy_time", version = "0.14.0" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_ui = { path = "../bevy_ui", version = "0.14.0", features = ["bevy_text"] }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_window = { path = "../bevy_window", version = "0.14.0" }
bevy_text = { path = "../bevy_text", version = "0.14.0" }
bevy_state = { path = "../bevy_state", version = "0.14.0" }
# other
serde = { version = "1.0", features = ["derive"], optional = true }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_diagnostic"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides diagnostic functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -15,11 +15,12 @@ sysinfo_plugin = ["sysinfo"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_core = { path = "../bevy_core", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_time = { path = "../bevy_time", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0" }
const-fnv1a-hash = "1.1.0"

View File

@ -4,6 +4,9 @@ use bevy_ecs::system::Resource;
/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %)
///
/// Note that gathering system information is a time intensive task and therefore can't be done on every frame.
/// Any system diagnostics gathered by this plugin may not be current when you access them.
///
/// Supported targets:
/// * linux,
/// * windows,
@ -19,8 +22,7 @@ use bevy_ecs::system::Resource;
pub struct SystemInformationDiagnosticsPlugin;
impl Plugin for SystemInformationDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, internal::setup_system)
.add_systems(Update, internal::diagnostic_system);
internal::setup_plugin(app);
}
}
@ -58,6 +60,14 @@ pub struct SystemInfo {
))]
pub mod internal {
use bevy_ecs::{prelude::ResMut, system::Local};
use std::{
sync::{Arc, Mutex},
time::Instant,
};
use bevy_app::{App, First, Startup, Update};
use bevy_ecs::system::Resource;
use bevy_tasks::{available_parallelism, block_on, poll_once, AsyncComputeTaskPool, Task};
use bevy_utils::tracing::info;
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
@ -67,41 +77,91 @@ pub mod internal {
const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0;
pub(crate) fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
pub(super) fn setup_plugin(app: &mut App) {
app.add_systems(Startup, setup_system)
.add_systems(First, launch_diagnostic_tasks)
.add_systems(Update, read_diagnostic_tasks)
.init_resource::<SysinfoTasks>();
}
fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
diagnostics
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::CPU_USAGE).with_suffix("%"));
diagnostics
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%"));
}
pub(crate) fn diagnostic_system(
mut diagnostics: Diagnostics,
mut sysinfo: Local<Option<System>>,
struct SysinfoRefreshData {
current_cpu_usage: f64,
current_used_mem: f64,
}
#[derive(Resource, Default)]
struct SysinfoTasks {
tasks: Vec<Task<SysinfoRefreshData>>,
}
fn launch_diagnostic_tasks(
mut tasks: ResMut<SysinfoTasks>,
// TODO: Consider a fair mutex
mut sysinfo: Local<Option<Arc<Mutex<System>>>>,
// TODO: FromWorld for Instant?
mut last_refresh: Local<Option<Instant>>,
) {
if sysinfo.is_none() {
*sysinfo = Some(System::new_with_specifics(
let sysinfo = sysinfo.get_or_insert_with(|| {
Arc::new(Mutex::new(System::new_with_specifics(
RefreshKind::new()
.with_cpu(CpuRefreshKind::new().with_cpu_usage())
.with_memory(MemoryRefreshKind::everything()),
));
}
let Some(sys) = sysinfo.as_mut() else {
return;
};
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
sys.refresh_memory();
let current_cpu_usage = sys.global_cpu_info().cpu_usage();
// `memory()` fns return a value in bytes
let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB;
let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB;
let current_used_mem = used_mem / total_mem * 100.0;
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || {
current_cpu_usage as f64
)))
});
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
current_used_mem
let last_refresh = last_refresh.get_or_insert_with(Instant::now);
let thread_pool = AsyncComputeTaskPool::get();
// Only queue a new system refresh task when necessary
// Queueing earlier than that will not give new data
if last_refresh.elapsed() > sysinfo::MINIMUM_CPU_UPDATE_INTERVAL
// These tasks don't yield and will take up all of the task pool's
// threads if we don't limit their amount.
&& tasks.tasks.len() * 2 < available_parallelism()
{
let sys = Arc::clone(sysinfo);
let task = thread_pool.spawn(async move {
let mut sys = sys.lock().unwrap();
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
sys.refresh_memory();
let current_cpu_usage = sys.global_cpu_info().cpu_usage().into();
// `memory()` fns return a value in bytes
let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB;
let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB;
let current_used_mem = used_mem / total_mem * 100.0;
SysinfoRefreshData {
current_cpu_usage,
current_used_mem,
}
});
tasks.tasks.push(task);
*last_refresh = Instant::now();
}
}
fn read_diagnostic_tasks(mut diagnostics: Diagnostics, mut tasks: ResMut<SysinfoTasks>) {
tasks.tasks.retain_mut(|task| {
let Some(data) = block_on(poll_once(task)) else {
return true;
};
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || {
data.current_cpu_usage
});
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
data.current_used_mem
});
false
});
}
@ -145,12 +205,14 @@ pub mod internal {
not(feature = "dynamic_linking")
)))]
pub mod internal {
pub(crate) fn setup_system() {
bevy_utils::tracing::warn!("This platform and/or configuration is not supported!");
use bevy_app::{App, Startup};
pub(super) fn setup_plugin(app: &mut App) {
app.add_systems(Startup, setup_system);
}
pub(crate) fn diagnostic_system() {
// no-op
fn setup_system() {
bevy_utils::tracing::warn!("This platform and/or configuration is not supported!");
}
impl Default for super::SystemInfo {

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_dylib"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Force the Bevy Engine to be dynamically linked for faster linking"
homepage = "https://bevyengine.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
crate-type = ["dylib"]
[dependencies]
bevy_internal = { path = "../bevy_internal", version = "0.14.0-dev", default-features = false }
bevy_internal = { path = "../bevy_internal", version = "0.14.0", default-features = false }
[lints]
workspace = true

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_dynamic_plugin"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides dynamic plugin loading capabilities for non-wasm platforms"
homepage = "https://bevyengine.org"
@ -10,7 +10,7 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
# other
libloading = { version = "0.8" }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_ecs"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Bevy Engine's entity component system"
homepage = "https://bevyengine.org"
@ -15,13 +15,14 @@ trace = []
multi_threaded = ["bevy_tasks/multi_threaded", "arrayvec"]
bevy_debug_stepping = []
default = ["bevy_reflect"]
serialize = ["dep:serde"]
[dependencies]
bevy_ptr = { path = "../bevy_ptr", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", optional = true }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_ecs_macros = { path = "macros", version = "0.14.0-dev" }
bevy_ptr = { path = "../bevy_ptr", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", optional = true }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_ecs_macros = { path = "macros", version = "0.14.0" }
petgraph = "0.6"
bitflags = "2.3"

View File

@ -307,4 +307,52 @@ fn reader(mut reader: EventReader<MyEvent>) {
A minimal set up using events can be seen in [`events.rs`](examples/events.rs).
### Observers
Observers are systems that listen for a "trigger" of a specific `Event`:
```rust
use bevy_ecs::prelude::*;
#[derive(Event)]
struct MyEvent {
message: String
}
let mut world = World::new();
world.observe(|trigger: Trigger<MyEvent>| {
println!("{}", trigger.event().message);
});
world.flush();
world.trigger(MyEvent {
message: "hello!".to_string(),
});
```
These differ from `EventReader` and `EventWriter` in that they are "reactive". Rather than happening at a specific point in a schedule, they happen _immediately_ whenever a trigger happens. Triggers can trigger other triggers, and they all will be evaluated at the same time!
Events can also be triggered to target specific entities:
```rust
use bevy_ecs::prelude::*;
#[derive(Event)]
struct Explode;
let mut world = World::new();
let entity = world.spawn_empty().id();
world.observe(|trigger: Trigger<Explode>, mut commands: Commands| {
println!("Entity {:?} goes BOOM!", trigger.entity());
commands.entity(trigger.entity()).despawn();
});
world.flush();
world.trigger_targets(Explode, entity);
```
[bevy]: https://bevyengine.org/

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_ecs_macros"
version = "0.14.0-dev"
version = "0.14.0"
description = "Bevy ECS Macros"
edition = "2021"
license = "MIT OR Apache-2.0"
@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.14.0-dev" }
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.14.0" }
syn = { version = "2.0", features = ["full"] }
quote = "1.0"

View File

@ -18,6 +18,10 @@ pub fn derive_event(input: TokenStream) -> TokenStream {
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
}
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::SparseSet;
}
})
}

View File

@ -74,6 +74,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
.collect::<Vec<_>>();
let mut field_component_ids = Vec::new();
let mut field_get_component_ids = Vec::new();
let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new();
for (((i, field_type), field_kind), field) in field_type
@ -87,6 +88,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
field_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
});
field_get_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
});
match field {
Some(field) => {
field_get_components.push(quote! {
@ -133,6 +137,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
#(#field_component_ids)*
}
fn get_component_ids(
components: &#ecs_path::component::Components,
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
){
#(#field_get_component_ids)*
}
#[allow(unused_variables, non_snake_case)]
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
where
@ -435,6 +446,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
}
fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: #path::world::DeferredWorld) {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::queue(&mut state.state, system_meta, world);
}
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &#path::system::SystemMeta,

View File

@ -164,9 +164,9 @@ pub(crate) fn world_query_impl(
#( <#field_types>::update_component_access(&state.#named_field_idents, _access); )*
}
fn init_state(initializer: &mut #path::component::ComponentInitializer) -> #state_struct_name #user_ty_generics {
fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics {
#state_struct_name {
#(#named_field_idents: <#field_types>::init_state(initializer),)*
#(#named_field_idents: <#field_types>::init_state(world),)*
}
}

View File

@ -23,6 +23,7 @@ use crate::{
bundle::BundleId,
component::{ComponentId, Components, StorageType},
entity::{Entity, EntityLocation},
observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
};
use std::{
@ -119,6 +120,7 @@ pub(crate) struct AddBundle {
/// For each component iterated in the same order as the source [`Bundle`](crate::bundle::Bundle),
/// indicate if the component is newly added to the target archetype or if it already existed
pub bundle_status: Vec<ComponentStatus>,
pub added: Vec<ComponentId>,
}
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
@ -202,12 +204,14 @@ impl Edges {
bundle_id: BundleId,
archetype_id: ArchetypeId,
bundle_status: Vec<ComponentStatus>,
added: Vec<ComponentId>,
) {
self.add_bundle.insert(
bundle_id,
AddBundle {
archetype_id,
bundle_status,
added,
},
);
}
@ -314,6 +318,9 @@ bitflags::bitflags! {
const ON_ADD_HOOK = (1 << 0);
const ON_INSERT_HOOK = (1 << 1);
const ON_REMOVE_HOOK = (1 << 2);
const ON_ADD_OBSERVER = (1 << 3);
const ON_INSERT_OBSERVER = (1 << 4);
const ON_REMOVE_OBSERVER = (1 << 5);
}
}
@ -335,6 +342,7 @@ pub struct Archetype {
impl Archetype {
pub(crate) fn new(
components: &Components,
observers: &Observers,
id: ArchetypeId,
table_id: TableId,
table_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
@ -348,6 +356,7 @@ impl Archetype {
// SAFETY: We are creating an archetype that includes this component so it must exist
let info = unsafe { components.get_info_unchecked(component_id) };
info.update_archetype_flags(&mut flags);
observers.update_archetype_flags(component_id, &mut flags);
archetype_components.insert(
component_id,
ArchetypeComponentInfo {
@ -361,6 +370,7 @@ impl Archetype {
// SAFETY: We are creating an archetype that includes this component so it must exist
let info = unsafe { components.get_info_unchecked(component_id) };
info.update_archetype_flags(&mut flags);
observers.update_archetype_flags(component_id, &mut flags);
archetype_components.insert(
component_id,
ArchetypeComponentInfo {
@ -580,21 +590,45 @@ impl Archetype {
/// Returns true if any of the components in this archetype have `on_add` hooks
#[inline]
pub(crate) fn has_on_add(&self) -> bool {
pub fn has_add_hook(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_HOOK)
}
/// Returns true if any of the components in this archetype have `on_insert` hooks
#[inline]
pub(crate) fn has_on_insert(&self) -> bool {
pub fn has_insert_hook(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK)
}
/// Returns true if any of the components in this archetype have `on_remove` hooks
#[inline]
pub(crate) fn has_on_remove(&self) -> bool {
pub fn has_remove_hook(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK)
}
/// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer
///
/// [`OnAdd`]: crate::world::OnAdd
#[inline]
pub fn has_add_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
}
/// Returns true if any of the components in this archetype have at least one [`OnInsert`] observer
///
/// [`OnInsert`]: crate::world::OnInsert
#[inline]
pub fn has_insert_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
}
/// Returns true if any of the components in this archetype have at least one [`OnRemove`] observer
///
/// [`OnRemove`]: crate::world::OnRemove
#[inline]
pub fn has_remove_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
}
}
/// The next [`ArchetypeId`] in an [`Archetypes`] collection.
@ -681,6 +715,7 @@ impl Archetypes {
unsafe {
archetypes.get_id_or_insert(
&Components::default(),
&Observers::default(),
TableId::empty(),
Vec::new(),
Vec::new(),
@ -782,6 +817,7 @@ impl Archetypes {
pub(crate) unsafe fn get_id_or_insert(
&mut self,
components: &Components,
observers: &Observers,
table_id: TableId,
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
@ -808,6 +844,7 @@ impl Archetypes {
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
archetypes.push(Archetype::new(
components,
observers,
id,
table_id,
table_components.into_iter().zip(table_archetype_components),
@ -832,6 +869,20 @@ impl Archetypes {
archetype.clear_entities();
}
}
pub(crate) fn update_flags(
&mut self,
component_id: ComponentId,
flags: ArchetypeFlags,
set: bool,
) {
// TODO: Refactor component index to speed this up.
for archetype in &mut self.archetypes {
if archetype.contains(component_id) {
archetype.flags.set(flags, set);
}
}
}
}
impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {

View File

@ -2,8 +2,9 @@
//!
//! This module contains the [`Bundle`] trait and some other helper types.
use std::any::TypeId;
pub use bevy_ecs_macros::Bundle;
use bevy_utils::{HashMap, HashSet, TypeIdMap};
use crate::{
archetype::{
@ -12,14 +13,15 @@ use crate::{
},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
observer::Observers,
prelude::World,
query::DebugCheckedUnwrap,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
world::unsafe_world_cell::UnsafeWorldCell,
world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT},
};
use bevy_ptr::{ConstNonNull, OwningPtr};
use bevy_utils::all_tuples;
use std::any::TypeId;
use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap};
use std::ptr::NonNull;
/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity.
@ -155,6 +157,9 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static {
ids: &mut impl FnMut(ComponentId),
);
/// Gets this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered.
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>));
/// Calls `func`, which should return data for each component in the bundle, in the order of
/// this bundle's [`Component`]s
///
@ -204,6 +209,10 @@ unsafe impl<C: Component> Bundle for C {
// Safety: The id given in `component_ids` is for `Self`
unsafe { ptr.read() }
}
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)) {
ids(components.get_id(TypeId::of::<C>()));
}
}
impl<C: Component> DynamicBundle for C {
@ -227,6 +236,11 @@ macro_rules! tuple_impl {
$(<$name as Bundle>::component_ids(components, storages, ids);)*
}
#[allow(unused_variables)]
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)){
$(<$name as Bundle>::get_component_ids(components, ids);)*
}
#[allow(unused_variables, unused_mut)]
#[allow(clippy::unused_unit)]
unsafe fn from_components<T, F>(ctx: &mut T, func: &mut F) -> Self
@ -432,6 +446,7 @@ impl BundleInfo {
archetypes: &mut Archetypes,
storages: &mut Storages,
components: &Components,
observers: &Observers,
archetype_id: ArchetypeId,
) -> ArchetypeId {
if let Some(add_bundle_id) = archetypes[archetype_id].edges().get_add_bundle(self.id) {
@ -440,6 +455,7 @@ impl BundleInfo {
let mut new_table_components = Vec::new();
let mut new_sparse_set_components = Vec::new();
let mut bundle_status = Vec::with_capacity(self.component_ids.len());
let mut added = Vec::new();
let current_archetype = &mut archetypes[archetype_id];
for component_id in self.component_ids.iter().cloned() {
@ -447,6 +463,7 @@ impl BundleInfo {
bundle_status.push(ComponentStatus::Mutated);
} else {
bundle_status.push(ComponentStatus::Added);
added.push(component_id);
// SAFETY: component_id exists
let component_info = unsafe { components.get_info_unchecked(component_id) };
match component_info.storage_type() {
@ -459,7 +476,7 @@ impl BundleInfo {
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
let edges = current_archetype.edges_mut();
// the archetype does not change when we add this bundle
edges.insert_add_bundle(self.id, archetype_id, bundle_status);
edges.insert_add_bundle(self.id, archetype_id, bundle_status, added);
archetype_id
} else {
let table_id;
@ -498,6 +515,7 @@ impl BundleInfo {
// SAFETY: ids in self must be valid
let new_archetype_id = archetypes.get_id_or_insert(
components,
observers,
table_id,
table_components,
sparse_set_components,
@ -507,6 +525,7 @@ impl BundleInfo {
self.id,
new_archetype_id,
bundle_status,
added,
);
new_archetype_id
}
@ -567,6 +586,7 @@ impl<'w> BundleInserter<'w> {
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
archetype_id,
);
if new_archetype_id == archetype_id {
@ -786,27 +806,21 @@ impl<'w> BundleInserter<'w> {
}
};
let new_archetype = &*new_archetype;
// SAFETY: We have no outstanding mutable references to world as they were dropped
let mut deferred_world = unsafe { self.world.into_deferred() };
if new_archetype.has_on_add() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(
entity,
bundle_info
.iter_components()
.zip(add_bundle.bundle_status.iter())
.filter(|(_, &status)| status == ComponentStatus::Added)
.map(|(id, _)| id),
);
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(new_archetype, entity, add_bundle.added.iter().cloned());
if new_archetype.has_add_observer() {
deferred_world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned());
}
deferred_world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components());
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components());
}
}
if new_archetype.has_on_insert() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) }
}
new_location
@ -853,6 +867,7 @@ impl<'w> BundleSpawner<'w> {
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
ArchetypeId::EMPTY,
);
let archetype = &mut world.archetypes[new_archetype_id];
@ -882,12 +897,12 @@ impl<'w> BundleSpawner<'w> {
entity: Entity,
bundle: T,
) -> EntityLocation {
let table = self.table.as_mut();
let archetype = self.archetype.as_mut();
// SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid
let bundle_info = self.bundle_info.as_ref();
// SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid
let location = {
let table = self.table.as_mut();
let archetype = self.archetype.as_mut();
// SAFETY: Mutable references do not alias and will be dropped after this block
let (sparse_sets, entities) = {
let world = self.world.world_mut();
@ -910,16 +925,20 @@ impl<'w> BundleSpawner<'w> {
// SAFETY: We have no outstanding mutable references to world as they were dropped
let mut deferred_world = unsafe { self.world.into_deferred() };
if archetype.has_on_add() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe { deferred_world.trigger_on_add(entity, bundle_info.iter_components()) };
}
if archetype.has_on_insert() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) };
}
// SAFETY: `DeferredWorld` cannot provide mutable access to `Archetypes`.
let archetype = self.archetype.as_ref();
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(archetype, entity, bundle_info.iter_components());
if archetype.has_add_observer() {
deferred_world.trigger_observers(ON_ADD, entity, bundle_info.iter_components());
}
deferred_world.trigger_on_insert(archetype, entity, bundle_info.iter_components());
if archetype.has_insert_observer() {
deferred_world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components());
}
};
location
}
@ -947,7 +966,7 @@ impl<'w> BundleSpawner<'w> {
#[inline]
pub(crate) unsafe fn flush_commands(&mut self) {
// SAFETY: pointers on self can be invalidated,
self.world.world_mut().flush_commands();
self.world.world_mut().flush();
}
}
@ -1223,13 +1242,13 @@ mod tests {
world
.register_component_hooks::<C>()
.on_add(|mut world, _, _| {
world.resource_mut::<R>().assert_order(2);
world.resource_mut::<R>().assert_order(3);
});
world
.register_component_hooks::<D>()
.on_add(|mut world, _, _| {
world.resource_mut::<R>().assert_order(3);
world.resource_mut::<R>().assert_order(2);
});
world.spawn(A).flush();

View File

@ -21,7 +21,6 @@ use std::{
borrow::Cow,
marker::PhantomData,
mem::needs_drop,
ops::Deref,
};
/// A data type that can be used to store data for an [entity].
@ -838,75 +837,6 @@ impl Components {
}
}
/// A wrapper over a mutable [`Components`] reference that allows for state initialization.
/// This can be obtained with [`World::component_initializer`].
pub struct ComponentInitializer<'w> {
pub(crate) components: &'w mut Components,
pub(crate) storages: &'w mut Storages,
}
impl<'w> Deref for ComponentInitializer<'w> {
type Target = Components;
fn deref(&self) -> &Components {
self.components
}
}
impl<'w> ComponentInitializer<'w> {
/// Initializes a component of type `T` with this instance.
/// If a component of this type has already been initialized, this will return
/// the ID of the pre-existing component.
///
/// # See also
///
/// * [`Components::component_id()`]
/// * [`Components::init_component_with_descriptor()`]
#[inline]
pub fn init_component<T: Component>(&mut self) -> ComponentId {
self.components.init_component::<T>(self.storages)
}
/// Initializes a component described by `descriptor`.
///
/// ## Note
///
/// If this method is called multiple times with identical descriptors, a distinct `ComponentId`
/// will be created for each one.
///
/// # See also
///
/// * [`Components::component_id()`]
/// * [`Components::init_component()`]
pub fn init_component_with_descriptor(
&mut self,
descriptor: ComponentDescriptor,
) -> ComponentId {
self.components
.init_component_with_descriptor(self.storages, descriptor)
}
/// Initializes a [`Resource`] of type `T` with this instance.
/// If a resource of this type has already been initialized, this will return
/// the ID of the pre-existing resource.
///
/// # See also
///
/// * [`Components::resource_id()`]
#[inline]
pub fn init_resource<T: Resource>(&mut self) -> ComponentId {
self.components.init_resource::<T>()
}
/// Initializes a [non-send resource](crate::system::NonSend) of type `T` with this instance.
/// If a resource of this type has already been initialized, this will return
/// the ID of the pre-existing resource.
#[inline]
pub fn init_non_send<T: Any>(&mut self) -> ComponentId {
self.components.init_non_send::<T>()
}
}
/// A value that tracks when a system ran relative to other systems.
/// This is used to power change detection.
///

View File

@ -38,7 +38,7 @@
mod map_entities;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serde"))]
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
pub use map_entities::*;
@ -57,7 +57,7 @@ use crate::{
},
storage::{SparseSetIndex, TableId, TableRow},
};
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::{fmt, hash::Hash, mem, num::NonZeroU32, sync::atomic::Ordering};
@ -146,7 +146,7 @@ type IdCursor = isize;
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect_value(Hash, PartialEq))]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serde"),
all(feature = "bevy_reflect", feature = "serialize"),
reflect_value(Serialize, Deserialize)
)]
// Alignment repr necessary to allow LLVM to better output
@ -368,7 +368,7 @@ impl From<Entity> for Identifier {
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl Serialize for Entity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -378,7 +378,7 @@ impl Serialize for Entity {
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl<'de> Deserialize<'de> for Entity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -392,13 +392,7 @@ impl<'de> Deserialize<'de> for Entity {
impl fmt::Display for Entity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}v{}|{}",
self.index(),
self.generation(),
self.to_bits()
)
write!(f, "{}v{}", self.index(), self.generation())
}
}
@ -1162,9 +1156,7 @@ mod tests {
fn entity_display() {
let entity = Entity::from_raw(42);
let string = format!("{}", entity);
let bits = entity.to_bits().to_string();
assert!(string.contains("42"));
assert!(string.contains("v1"));
assert!(string.contains(&bits));
}
}

View File

@ -1,11 +1,12 @@
//! Event handling types.
use crate as bevy_ecs;
#[cfg(feature = "multi_threaded")]
use crate::batching::BatchingStrategy;
use crate::change_detection::MutUntyped;
use crate::{
change_detection::{DetectChangesMut, Mut},
component::{ComponentId, Tick},
component::{Component, ComponentId, Tick},
system::{Local, Res, ResMut, Resource, SystemParam},
world::World,
};
@ -24,16 +25,30 @@ use std::{
slice::Iter,
};
/// A type that can be stored in an [`Events<E>`] resource
/// Something that "happens" and might be read / observed by app logic.
///
/// Events can be stored in an [`Events<E>`] resource
/// You can conveniently access events using the [`EventReader`] and [`EventWriter`] system parameter.
///
/// Events can also be "triggered" on a [`World`], which will then cause any [`Observer`] of that trigger to run.
///
/// This trait can be derived.
///
/// Events implement the [`Component`] type (and they automatically do when they are derived). Events are (generally)
/// not directly inserted as components. More often, the [`ComponentId`] is used to identify the event type within the
/// context of the ECS.
///
/// Events must be thread-safe.
///
/// [`World`]: crate::world::World
/// [`ComponentId`]: crate::component::ComponentId
/// [`Observer`]: crate::observer::Observer
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an `Event`",
label = "invalid `Event`",
note = "consider annotating `{Self}` with `#[derive(Event)]`"
)]
pub trait Event: Send + Sync + 'static {}
pub trait Event: Component {}
/// An `EventId` uniquely identifies an event stored in a specific [`World`].
///
@ -495,6 +510,7 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
/// assert_eq!(counter.into_inner(), 4950);
/// ```
///
#[cfg(feature = "multi_threaded")]
pub fn par_read(&mut self) -> EventParIter<'_, E> {
self.reader.par_read(&self.events)
}
@ -556,6 +572,11 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
///
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
/// # Observers
///
/// "Buffered" Events, such as those sent directly in [`Events`] or sent using [`EventWriter`], do _not_ automatically
/// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will
/// be triggered, and if so, _when_ it will be triggered in the schedule.
///
/// # Concurrency
///
@ -588,6 +609,8 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
/// }
/// ```
/// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work.
///
/// [`Observer`]: crate::observer::Observer
#[derive(SystemParam)]
pub struct EventWriter<'w, E: Event> {
events: ResMut<'w, Events<E>>,
@ -701,6 +724,7 @@ impl<E: Event> ManualEventReader<E> {
}
/// See [`EventReader::par_read`]
#[cfg(feature = "multi_threaded")]
pub fn par_read<'a>(&'a mut self, events: &'a Events<E>) -> EventParIter<'a, E> {
EventParIter::new(self, events)
}
@ -869,13 +893,16 @@ impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
}
/// A parallel iterator over `Event`s.
#[cfg(feature = "multi_threaded")]
#[derive(Debug)]
pub struct EventParIter<'a, E: Event> {
reader: &'a mut ManualEventReader<E>,
slices: [&'a [EventInstance<E>]; 2],
batching_strategy: BatchingStrategy,
unread: usize,
}
#[cfg(feature = "multi_threaded")]
impl<'a, E: Event> EventParIter<'a, E> {
/// Creates a new parallel iterator over `events` that have not yet been seen by `reader`.
pub fn new(reader: &'a mut ManualEventReader<E>, events: &'a Events<E>) -> Self {
@ -897,6 +924,7 @@ impl<'a, E: Event> EventParIter<'a, E> {
reader,
slices: [a, b],
batching_strategy: BatchingStrategy::default(),
unread: unread_count,
}
}
@ -932,7 +960,7 @@ impl<'a, E: Event> EventParIter<'a, E> {
/// initialized and run from the ECS scheduler, this should never panic.
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
pub fn for_each_with_id<FN: Fn(&'a E, EventId<E>) + Send + Sync + Clone>(self, func: FN) {
pub fn for_each_with_id<FN: Fn(&'a E, EventId<E>) + Send + Sync + Clone>(mut self, func: FN) {
#[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
{
self.into_iter().for_each(|(e, i)| func(e, i));
@ -962,6 +990,10 @@ impl<'a, E: Event> EventParIter<'a, E> {
});
}
});
// Events are guaranteed to be read at this point.
self.reader.last_event_count += self.unread;
self.unread = 0;
}
}
@ -976,6 +1008,7 @@ impl<'a, E: Event> EventParIter<'a, E> {
}
}
#[cfg(feature = "multi_threaded")]
impl<'a, E: Event> IntoIterator for EventParIter<'a, E> {
type IntoIter = EventIteratorWithId<'a, E>;
type Item = <Self::IntoIter as Iterator>::Item;
@ -1010,12 +1043,30 @@ struct RegisteredEvent {
/// to update all of the events.
#[derive(Resource, Default)]
pub struct EventRegistry {
needs_update: bool,
/// Should the events be updated?
///
/// This field is generally automatically updated by the [`signal_event_update_system`](crate::event::update::signal_event_update_system).
pub should_update: ShouldUpdateEvents,
event_updates: Vec<RegisteredEvent>,
}
/// Controls whether or not the events in an [`EventRegistry`] should be updated.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShouldUpdateEvents {
/// Without any fixed timestep, events should always be updated each frame.
#[default]
Always,
/// We need to wait until at least one pass of the fixed update schedules to update the events.
Waiting,
/// At least one pass of the fixed update schedules has occurred, and the events are ready to be updated.
Ready,
}
impl EventRegistry {
/// Registers an event type to be updated.
/// Registers an event type to be updated in a given [`World`]
///
/// If no instance of the [`EventRegistry`] exists in the world, this will add one - otherwise it will use
/// the existing instance.
pub fn register_event<T: Event>(world: &mut World) {
// By initializing the resource here, we can be sure that it is present,
// and receive the correct, up-to-date `ComponentId` even if it was previously removed.
@ -1033,6 +1084,16 @@ impl EventRegistry {
});
}
/// Removes an event from the world and it's associated [`EventRegistry`].
pub fn deregister_events<T: Event>(world: &mut World) {
let component_id = world.init_resource::<Events<T>>();
let mut registry = world.get_resource_or_insert_with(Self::default);
registry
.event_updates
.retain(|e| e.component_id != component_id);
world.remove_resource::<Events<T>>();
}
/// Updates all of the registered events in the World.
pub fn run_updates(&mut self, world: &mut World, last_change_tick: Tick) {
for registered_event in &mut self.event_updates {
@ -1058,9 +1119,12 @@ impl EventRegistry {
pub struct EventUpdates;
/// Signals the [`event_update_system`] to run after `FixedUpdate` systems.
///
/// This will change the behavior of the [`EventRegistry`] to only run after a fixed update cycle has passed.
/// Normally, this will simply run every frame.
pub fn signal_event_update_system(signal: Option<ResMut<EventRegistry>>) {
if let Some(mut registry) = signal {
registry.needs_update = true;
registry.should_update = ShouldUpdateEvents::Ready;
}
}
@ -1069,18 +1133,34 @@ pub fn event_update_system(world: &mut World, mut last_change_tick: Local<Tick>)
if world.contains_resource::<EventRegistry>() {
world.resource_scope(|world, mut registry: Mut<EventRegistry>| {
registry.run_updates(world, *last_change_tick);
// Disable the system until signal_event_update_system runs again.
registry.needs_update = false;
registry.should_update = match registry.should_update {
// If we're always updating, keep doing so.
ShouldUpdateEvents::Always => ShouldUpdateEvents::Always,
// Disable the system until signal_event_update_system runs again.
ShouldUpdateEvents::Waiting | ShouldUpdateEvents::Ready => {
ShouldUpdateEvents::Waiting
}
};
});
}
*last_change_tick = world.change_tick();
}
/// A run condition for [`event_update_system`].
pub fn event_update_condition(signal: Option<Res<EventRegistry>>) -> bool {
// If we haven't got a signal to update the events, but we *could* get such a signal
// return early and update the events later.
signal.map_or(false, |signal| signal.needs_update)
///
/// If [`signal_event_update_system`] has been run at least once,
/// we will wait for it to be run again before updating the events.
///
/// Otherwise, we will always update the events.
pub fn event_update_condition(maybe_signal: Option<Res<EventRegistry>>) -> bool {
match maybe_signal {
Some(signal) => match signal.should_update {
ShouldUpdateEvents::Always | ShouldUpdateEvents::Ready => true,
ShouldUpdateEvents::Waiting => false,
},
None => true,
}
}
/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch.
@ -1517,28 +1597,83 @@ mod tests {
#[cfg(feature = "multi_threaded")]
#[test]
fn test_events_par_iter() {
use std::{collections::HashSet, sync::mpsc};
use crate::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Resource)]
struct Counter(AtomicUsize);
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
for i in 0..100 {
world.send_event(TestEvent { i });
for _ in 0..100 {
world.send_event(TestEvent { i: 1 });
}
let mut schedule = Schedule::default();
schedule.add_systems(|mut events: EventReader<TestEvent>| {
let (tx, rx) = mpsc::channel();
events.par_read().for_each(|event| {
tx.send(event.i).unwrap();
});
drop(tx);
let observed: HashSet<_> = rx.into_iter().collect();
assert_eq!(observed, HashSet::from_iter(0..100));
});
schedule.add_systems(
|mut events: EventReader<TestEvent>, counter: ResMut<Counter>| {
events.par_read().for_each(|event| {
counter.0.fetch_add(event.i, Ordering::Relaxed);
});
},
);
world.insert_resource(Counter(AtomicUsize::new(0)));
schedule.run(&mut world);
let counter = world.remove_resource::<Counter>().unwrap();
assert_eq!(counter.0.into_inner(), 100);
world.insert_resource(Counter(AtomicUsize::new(0)));
schedule.run(&mut world);
let counter = world.remove_resource::<Counter>().unwrap();
assert_eq!(counter.0.into_inner(), 0);
}
#[test]
fn iter_current_update_events_iterates_over_current_events() {
#[derive(Event, Clone)]
struct TestEvent;
let mut test_events = Events::<TestEvent>::default();
// Starting empty
assert_eq!(test_events.len(), 0);
assert_eq!(test_events.iter_current_update_events().count(), 0);
test_events.update();
// Sending one event
test_events.send(TestEvent);
assert_eq!(test_events.len(), 1);
assert_eq!(test_events.iter_current_update_events().count(), 1);
test_events.update();
// Sending two events on the next frame
test_events.send(TestEvent);
test_events.send(TestEvent);
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
assert_eq!(test_events.iter_current_update_events().count(), 2);
test_events.update();
// Sending zero events
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
assert_eq!(test_events.iter_current_update_events().count(), 0);
}
#[test]
fn test_event_registry_can_add_and_remove_events_to_world() {
use bevy_ecs::prelude::*;
let mut world = World::new();
EventRegistry::register_event::<TestEvent>(&mut world);
let has_events = world.get_resource::<Events<TestEvent>>().is_some();
assert!(has_events, "Should have the events resource");
EventRegistry::deregister_events::<TestEvent>(&mut world);
let has_events = world.get_resource::<Events<TestEvent>>().is_some();
assert!(!has_events, "Should not have the events resource");
}
}

View File

@ -21,6 +21,7 @@ pub mod event;
pub mod identifier;
pub mod intern;
pub mod label;
pub mod observer;
pub mod query;
#[cfg(feature = "bevy_reflect")]
pub mod reflect;
@ -45,7 +46,8 @@ pub mod prelude {
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
component::Component,
entity::{Entity, EntityMapper},
event::{Event, EventReader, EventWriter, Events},
event::{Event, EventReader, EventWriter, Events, ShouldUpdateEvents},
observer::{Observer, Trigger},
query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
removal_detection::RemovedComponents,
schedule::{
@ -57,7 +59,9 @@ pub mod prelude {
ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemBuilder,
SystemParamFunction,
},
world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World},
world::{
EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, World,
},
};
}

View File

@ -0,0 +1,42 @@
use crate::{
component::{Component, ComponentHooks, StorageType},
entity::Entity,
observer::ObserverState,
};
/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to.
#[derive(Default)]
pub(crate) struct ObservedBy(pub(crate) Vec<Entity>);
impl Component for ObservedBy {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_remove(|mut world, entity, _| {
let observed_by = {
let mut component = world.get_mut::<ObservedBy>(entity).unwrap();
std::mem::take(&mut component.0)
};
for e in observed_by {
let (total_entities, despawned_watched_entities) = {
let Some(mut entity_mut) = world.get_entity_mut(e) else {
continue;
};
let Some(mut state) = entity_mut.get_mut::<ObserverState>() else {
continue;
};
state.despawned_watched_entities += 1;
(
state.descriptor.entities.len(),
state.despawned_watched_entities as usize,
)
};
// Despawn Observer if it has no more active sources.
if total_entities == despawned_watched_entities {
world.commands().entity(e).despawn();
}
}
});
}
}

View File

@ -0,0 +1,665 @@
//! Types for creating and storing [`Observer`]s
mod entity_observer;
mod runner;
mod trigger_event;
pub use runner::*;
pub use trigger_event::*;
use crate::observer::entity_observer::ObservedBy;
use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*};
use crate::{component::ComponentId, prelude::*, world::DeferredWorld};
use bevy_ptr::Ptr;
use bevy_utils::{EntityHashMap, HashMap};
use std::marker::PhantomData;
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well.
pub struct Trigger<'w, E, B: Bundle = ()> {
event: &'w mut E,
trigger: ObserverTrigger,
_marker: PhantomData<B>,
}
impl<'w, E, B: Bundle> Trigger<'w, E, B> {
/// Creates a new trigger for the given event and observer information.
pub fn new(event: &'w mut E, trigger: ObserverTrigger) -> Self {
Self {
event,
trigger,
_marker: PhantomData,
}
}
/// Returns the event type of this trigger.
pub fn event_type(&self) -> ComponentId {
self.trigger.event_type
}
/// Returns a reference to the triggered event.
pub fn event(&self) -> &E {
self.event
}
/// Returns a mutable reference to the triggered event.
pub fn event_mut(&mut self) -> &mut E {
self.event
}
/// Returns a pointer to the triggered event.
pub fn event_ptr(&self) -> Ptr {
Ptr::from(&self.event)
}
/// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`].
pub fn entity(&self) -> Entity {
self.trigger.entity
}
}
/// A description of what an [`Observer`] observes.
#[derive(Default, Clone)]
pub struct ObserverDescriptor {
/// The events the observer is watching.
events: Vec<ComponentId>,
/// The components the observer is watching.
components: Vec<ComponentId>,
/// The entities the observer is watching.
entities: Vec<Entity>,
}
impl ObserverDescriptor {
/// Add the given `events` to the descriptor.
/// # Safety
/// The type of each [`ComponentId`] in `events` _must_ match the actual value
/// of the event passed into the observer.
pub unsafe fn with_events(mut self, events: Vec<ComponentId>) -> Self {
self.events = events;
self
}
/// Add the given `components` to the descriptor.
pub fn with_components(mut self, components: Vec<ComponentId>) -> Self {
self.components = components;
self
}
/// Add the given `entities` to the descriptor.
pub fn with_entities(mut self, entities: Vec<Entity>) -> Self {
self.entities = entities;
self
}
pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) {
self.events.extend(descriptor.events.iter().copied());
self.components
.extend(descriptor.components.iter().copied());
self.entities.extend(descriptor.entities.iter().copied());
}
}
/// Event trigger metadata for a given [`Observer`],
#[derive(Debug)]
pub struct ObserverTrigger {
/// The [`Entity`] of the observer handling the trigger.
pub observer: Entity,
/// The [`ComponentId`] the trigger targeted.
pub event_type: ComponentId,
/// The entity the trigger targeted.
pub entity: Entity,
}
// Map between an observer entity and its runner
type ObserverMap = EntityHashMap<Entity, ObserverRunner>;
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component.
#[derive(Default, Debug)]
pub struct CachedComponentObservers {
// Observers listening to triggers targeting this component
map: ObserverMap,
// Observers listening to triggers targeting this component on a specific entity
entity_map: EntityHashMap<Entity, ObserverMap>,
}
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger.
#[derive(Default, Debug)]
pub struct CachedObservers {
// Observers listening for any time this trigger is fired
map: ObserverMap,
// Observers listening for this trigger fired at a specific component
component_observers: HashMap<ComponentId, CachedComponentObservers>,
// Observers listening for this trigger fired at a specific entity
entity_observers: EntityHashMap<Entity, ObserverMap>,
}
/// Metadata for observers. Stores a cache mapping trigger ids to the registered observers.
#[derive(Default, Debug)]
pub struct Observers {
// Cached ECS observers to save a lookup most common triggers.
on_add: CachedObservers,
on_insert: CachedObservers,
on_remove: CachedObservers,
// Map from trigger type to set of observers
cache: HashMap<ComponentId, CachedObservers>,
}
impl Observers {
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
match event_type {
ON_ADD => &mut self.on_add,
ON_INSERT => &mut self.on_insert,
ON_REMOVE => &mut self.on_remove,
_ => self.cache.entry(event_type).or_default(),
}
}
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
match event_type {
ON_ADD => Some(&self.on_add),
ON_INSERT => Some(&self.on_insert),
ON_REMOVE => Some(&self.on_remove),
_ => self.cache.get(&event_type),
}
}
/// This will run the observers of the given `event_type`, targeting the given `entity` and `components`.
pub(crate) fn invoke<T>(
mut world: DeferredWorld,
event_type: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
data: &mut T,
) {
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
let (mut world, observers) = unsafe {
let world = world.as_unsafe_world_cell();
// SAFETY: There are no outstanding world references
world.increment_trigger_id();
let observers = world.observers();
let Some(observers) = observers.try_get_observers(event_type) else {
return;
};
// SAFETY: The only outstanding reference to world is `observers`
(world.into_deferred(), observers)
};
let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| {
(runner)(
world.reborrow(),
ObserverTrigger {
observer,
event_type,
entity,
},
data.into(),
);
};
// Trigger observers listening for any kind of this trigger
observers.map.iter().for_each(&mut trigger_observer);
// Trigger entity observers listening for this kind of trigger
if entity != Entity::PLACEHOLDER {
if let Some(map) = observers.entity_observers.get(&entity) {
map.iter().for_each(&mut trigger_observer);
}
}
// Trigger observers listening to this trigger targeting a specific component
components.for_each(|id| {
if let Some(component_observers) = observers.component_observers.get(&id) {
component_observers
.map
.iter()
.for_each(&mut trigger_observer);
if entity != Entity::PLACEHOLDER {
if let Some(map) = component_observers.entity_map.get(&entity) {
map.iter().for_each(&mut trigger_observer);
}
}
}
});
}
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
match event_type {
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
_ => None,
}
}
pub(crate) fn update_archetype_flags(
&self,
component_id: ComponentId,
flags: &mut ArchetypeFlags,
) {
if self.on_add.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
}
if self
.on_insert
.component_observers
.contains_key(&component_id)
{
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
}
if self
.on_remove
.component_observers
.contains_key(&component_id)
{
flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER);
}
}
}
impl World {
/// Spawn a "global" [`Observer`] and returns it's [`Entity`].
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
) -> EntityWorldMut {
self.spawn(Observer::new(system))
}
/// Triggers the given `event`, which will run any observers watching for it.
pub fn trigger(&mut self, event: impl Event) {
TriggerEvent { event, targets: () }.apply(self);
}
/// Triggers the given `event` for the given `targets`, which will run any observers watching for it.
pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) {
TriggerEvent { event, targets }.apply(self);
}
/// Register an observer to the cache, called when an observer is created
pub(crate) fn register_observer(&mut self, observer_entity: Entity) {
// SAFETY: References do not alias.
let (observer_state, archetypes, observers) = unsafe {
let observer_state: *const ObserverState =
self.get::<ObserverState>(observer_entity).unwrap();
// Populate ObservedBy for each observed entity.
for watched_entity in &(*observer_state).descriptor.entities {
let mut entity_mut = self.entity_mut(*watched_entity);
let mut observed_by = entity_mut.entry::<ObservedBy>().or_default();
observed_by.0.push(observer_entity);
}
(&*observer_state, &mut self.archetypes, &mut self.observers)
};
let descriptor = &observer_state.descriptor;
for &event_type in &descriptor.events {
let cache = observers.get_observers(event_type);
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
cache.map.insert(observer_entity, observer_state.runner);
} else if descriptor.components.is_empty() {
// Observer is not targeting any components so register it as an entity observer
for &watched_entity in &observer_state.descriptor.entities {
let map = cache.entity_observers.entry(watched_entity).or_default();
map.insert(observer_entity, observer_state.runner);
}
} else {
// Register observer for each watched component
for &component in &descriptor.components {
let observers =
cache
.component_observers
.entry(component)
.or_insert_with(|| {
if let Some(flag) = Observers::is_archetype_cached(event_type) {
archetypes.update_flags(component, flag, true);
}
CachedComponentObservers::default()
});
if descriptor.entities.is_empty() {
// Register for all triggers targeting the component
observers.map.insert(observer_entity, observer_state.runner);
} else {
// Register for each watched entity
for &watched_entity in &descriptor.entities {
let map = observers.entity_map.entry(watched_entity).or_default();
map.insert(observer_entity, observer_state.runner);
}
}
}
}
}
}
/// Remove the observer from the cache, called when an observer gets despawned
pub(crate) fn unregister_observer(&mut self, entity: Entity, descriptor: ObserverDescriptor) {
let archetypes = &mut self.archetypes;
let observers = &mut self.observers;
for &event_type in &descriptor.events {
let cache = observers.get_observers(event_type);
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
cache.map.remove(&entity);
} else if descriptor.components.is_empty() {
for watched_entity in &descriptor.entities {
// This check should be unnecessary since this observer hasn't been unregistered yet
let Some(observers) = cache.entity_observers.get_mut(watched_entity) else {
continue;
};
observers.remove(&entity);
if observers.is_empty() {
cache.entity_observers.remove(watched_entity);
}
}
} else {
for component in &descriptor.components {
let Some(observers) = cache.component_observers.get_mut(component) else {
continue;
};
if descriptor.entities.is_empty() {
observers.map.remove(&entity);
} else {
for watched_entity in &descriptor.entities {
let Some(map) = observers.entity_map.get_mut(watched_entity) else {
continue;
};
map.remove(&entity);
if map.is_empty() {
observers.entity_map.remove(watched_entity);
}
}
}
if observers.map.is_empty() && observers.entity_map.is_empty() {
cache.component_observers.remove(component);
if let Some(flag) = Observers::is_archetype_cached(event_type) {
archetypes.update_flags(*component, flag, false);
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use bevy_ptr::OwningPtr;
use crate as bevy_ecs;
use crate::observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState};
use crate::prelude::*;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
#[derive(Component)]
struct C;
#[derive(Component)]
#[component(storage = "SparseSet")]
struct S;
#[derive(Event)]
struct EventA;
#[derive(Resource, Default)]
struct R(usize);
impl R {
#[track_caller]
fn assert_order(&mut self, count: usize) {
assert_eq!(count, self.0);
self.0 += 1;
}
}
#[test]
fn observer_order_spawn_despawn() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_insert_remove() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
let mut entity = world.spawn_empty();
entity.insert(A);
entity.remove::<A>();
entity.flush();
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_insert_remove_sparse() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(2));
let mut entity = world.spawn_empty();
entity.insert(S);
entity.remove::<S>();
entity.flush();
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_recursive() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(
|obs: Trigger<OnAdd, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(0);
commands.entity(obs.entity()).insert(B);
},
);
world.observe(
|obs: Trigger<OnRemove, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(2);
commands.entity(obs.entity()).remove::<B>();
},
);
world.observe(
|obs: Trigger<OnAdd, B>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(1);
commands.entity(obs.entity()).remove::<A>();
},
);
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<R>| {
res.assert_order(3);
});
let entity = world.spawn(A).flush();
let entity = world.get_entity(entity).unwrap();
assert!(!entity.contains::<A>());
assert!(!entity.contains::<B>());
assert_eq!(4, world.resource::<R>().0);
}
#[test]
fn observer_multiple_listeners() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.spawn(A).flush();
assert_eq!(2, world.resource::<R>().0);
// Our A entity plus our two observers
assert_eq!(world.entities().len(), 3);
}
#[test]
fn observer_multiple_events() {
let mut world = World::new();
world.init_resource::<R>();
let on_remove = world.init_component::<OnRemove>();
world.spawn(
// SAFETY: OnAdd and OnRemove are both unit types, so this is safe
unsafe {
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1)
.with_event(on_remove)
},
);
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_multiple_components() {
let mut world = World::new();
world.init_resource::<R>();
world.init_component::<A>();
world.init_component::<B>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
let entity = world.spawn(A).id();
world.entity_mut(entity).insert(B);
world.flush();
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_despawn() {
let mut world = World::new();
world.init_resource::<R>();
let observer = world
.observe(|_: Trigger<OnAdd, A>| panic!("Observer triggered after being despawned."))
.id();
world.despawn(observer);
world.spawn(A).flush();
}
#[test]
fn observer_multiple_matches() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
world.spawn((A, B)).flush();
assert_eq!(1, world.resource::<R>().0);
}
#[test]
fn observer_no_target() {
let mut world = World::new();
world.init_resource::<R>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
assert_eq!(obs.entity(), Entity::PLACEHOLDER);
res.0 += 1;
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush.
world.flush();
world.trigger(EventA);
world.flush();
assert_eq!(1, world.resource::<R>().0);
}
#[test]
fn observer_entity_routing() {
let mut world = World::new();
world.init_resource::<R>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventA>, mut res: ResMut<R>| res.0 += 1)
.id();
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
assert_eq!(obs.entity(), entity);
res.0 += 1;
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush.
world.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_dynamic_component() {
let mut world = World::new();
world.init_resource::<R>();
let component_id = world.init_component::<A>();
world.spawn(
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<R>| res.0 += 1)
.with_component(component_id),
);
let mut entity = world.spawn_empty();
OwningPtr::make(A, |ptr| {
// SAFETY: we registered `component_id` above.
unsafe { entity.insert_by_id(component_id, ptr) };
});
let entity = entity.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(1, world.resource::<R>().0);
}
#[test]
fn observer_dynamic_trigger() {
let mut world = World::new();
world.init_resource::<R>();
let event_a = world.init_component::<EventA>();
world.spawn(ObserverState {
// SAFETY: we registered `event_a` above and it matches the type of TriggerA
descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) },
runner: |mut world, _trigger, _ptr| {
world.resource_mut::<R>().0 += 1;
},
..Default::default()
});
world.commands().add(
// SAFETY: we registered `event_a` above and it matches the type of TriggerA
unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) },
);
world.flush();
assert_eq!(1, world.resource::<R>().0);
}
}

View File

@ -0,0 +1,412 @@
use crate::{
component::{ComponentHooks, ComponentId, StorageType},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
system::{IntoObserverSystem, ObserverSystem},
world::DeferredWorld,
};
use bevy_ptr::PtrMut;
/// Contains [`Observer`] information. This defines how a given observer behaves. It is the
/// "source of truth" for a given observer entity's behavior.
pub struct ObserverState {
pub(crate) descriptor: ObserverDescriptor,
pub(crate) runner: ObserverRunner,
pub(crate) last_trigger_id: u32,
pub(crate) despawned_watched_entities: u32,
}
impl Default for ObserverState {
fn default() -> Self {
Self {
runner: |_, _, _| {},
last_trigger_id: 0,
despawned_watched_entities: 0,
descriptor: Default::default(),
}
}
}
impl ObserverState {
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
/// is triggered.
pub fn with_event(mut self, event: ComponentId) -> Self {
self.descriptor.events.push(event);
self
}
/// Observe the given event list. This will cause the [`Observer`] to run whenever an event with any of the given [`ComponentId`]s
/// is triggered.
pub fn with_events(mut self, events: impl IntoIterator<Item = ComponentId>) -> Self {
self.descriptor.events.extend(events);
self
}
/// Observe the given [`Entity`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for any [`Entity`] target in the list.
pub fn with_entities(mut self, entities: impl IntoIterator<Item = Entity>) -> Self {
self.descriptor.entities.extend(entities);
self
}
/// Observe the given [`ComponentId`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for any [`ComponentId`] target in the list.
pub fn with_components(mut self, components: impl IntoIterator<Item = ComponentId>) -> Self {
self.descriptor.components.extend(components);
self
}
}
impl Component for ObserverState {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|mut world, entity, _| {
world.commands().add(move |world: &mut World| {
world.register_observer(entity);
});
});
hooks.on_remove(|mut world, entity, _| {
let descriptor = std::mem::take(
&mut world
.entity_mut(entity)
.get_mut::<ObserverState>()
.unwrap()
.as_mut()
.descriptor,
);
world.commands().add(move |world: &mut World| {
world.unregister_observer(entity, descriptor);
});
});
}
}
/// Type for function that is run when an observer is triggered.
/// Typically refers to the default runner that runs the system stored in the associated [`ObserverSystemComponent`],
/// but can be overridden for custom behaviour.
pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut);
/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer".
///
/// Observers listen for a "trigger" of a specific [`Event`]. Events are triggered by calling [`World::trigger`] or [`World::trigger_targets`].
///
/// Note that "buffered" events sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered. They must be triggered at a specific
/// point in the schedule.
///
/// # Usage
///
/// The simplest usage
/// of the observer pattern looks like this:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// #[derive(Event)]
/// struct Speak {
/// message: String,
/// }
///
/// world.observe(|trigger: Trigger<Speak>| {
/// println!("{}", trigger.event().message);
/// });
///
/// // Observers currently require a flush() to be registered. In the context of schedules,
/// // this will generally be done for you.
/// world.flush();
///
/// world.trigger(Speak {
/// message: "Hello!".into(),
/// });
/// ```
///
/// Notice that we used [`World::observe`]. This is just a shorthand for spawning an [`Observer`] manually:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct Speak;
/// // These are functionally the same:
/// world.observe(|trigger: Trigger<Speak>| {});
/// world.spawn(Observer::new(|trigger: Trigger<Speak>| {}));
/// ```
///
/// Observers are systems. They can access arbitrary [`World`] data by adding [`SystemParam`]s:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct PrintNames;
/// # #[derive(Component, Debug)]
/// # struct Name;
/// world.observe(|trigger: Trigger<PrintNames>, names: Query<&Name>| {
/// for name in &names {
/// println!("{name:?}");
/// }
/// });
/// ```
///
/// Note that [`Trigger`] must always be the first parameter.
///
/// You can also add [`Commands`], which means you can spawn new entities, insert new components, etc:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct SpawnThing;
/// # #[derive(Component, Debug)]
/// # struct Thing;
/// world.observe(|trigger: Trigger<SpawnThing>, mut commands: Commands| {
/// commands.spawn(Thing);
/// });
/// ```
///
/// Observers can also trigger new events:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct A;
/// # #[derive(Event)]
/// # struct B;
/// world.observe(|trigger: Trigger<A>, mut commands: Commands| {
/// commands.trigger(B);
/// });
/// ```
///
/// When the commands are flushed (including these "nested triggers") they will be
/// recursively evaluated until there are no commands left, meaning nested triggers all
/// evaluate at the same time!
///
/// Events can be triggered for entities, which will be passed to the [`Observer`]:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// #[derive(Event)]
/// struct Explode;
///
/// world.observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("Entity {:?} goes BOOM!", trigger.entity());
/// commands.entity(trigger.entity()).despawn();
/// });
///
/// world.flush();
///
/// world.trigger_targets(Explode, entity);
/// ```
///
/// You can trigger multiple entities at once:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event)]
/// # struct Explode;
/// world.trigger_targets(Explode, [e1, e2]);
/// ```
///
/// Observers can also watch _specific_ entities, which enables you to assign entity-specific logic:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component, Debug)]
/// # struct Name(String);
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event)]
/// # struct Explode;
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("Boom!");
/// commands.entity(trigger.entity()).despawn();
/// });
///
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("The explosion fizzles! This entity is immune!");
/// });
/// ```
///
/// If all entities watched by a given [`Observer`] are despawned, the [`Observer`] entity will also be despawned.
/// This protects against observer "garbage" building up over time.
///
/// The examples above calling [`EntityWorldMut::observe`] to add entity-specific observer logic are (once again)
/// just shorthand for spawning an [`Observer`] directly:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// # #[derive(Event)]
/// # struct Explode;
/// let mut observer = Observer::new(|trigger: Trigger<Explode>| {});
/// observer.watch_entity(entity);
/// world.spawn(observer);
/// ```
///
/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities!
///
/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`].
///
/// When first added, [`Observer`] will also create an [`ObserverState`] component, which registers the observer with the [`World`] and
/// serves as the "source of truth" of the observer.
///
/// [`SystemParam`]: crate::system::SystemParam
pub struct Observer<T: 'static, B: Bundle> {
system: BoxedObserverSystem<T, B>,
descriptor: ObserverDescriptor,
}
impl<E: Event, B: Bundle> Observer<E, B> {
/// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered
/// for _any_ entity (or no entity).
pub fn new<M>(system: impl IntoObserverSystem<E, B, M>) -> Self {
Self {
system: Box::new(IntoObserverSystem::into_system(system)),
descriptor: Default::default(),
}
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
pub fn with_entity(mut self, entity: Entity) -> Self {
self.descriptor.entities.push(entity);
self
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
/// Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects.
pub fn watch_entity(&mut self, entity: Entity) {
self.descriptor.entities.push(entity);
}
/// Observe the given `component`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// with the given component target.
pub fn with_component(mut self, component: ComponentId) -> Self {
self.descriptor.components.push(component);
self
}
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
/// is triggered.
/// # Safety
/// The type of the `event` [`ComponentId`] _must_ match the actual value
/// of the event passed into the observer system.
pub unsafe fn with_event(mut self, event: ComponentId) -> Self {
self.descriptor.events.push(event);
self
}
}
impl<E: Event, B: Bundle> Component for Observer<E, B> {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|mut world, entity, _| {
world.commands().add(move |world: &mut World| {
let event_type = world.init_component::<E>();
let mut components = Vec::new();
B::component_ids(&mut world.components, &mut world.storages, &mut |id| {
components.push(id);
});
let mut descriptor = ObserverDescriptor {
events: vec![event_type],
components,
..Default::default()
};
// Initialize System
let system: *mut dyn ObserverSystem<E, B> =
if let Some(mut observe) = world.get_mut::<Self>(entity) {
descriptor.merge(&observe.descriptor);
&mut *observe.system
} else {
return;
};
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias
unsafe {
(*system).initialize(world);
}
{
let mut entity = world.entity_mut(entity);
if let crate::world::Entry::Vacant(entry) = entity.entry::<ObserverState>() {
entry.insert(ObserverState {
descriptor,
runner: observer_system_runner::<E, B>,
..Default::default()
});
}
}
});
});
}
}
/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`].
pub type BoxedObserverSystem<E = (), B = ()> = Box<dyn ObserverSystem<E, B>>;
fn observer_system_runner<E: Event, B: Bundle>(
mut world: DeferredWorld,
observer_trigger: ObserverTrigger,
ptr: PtrMut,
) {
let world = world.as_unsafe_world_cell();
// SAFETY: Observer was triggered so must still exist in world
let observer_cell = unsafe {
world
.get_entity(observer_trigger.observer)
.debug_checked_unwrap()
};
// SAFETY: Observer was triggered so must have an `ObserverState`
let mut state = unsafe {
observer_cell
.get_mut::<ObserverState>()
.debug_checked_unwrap()
};
// TODO: Move this check into the observer cache to avoid dynamic dispatch
// SAFETY: We only access world metadata
let last_trigger = unsafe { world.world_metadata() }.last_trigger_id();
if state.last_trigger_id == last_trigger {
return;
}
state.last_trigger_id = last_trigger;
// SAFETY: Caller ensures `ptr` is castable to `&mut T`
let trigger: Trigger<E, B> = Trigger::new(unsafe { ptr.deref_mut() }, observer_trigger);
// SAFETY: the static lifetime is encapsulated in Trigger / cannot leak out.
// Additionally, IntoObserverSystem is only implemented for functions starting
// with for<'a> Trigger<'a>, meaning users cannot specify Trigger<'static> manually,
// allowing the Trigger<'static> to be moved outside of the context of the system.
// This transmute is obviously not ideal, but it is safe. Ideally we can remove the
// static constraint from ObserverSystem, but so far we have not found a way.
let trigger: Trigger<'static, E, B> = unsafe { std::mem::transmute(trigger) };
// SAFETY: Observer was triggered so must have an `ObserverSystemComponent`
let system = unsafe {
&mut observer_cell
.get_mut::<Observer<E, B>>()
.debug_checked_unwrap()
.system
};
system.update_archetype_component_access(world);
// SAFETY:
// - `update_archetype_component_access` was just called
// - there are no outstanding references to world except a private component
// - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld`
// - system is the same type erased system from above
unsafe {
system.run_unsafe(trigger, world);
system.queue_deferred(world.into_deferred());
}
}

View File

@ -0,0 +1,165 @@
use crate::{
component::ComponentId,
entity::Entity,
event::Event,
world::{Command, DeferredWorld, World},
};
/// A [`Command`] that emits a given trigger for a given set of targets.
pub struct TriggerEvent<E, Targets: TriggerTargets = ()> {
/// The event to trigger.
pub event: E,
/// The targets to trigger the event for.
pub targets: Targets,
}
impl<E: Event, Targets: TriggerTargets> Command for TriggerEvent<E, Targets> {
fn apply(mut self, world: &mut World) {
let event_type = world.init_component::<E>();
trigger_event(world, event_type, &mut self.event, self.targets);
}
}
/// Emit a trigger for a dynamic component id. This is unsafe and must be verified manually.
pub struct EmitDynamicTrigger<T, Targets: TriggerTargets = ()> {
event_type: ComponentId,
event_data: T,
targets: Targets,
}
impl<E, Targets: TriggerTargets> EmitDynamicTrigger<E, Targets> {
/// Sets the event type of the resulting trigger, used for dynamic triggers
/// # Safety
/// Caller must ensure that the component associated with `event_type` is accessible as E
pub unsafe fn new_with_id(event_type: ComponentId, event_data: E, targets: Targets) -> Self {
Self {
event_type,
event_data,
targets,
}
}
}
impl<E: Event, Targets: TriggerTargets> Command for EmitDynamicTrigger<E, Targets> {
fn apply(mut self, world: &mut World) {
trigger_event(world, self.event_type, &mut self.event_data, self.targets);
}
}
#[inline]
fn trigger_event<E, Targets: TriggerTargets>(
world: &mut World,
event_type: ComponentId,
event_data: &mut E,
targets: Targets,
) {
let mut world = DeferredWorld::from(world);
if targets.entities().len() == 0 {
// SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new`
unsafe {
world.trigger_observers_with_data(
event_type,
Entity::PLACEHOLDER,
targets.components(),
event_data,
);
};
} else {
for target in targets.entities() {
// SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new`
unsafe {
world.trigger_observers_with_data(
event_type,
target,
targets.components(),
event_data,
);
};
}
}
}
/// Represents a collection of targets for a specific [`Trigger`] of an [`Event`]. Targets can be of type [`Entity`] or [`ComponentId`].
/// When a trigger occurs for a given event and [`TriggerTargets`], any [`Observer`] that watches for that specific event-target combination
/// will run.
///
/// [`Trigger`]: crate::observer::Trigger
/// [`Observer`]: crate::observer::Observer
pub trait TriggerTargets: Send + Sync + 'static {
/// The components the trigger should target.
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId>;
/// The entities the trigger should target.
fn entities(&self) -> impl ExactSizeIterator<Item = Entity>;
}
impl TriggerTargets for () {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl TriggerTargets for Entity {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
std::iter::once(*self)
}
}
impl TriggerTargets for Vec<Entity> {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
self.iter().copied()
}
}
impl<const N: usize> TriggerTargets for [Entity; N] {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
self.iter().copied()
}
}
impl TriggerTargets for ComponentId {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
std::iter::once(*self)
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl TriggerTargets for Vec<ComponentId> {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
self.iter().copied()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl<const N: usize> TriggerTargets for [ComponentId; N] {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
self.iter().copied()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}

View File

@ -647,6 +647,16 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
.extend(filtered_access_set.filtered_accesses);
}
/// Marks the set as reading all possible indices of type T.
pub fn read_all(&mut self) {
self.combined_access.read_all();
}
/// Marks the set as writing all T.
pub fn write_all(&mut self) {
self.combined_access.write_all();
}
/// Removes all accesses stored in this set.
pub fn clear(&mut self) {
self.combined_access.clear();

View File

@ -43,9 +43,8 @@ pub struct QueryBuilder<'w, D: QueryData = (), F: QueryFilter = ()> {
impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
/// Creates a new builder with the accesses required for `Q` and `F`
pub fn new(world: &'w mut World) -> Self {
let initializer = &mut world.component_initializer();
let fetch_state = D::init_state(initializer);
let filter_state = F::init_state(initializer);
let fetch_state = D::init_state(world);
let filter_state = F::init_state(world);
let mut access = FilteredAccess::default();
D::update_component_access(&fetch_state, &mut access);
@ -96,7 +95,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
/// Adds accesses required for `T` to self.
pub fn data<T: QueryData>(&mut self) -> &mut Self {
let state = T::init_state(&mut self.world.component_initializer());
let state = T::init_state(self.world);
let mut access = FilteredAccess::default();
T::update_component_access(&state, &mut access);
self.extend_access(access);
@ -105,7 +104,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
/// Adds filter from `T` to self.
pub fn filter<T: QueryFilter>(&mut self) -> &mut Self {
let state = T::init_state(&mut self.world.component_initializer());
let state = T::init_state(self.world);
let mut access = FilteredAccess::default();
T::update_component_access(&state, &mut access);
self.extend_access(access);
@ -223,9 +222,8 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
pub fn transmute_filtered<NewD: QueryData, NewF: QueryFilter>(
&mut self,
) -> &mut QueryBuilder<'w, NewD, NewF> {
let initializer = &mut self.world.component_initializer();
let mut fetch_state = NewD::init_state(initializer);
let filter_state = NewF::init_state(initializer);
let mut fetch_state = NewD::init_state(self.world);
let filter_state = NewF::init_state(self.world);
NewD::set_access(&mut fetch_state, &self.access);

View File

@ -1,20 +1,20 @@
use crate::{
archetype::{Archetype, Archetypes},
change_detection::{Ticks, TicksMut},
component::{Component, ComponentId, ComponentInitializer, Components, StorageType, Tick},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{ComponentSparseSet, Table, TableRow},
world::{
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, FilteredEntityMut,
FilteredEntityRef, Mut, Ref,
FilteredEntityRef, Mut, Ref, World,
},
};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
use std::{cell::UnsafeCell, marker::PhantomData};
/// Types that can be fetched from a [`World`](crate::world::World) using a [`Query`].
/// Types that can be fetched from a [`World`] using a [`Query`].
///
/// There are many types that natively implement this trait:
///
@ -335,7 +335,7 @@ unsafe impl WorldQuery for Entity {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -407,7 +407,7 @@ unsafe impl WorldQuery for EntityLocation {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -486,7 +486,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
access.read_all();
}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -562,7 +562,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> {
access.write_all();
}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -660,7 +660,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
filtered_access.access.extend(&state.access);
}
fn init_state(_initializer: &mut ComponentInitializer) -> Self::State {
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
}
@ -772,7 +772,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
filtered_access.access.extend(&state.access);
}
fn init_state(_initializer: &mut ComponentInitializer) -> Self::State {
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
}
@ -846,7 +846,7 @@ unsafe impl WorldQuery for &Archetype {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -995,8 +995,8 @@ unsafe impl<T: Component> WorldQuery for &T {
access.add_read(component_id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1178,8 +1178,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
access.add_read(component_id);
}
fn init_state(initializer: &mut ComponentInitializer<'_>) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1361,8 +1361,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
access.add_write(component_id);
}
fn init_state(initializer: &mut ComponentInitializer<'_>) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1460,8 +1460,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> {
}
// Forwarded to `&mut T`
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
<&mut T as WorldQuery>::init_state(initializer)
fn init_state(world: &mut World) -> ComponentId {
<&mut T as WorldQuery>::init_state(world)
}
// Forwarded to `&mut T`
@ -1581,8 +1581,8 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
access.extend_access(&intermediate);
}
fn init_state(initializer: &mut ComponentInitializer) -> T::State {
T::init_state(initializer)
fn init_state(world: &mut World) -> T::State {
T::init_state(world)
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1736,8 +1736,8 @@ unsafe impl<T: Component> WorldQuery for Has<T> {
access.access_mut().add_archetypal(component_id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1862,16 +1862,17 @@ macro_rules! impl_anytuple_fetch {
}
fn update_component_access(state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {
let ($($name,)*) = state;
let mut _new_access = _access.clone();
// update the filters (Or<(With<$name>,)>)
let ($($name,)*) = state;
let mut _not_first = false;
$(
if _not_first {
// we use an intermediate access because we only want to update the filter_sets, not the access
let mut intermediate = _access.clone();
$name::update_component_access($name, &mut intermediate);
_new_access.append_or(&intermediate);
_new_access.extend_access(&intermediate);
} else {
$name::update_component_access($name, &mut _new_access);
_new_access.required = _access.required.clone();
@ -1879,11 +1880,16 @@ macro_rules! impl_anytuple_fetch {
}
)*
*_access = _new_access;
_access.filter_sets = _new_access.filter_sets;
// update the access (add the read/writes)
// Option<T> updates the access but not the filter_sets
<($(Option<$name>,)*)>::update_component_access(state, _access);
}
#[allow(unused_variables)]
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
($($name::init_state(initializer),)*)
fn init_state(world: &mut World) -> Self::State {
($($name::init_state(world),)*)
}
#[allow(unused_variables)]
fn get_state(components: &Components) -> Option<Self::State> {
@ -1959,8 +1965,8 @@ unsafe impl<D: QueryData> WorldQuery for NopWorldQuery<D> {
fn update_component_access(_state: &D::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
D::init_state(initializer)
fn init_state(world: &mut World) -> Self::State {
D::init_state(world)
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -2026,7 +2032,7 @@ unsafe impl<T: ?Sized> WorldQuery for PhantomData<T> {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) -> Self::State {}
fn init_state(_world: &mut World) -> Self::State {}
fn get_state(_components: &Components) -> Option<Self::State> {
Some(())

View File

@ -1,10 +1,10 @@
use crate::{
archetype::Archetype,
component::{Component, ComponentId, ComponentInitializer, Components, StorageType, Tick},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::Entity,
query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{Column, ComponentSparseSet, Table, TableRow},
world::unsafe_world_cell::UnsafeWorldCell,
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
@ -183,8 +183,8 @@ unsafe impl<T: Component> WorldQuery for With<T> {
access.and_with(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -291,8 +291,8 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
access.and_without(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -461,8 +461,8 @@ macro_rules! impl_or_query_filter {
*access = _new_access;
}
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
($($filter::init_state(initializer),)*)
fn init_state(world: &mut World) -> Self::State {
($($filter::init_state(world),)*)
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -693,8 +693,8 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
access.add_read(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<ComponentId> {
@ -904,8 +904,8 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
access.add_read(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<ComponentId> {

View File

@ -730,6 +730,77 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
}
}
/// Sorts all query items into a new iterator with a key extraction function over the query lens.
///
/// This sort is unstable (i.e., may reorder equal elements).
///
/// This uses [`slice::sort_unstable_by_key`] internally.
///
/// Defining the lens works like [`transmute_lens`](crate::system::Query::transmute_lens).
/// This includes the allowed parameter type changes listed under [allowed transmutes].
/// However, the lens uses the filter of the original query when present.
///
/// The sort is not cached across system runs.
///
/// [allowed transmutes]: crate::system::Query#allowed-transmutes
///
/// # Panics
///
/// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty.
pub fn sort_unstable_by_key<L: ReadOnlyQueryData + 'w, K>(
self,
mut f: impl FnMut(&L::Item<'w>) -> K,
) -> QuerySortedIter<
'w,
's,
D,
F,
impl ExactSizeIterator<Item = Entity> + DoubleEndedIterator + FusedIterator + 'w,
>
where
K: Ord,
{
// On the first successful iteration of `QueryIterationCursor`, `archetype_entities` or `table_entities`
// will be set to a non-zero value. The correctness of this method relies on this.
// I.e. this sort method will execute if and only if `next` on `QueryIterationCursor` of a
// non-empty `QueryIter` has not yet been called. When empty, this sort method will not panic.
if !self.cursor.archetype_entities.is_empty() || !self.cursor.table_entities.is_empty() {
panic!("it is not valid to call sort() after next()")
}
let world = self.world;
let query_lens_state = self
.query_state
.transmute_filtered::<(L, Entity), F>(world.components());
// SAFETY:
// `self.world` has permission to access the required components.
// The original query iter has not been iterated on, so no items are aliased from it.
let query_lens = unsafe {
query_lens_state.iter_unchecked_manual(
world,
world.last_change_tick(),
world.change_tick(),
)
};
let mut keyed_query: Vec<_> = query_lens.collect();
keyed_query.sort_unstable_by_key(|(lens, _)| f(lens));
let entity_iter = keyed_query.into_iter().map(|(.., entity)| entity);
// SAFETY:
// `self.world` has permission to access the required components.
// Each lens query item is dropped before the respective actual query item is accessed.
unsafe {
QuerySortedIter::new(
world,
self.query_state,
entity_iter,
world.last_change_tick(),
world.change_tick(),
)
}
}
/// Sort all query items into a new iterator with a key extraction function over the query lens.
///
/// This sort is stable (i.e., does not reorder equal elements).
@ -874,7 +945,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Debug for QueryIter<'w, 's, D, F> {
///
/// This struct is created by the [`QueryIter::sort`], [`QueryIter::sort_unstable`],
/// [`QueryIter::sort_by`], [`QueryIter::sort_unstable_by`], [`QueryIter::sort_by_key`],
/// and [`QueryIter::sort_by_cached_key`] methods.
/// [`QueryIter::sort_unstable_by_key`], and [`QueryIter::sort_by_cached_key`] methods.
pub struct QuerySortedIter<'w, 's, D: QueryData, F: QueryFilter, I>
where
I: Iterator<Item = Entity>,
@ -1681,6 +1752,11 @@ mod tests {
.sort_by_key::<Entity, _>(|&e| e)
.collect::<Vec<_>>();
let sort_unstable_by_key = query
.iter(&world)
.sort_unstable_by_key::<Entity, _>(|&e| e)
.collect::<Vec<_>>();
let sort_by_cached_key = query
.iter(&world)
.sort_by_cached_key::<Entity, _>(|&e| e)
@ -1701,6 +1777,9 @@ mod tests {
let mut sort_by_key_v2 = query.iter(&world).collect::<Vec<_>>();
sort_by_key_v2.sort_by_key(|&e| e);
let mut sort_unstable_by_key_v2 = query.iter(&world).collect::<Vec<_>>();
sort_unstable_by_key_v2.sort_unstable_by_key(|&e| e);
let mut sort_by_cached_key_v2 = query.iter(&world).collect::<Vec<_>>();
sort_by_cached_key_v2.sort_by_cached_key(|&e| e);
@ -1709,6 +1788,7 @@ mod tests {
assert_eq!(sort_by, sort_by_v2);
assert_eq!(sort_unstable_by, sort_unstable_by_v2);
assert_eq!(sort_by_key, sort_by_key_v2);
assert_eq!(sort_unstable_by_key, sort_unstable_by_key_v2);
assert_eq!(sort_by_cached_key, sort_by_cached_key_v2);
}

View File

@ -178,9 +178,8 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// `new_archetype` and its variants must be called on all of the World's archetypes before the
/// state can return valid query results.
fn new_uninitialized(world: &mut World) -> Self {
let initializer = &mut world.component_initializer();
let fetch_state = D::init_state(initializer);
let filter_state = F::init_state(initializer);
let fetch_state = D::init_state(world);
let filter_state = F::init_state(world);
let mut component_access = FilteredAccess::default();
D::update_component_access(&fetch_state, &mut component_access);
@ -215,9 +214,8 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// Creates a new [`QueryState`] from a given [`QueryBuilder`] and inherits its [`FilteredAccess`].
pub fn from_builder(builder: &mut QueryBuilder<D, F>) -> Self {
let initializer = &mut builder.world_mut().component_initializer();
let mut fetch_state = D::init_state(initializer);
let filter_state = F::init_state(initializer);
let mut fetch_state = D::init_state(builder.world_mut());
let filter_state = F::init_state(builder.world_mut());
D::set_access(&mut fetch_state, builder.access());
let mut state = Self {

View File

@ -1,10 +1,10 @@
use crate::{
archetype::Archetype,
component::{ComponentId, ComponentInitializer, Components, Tick},
component::{ComponentId, Components, Tick},
entity::Entity,
query::FilteredAccess,
storage::{Table, TableRow},
world::unsafe_world_cell::UnsafeWorldCell,
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_utils::all_tuples;
@ -79,7 +79,7 @@ pub unsafe trait WorldQuery {
///
/// # Safety
///
/// - `archetype` and `tables` must be from the same [`World`](crate::world::World) that [`WorldQuery::init_state`] was called on.
/// - `archetype` and `tables` must be from the same [`World`] that [`WorldQuery::init_state`] was called on.
/// - `table` must correspond to `archetype`.
/// - `state` must be the [`State`](Self::State) that `fetch` was initialized with.
unsafe fn set_archetype<'w>(
@ -94,7 +94,7 @@ pub unsafe trait WorldQuery {
///
/// # Safety
///
/// - `table` must be from the same [`World`](crate::world::World) that [`WorldQuery::init_state`] was called on.
/// - `table` must be from the same [`World`] that [`WorldQuery::init_state`] was called on.
/// - `state` must be the [`State`](Self::State) that `fetch` was initialized with.
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table);
@ -127,7 +127,7 @@ pub unsafe trait WorldQuery {
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>);
/// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type.
fn init_state(initializer: &mut ComponentInitializer) -> Self::State;
fn init_state(world: &mut World) -> Self::State;
/// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type using read-only
/// access to [`Components`].
@ -213,8 +213,8 @@ macro_rules! impl_tuple_world_query {
$($name::update_component_access($name, _access);)*
}
#[allow(unused_variables)]
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
($($name::init_state(initializer),)*)
fn init_state(world: &mut World) -> Self::State {
($($name::init_state(world),)*)
}
#[allow(unused_variables)]
fn get_state(components: &Components) -> Option<Self::State> {

View File

@ -116,11 +116,11 @@ impl RemovedComponentEvents {
///
/// If you are using `bevy_ecs` as a standalone crate,
/// note that the `RemovedComponents` list will not be automatically cleared for you,
/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers)
/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers).
///
/// For users of `bevy` and `bevy_app`, this is automatically done in `bevy_app::App::update`.
/// For the main world, [`World::clear_trackers`](World::clear_trackers) is run after the main schedule is run and after
/// `SubApp`'s have run.
/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is
/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`.
/// For the main world, this is delayed until after all `SubApp`s have run.
///
/// # Examples
///

View File

@ -127,6 +127,11 @@ where
self.system.apply_deferred(world);
}
#[inline]
fn queue_deferred(&mut self, world: crate::world::DeferredWorld) {
self.system.queue_deferred(world);
}
fn initialize(&mut self, world: &mut crate::prelude::World) {
self.system.initialize(world);
}

View File

@ -202,6 +202,12 @@ where
self.b.apply_deferred(world);
}
#[inline]
fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
self.a.queue_deferred(world.reborrow());
self.b.queue_deferred(world);
}
fn initialize(&mut self, world: &mut World) {
self.a.initialize(world);
self.b.initialize(world);

View File

@ -1,11 +1,13 @@
mod parallel_scope;
use super::{Deferred, IntoSystem, RegisterSystem, Resource};
use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
use crate::{
self as bevy_ecs,
bundle::Bundle,
component::ComponentId,
entity::{Entities, Entity},
event::Event,
observer::{Observer, TriggerEvent, TriggerTargets},
system::{RunSystemWithInput, SystemId},
world::command_queue::RawCommandQueue,
world::{Command, CommandQueue, EntityWorldMut, FromWorld, World},
@ -116,6 +118,17 @@ const _: () = {
world,
);
}
fn queue(
state: &mut Self::State,
system_meta: &bevy_ecs::system::SystemMeta,
world: bevy_ecs::world::DeferredWorld,
) {
<__StructFieldsAlias<'_, '_> as bevy_ecs::system::SystemParam>::queue(
&mut state.state,
system_meta,
world,
);
}
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &bevy_ecs::system::SystemMeta,
@ -150,7 +163,7 @@ impl<'w, 's> Commands<'w, 's> {
///
/// [system parameter]: crate::system::SystemParam
pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self {
Self::new_from_entities(queue, world.entities())
Self::new_from_entities(queue, &world.entities)
}
/// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference.
@ -735,6 +748,26 @@ impl<'w, 's> Commands<'w, 's> {
pub fn add<C: Command>(&mut self, command: C) {
self.push(command);
}
/// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that
/// isn't scoped to specific targets.
pub fn trigger(&mut self, event: impl Event) {
self.add(TriggerEvent { event, targets: () });
}
/// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that
/// watches those targets.
pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) {
self.add(TriggerEvent { event, targets });
}
/// Spawn an [`Observer`] and returns the [`EntityCommands`] associated with the entity that stores the observer.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> EntityCommands {
self.spawn(Observer::new(observer))
}
}
/// A [`Command`] which gets executed for a given [`Entity`].
@ -794,6 +827,7 @@ pub trait EntityCommand<Marker = ()>: Send + 'static {
/// Executes this command for the given [`Entity`].
fn apply(self, id: Entity, world: &mut World);
/// Returns a [`Command`] which executes this [`EntityCommand`] for the given [`Entity`].
#[must_use = "commands do nothing unless applied to a `World`"]
fn with_entity(self, id: Entity) -> WithEntity<Marker, Self>
where
Self: Sized,
@ -1017,6 +1051,7 @@ impl EntityCommands<'_> {
}
/// Despawns the entity.
/// This will emit a warning if the entity does not exist.
///
/// See [`World::despawn`] for more details.
///
@ -1025,10 +1060,6 @@ impl EntityCommands<'_> {
/// This won't clean up external references to the entity (such as parent-child relationships
/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state.
///
/// # Panics
///
/// The command will panic when applied if the associated entity does not exist.
///
/// # Example
///
/// ```
@ -1128,6 +1159,15 @@ impl EntityCommands<'_> {
pub fn commands(&mut self) -> Commands {
self.commands.reborrow()
}
/// Creates an [`Observer`](crate::observer::Observer) listening for a trigger of type `T` that targets this entity.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.add(observe(system));
self
}
}
impl<F> Command for F
@ -1288,6 +1328,16 @@ fn log_components(entity: Entity, world: &mut World) {
info!("Entity {:?}: {:?}", entity, debug_infos);
}
fn observe<E: Event, B: Bundle, M>(
observer: impl IntoObserverSystem<E, B, M>,
) -> impl EntityCommand {
move |entity, world: &mut World| {
if let Some(mut entity) = world.get_entity_mut(entity) {
entity.observe(observer);
}
}
}
#[cfg(test)]
#[allow(clippy::float_cmp, clippy::approx_constant)]
mod tests {

View File

@ -110,7 +110,7 @@ where
);
let out = self.func.run(world, input, params);
world.flush_commands();
world.flush();
let change_tick = world.change_tick.get_mut();
self.system_meta.last_run.set(*change_tick);
*change_tick = change_tick.wrapping_add(1);
@ -126,6 +126,13 @@ where
// might have buffers to apply, but this is handled by `PipeSystem`.
}
#[inline]
fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) {
// "pure" exclusive systems do not have any buffers to apply.
// Systems made by piping a normal system with an exclusive system
// might have buffers to apply, but this is handled by `PipeSystem`.
}
#[inline]
fn initialize(&mut self, world: &mut World) {
self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);

View File

@ -5,7 +5,7 @@ use crate::{
query::{Access, FilteredAccessSet},
schedule::{InternedSystemSet, SystemSet},
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId},
};
use bevy_utils::all_tuples;
@ -399,8 +399,8 @@ where
F: SystemParamFunction<Marker>,
{
func: F,
param_state: Option<<F::Param as SystemParam>::State>,
system_meta: SystemMeta,
pub(crate) param_state: Option<<F::Param as SystemParam>::State>,
pub(crate) system_meta: SystemMeta,
world_id: Option<WorldId>,
archetype_generation: ArchetypeGeneration,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
@ -542,6 +542,12 @@ where
F::Param::apply(param_state, &self.system_meta, world);
}
#[inline]
fn queue_deferred(&mut self, world: DeferredWorld) {
let param_state = self.param_state.as_mut().expect(Self::PARAM_MESSAGE);
F::Param::queue(param_state, &self.system_meta, world);
}
#[inline]
fn initialize(&mut self, world: &mut World) {
if let Some(id) = self.world_id {

View File

@ -108,6 +108,7 @@ mod commands;
mod exclusive_function_system;
mod exclusive_system_param;
mod function_system;
mod observer_system;
mod query;
#[allow(clippy::module_inception)]
mod system;
@ -124,6 +125,7 @@ pub use commands::*;
pub use exclusive_function_system::*;
pub use exclusive_system_param::*;
pub use function_system::*;
pub use observer_system::*;
pub use query::*;
pub use system::*;
pub use system_name::*;
@ -554,6 +556,58 @@ mod tests {
run_system(&mut world, sys);
}
#[test]
fn any_of_working() {
fn sys(_: Query<AnyOf<(&mut A, &B)>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
fn any_of_with_and_without_common() {
fn sys(_: Query<(&mut D, &C, AnyOf<(&A, &B)>)>, _: Query<&mut D, Without<C>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
#[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."]
fn any_of_with_mut_and_ref() {
fn sys(_: Query<AnyOf<(&mut A, &A)>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
#[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."]
fn any_of_with_ref_and_mut() {
fn sys(_: Query<AnyOf<(&A, &mut A)>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
#[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."]
fn any_of_with_mut_and_option() {
fn sys(_: Query<AnyOf<(&mut A, Option<&A>)>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
fn any_of_with_entity_and_mut() {
fn sys(_: Query<AnyOf<(Entity, &mut A)>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
fn any_of_with_empty_and_mut() {
fn sys(_: Query<AnyOf<((), &mut A)>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
#[should_panic = "error[B0001]"]
fn any_of_has_no_filter_with() {
@ -562,6 +616,14 @@ mod tests {
run_system(&mut world, sys);
}
#[test]
#[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."]
fn any_of_with_conflicting() {
fn sys(_: Query<AnyOf<(&mut A, &mut A)>>) {}
let mut world = World::default();
run_system(&mut world, sys);
}
#[test]
fn any_of_has_filter_with_when_both_have_it() {
fn sys(_: Query<(AnyOf<(&A, &A)>, &mut B)>, _: Query<&mut B, Without<A>>) {}

View File

@ -0,0 +1,71 @@
use bevy_utils::all_tuples;
use crate::{
prelude::{Bundle, Trigger},
system::{System, SystemParam, SystemParamFunction, SystemParamItem},
};
use super::IntoSystem;
/// Implemented for systems that have an [`Observer`] as the first argument.
pub trait ObserverSystem<E: 'static, B: Bundle>:
System<In = Trigger<'static, E, B>, Out = ()> + Send + 'static
{
}
impl<E: 'static, B: Bundle, T: System<In = Trigger<'static, E, B>, Out = ()> + Send + 'static>
ObserverSystem<E, B> for T
{
}
/// Implemented for systems that convert into [`ObserverSystem`].
pub trait IntoObserverSystem<E: 'static, B: Bundle, M>: Send + 'static {
/// The type of [`System`] that this instance converts into.
type System: ObserverSystem<E, B>;
/// Turns this value into its corresponding [`System`].
fn into_system(this: Self) -> Self::System;
}
impl<S: IntoSystem<Trigger<'static, E, B>, (), M> + Send + 'static, M, E: 'static, B: Bundle>
IntoObserverSystem<E, B, M> for S
where
S::System: ObserverSystem<E, B>,
{
type System = <S as IntoSystem<Trigger<'static, E, B>, (), M>>::System;
fn into_system(this: Self) -> Self::System {
IntoSystem::into_system(this)
}
}
macro_rules! impl_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<E: 'static, B: Bundle, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<fn(Trigger<E, B>, $($param,)*)> for Func
where
for <'a> &'a mut Func:
FnMut(Trigger<E, B>, $($param),*) +
FnMut(Trigger<E, B>, $(SystemParamItem<$param>),*)
{
type In = Trigger<'static, E, B>;
type Out = ();
type Param = ($($param,)*);
#[inline]
fn run(&mut self, input: Trigger<'static, E, B>, param_value: SystemParamItem< ($($param,)*)>) {
#[allow(clippy::too_many_arguments)]
fn call_inner<E: 'static, B: Bundle, $($param,)*>(
mut f: impl FnMut(Trigger<'static, E, B>, $($param,)*),
input: Trigger<'static, E, B>,
$($param: $param,)*
){
f(input, $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, input, $($param),*)
}
}
}
}
all_tuples!(impl_system_function, 0, 16, F);

View File

@ -1385,9 +1385,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Returns a [`QueryLens`] that can be used to get a query with the combined fetch.
///
/// For example, this can take a `Query<&A>` and a `Queryy<&B>` and return a `Query<&A, &B>`.
/// The returned query will only return items with both `A` and `B`. Note that since filter
/// are dropped, non-archetypal filters like `Added` and `Changed` will no be respected.
/// For example, this can take a `Query<&A>` and a `Query<&B>` and return a `Query<(&A, &B)>`.
/// The returned query will only return items with both `A` and `B`. Note that since filters
/// are dropped, non-archetypal filters like `Added` and `Changed` will not be respected.
/// To maintain or change filter terms see `Self::join_filtered`.
///
/// ## Example
@ -1446,7 +1446,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Equivalent to [`Self::join`] but also includes a [`QueryFilter`] type.
///
/// Note that the lens with iterate a subset of the original queries tables
/// Note that the lens with iterate a subset of the original queries' tables
/// and archetypes. This means that additional archetypal query terms like
/// `With` and `Without` will not necessarily be respected and non-archetypal
/// terms like `Added` and `Changed` will only be respected if they are in

View File

@ -4,6 +4,7 @@ use core::fmt::Debug;
use crate::component::Tick;
use crate::schedule::InternedSystemSet;
use crate::world::unsafe_world_cell::UnsafeWorldCell;
use crate::world::DeferredWorld;
use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World};
use std::any::TypeId;
@ -89,6 +90,10 @@ pub trait System: Send + Sync + 'static {
/// This is where [`Commands`](crate::system::Commands) get applied.
fn apply_deferred(&mut self, world: &mut World);
/// Enqueues any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers)
/// of this system into the world's command buffer.
fn queue_deferred(&mut self, world: DeferredWorld);
/// Initialize the system.
fn initialize(&mut self, _world: &mut World);

View File

@ -11,7 +11,7 @@ use crate::{
ReadOnlyQueryData,
},
system::{Query, SystemMeta},
world::{unsafe_world_cell::UnsafeWorldCell, FromWorld, World},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
};
use bevy_ecs_macros::impl_param_set;
pub use bevy_ecs_macros::Resource;
@ -159,6 +159,11 @@ pub unsafe trait SystemParam: Sized {
#[allow(unused_variables)]
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {}
/// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred).
#[inline]
#[allow(unused_variables)]
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {}
/// Creates a parameter to be passed into a [`SystemParamFunction`].
///
/// [`SystemParamFunction`]: super::SystemParamFunction
@ -712,6 +717,27 @@ unsafe impl SystemParam for &'_ World {
}
}
/// SAFETY: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references.
unsafe impl<'w> SystemParam for DeferredWorld<'w> {
type State = ();
type Item<'world, 'state> = DeferredWorld<'world>;
fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
system_meta.component_access_set.read_all();
system_meta.component_access_set.write_all();
system_meta.set_has_deferred();
}
unsafe fn get_param<'world, 'state>(
_state: &'state mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell<'world>,
_change_tick: Tick,
) -> Self::Item<'world, 'state> {
world.into_deferred()
}
}
/// A system local [`SystemParam`].
///
/// A local may only be accessed by the system itself and is therefore not visible to other systems.
@ -848,6 +874,8 @@ impl<'w, T: FromWorld + Send + 'static> BuildableSystemParam for Local<'w, T> {
pub trait SystemBuffer: FromWorld + Send + 'static {
/// Applies any deferred mutations to the [`World`].
fn apply(&mut self, system_meta: &SystemMeta, world: &mut World);
/// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred).
fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {}
}
/// A [`SystemParam`] that stores a buffer which gets applied to the [`World`] during
@ -1012,6 +1040,10 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
state.get().apply(system_meta, world);
}
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
state.get().queue(system_meta, world);
}
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
_system_meta: &SystemMeta,
@ -1424,6 +1456,11 @@ macro_rules! impl_system_param_tuple {
$($param::apply($param, _system_meta, _world);)*
}
#[inline]
fn queue(($($param,)*): &mut Self::State, _system_meta: &SystemMeta, mut _world: DeferredWorld) {
$($param::queue($param, _system_meta, _world.reborrow());)*
}
#[inline]
#[allow(clippy::unused_unit)]
unsafe fn get_param<'w, 's>(
@ -1572,6 +1609,10 @@ unsafe impl<P: SystemParam + 'static> SystemParam for StaticSystemParam<'_, '_,
P::apply(state, system_meta, world);
}
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
P::queue(state, system_meta, world);
}
unsafe fn get_param<'world, 'state>(
state: &'state mut Self::State,
system_meta: &SystemMeta,

View File

@ -3,6 +3,7 @@ use crate::system::{SystemBuffer, SystemMeta};
use std::{
fmt::Debug,
mem::MaybeUninit,
panic::{self, AssertUnwindSafe},
ptr::{addr_of_mut, NonNull},
};
@ -11,6 +12,8 @@ use bevy_utils::tracing::warn;
use crate::world::{Command, World};
use super::DeferredWorld;
struct CommandMeta {
/// SAFETY: The `value` must point to a value of type `T: Command`,
/// where `T` is some specific type that was used to produce this metadata.
@ -18,11 +21,8 @@ struct CommandMeta {
/// `world` is optional to allow this one function pointer to perform double-duty as a drop.
///
/// Advances `cursor` by the size of `T` in bytes.
consume_command_and_get_size: unsafe fn(
value: OwningPtr<Unaligned>,
world: Option<NonNull<World>>,
cursor: NonNull<usize>,
),
consume_command_and_get_size:
unsafe fn(value: OwningPtr<Unaligned>, world: Option<NonNull<World>>, cursor: &mut usize),
}
/// Densely and efficiently stores a queue of heterogenous types implementing [`Command`].
@ -41,6 +41,7 @@ pub struct CommandQueue {
// be passed to the corresponding `CommandMeta.apply_command_and_get_size` fn pointer.
pub(crate) bytes: Vec<MaybeUninit<u8>>,
pub(crate) cursor: usize,
pub(crate) panic_recovery: Vec<MaybeUninit<u8>>,
}
/// Wraps pointers to a [`CommandQueue`], used internally to avoid stacked borrow rules when
@ -49,6 +50,7 @@ pub struct CommandQueue {
pub(crate) struct RawCommandQueue {
pub(crate) bytes: NonNull<Vec<MaybeUninit<u8>>>,
pub(crate) cursor: NonNull<usize>,
pub(crate) panic_recovery: NonNull<Vec<MaybeUninit<u8>>>,
}
// CommandQueue needs to implement Debug manually, rather than deriving it, because the derived impl just prints
@ -117,6 +119,7 @@ impl CommandQueue {
RawCommandQueue {
bytes: NonNull::new_unchecked(addr_of_mut!(self.bytes)),
cursor: NonNull::new_unchecked(addr_of_mut!(self.cursor)),
panic_recovery: NonNull::new_unchecked(addr_of_mut!(self.panic_recovery)),
}
}
}
@ -130,6 +133,7 @@ impl RawCommandQueue {
Self {
bytes: NonNull::new_unchecked(Box::into_raw(Box::default())),
cursor: NonNull::new_unchecked(Box::into_raw(Box::new(0usize))),
panic_recovery: NonNull::new_unchecked(Box::into_raw(Box::default())),
}
}
}
@ -164,17 +168,23 @@ impl RawCommandQueue {
}
let meta = CommandMeta {
consume_command_and_get_size: |command, world, mut cursor| {
// SAFETY: Pointer is assured to be valid in `CommandQueue.apply_or_drop_queued`
unsafe { *cursor.as_mut() += std::mem::size_of::<C>() }
consume_command_and_get_size: |command, world, cursor| {
*cursor += std::mem::size_of::<C>();
// SAFETY: According to the invariants of `CommandMeta.consume_command_and_get_size`,
// `command` must point to a value of type `C`.
let command: C = unsafe { command.read_unaligned() };
match world {
// Apply command to the provided world...
// SAFETY: Calller ensures pointer is not null
Some(mut world) => command.apply(unsafe { world.as_mut() }),
Some(mut world) => {
// SAFETY: Caller ensures pointer is not null
let world = unsafe { world.as_mut() };
command.apply(world);
// The command may have queued up world commands, which we flush here to ensure they are also picked up.
// If the current command queue already the World Command queue, this will still behave appropriately because the global cursor
// is still at the current `stop`, ensuring only the newly queued Commands will be applied.
world.flush();
}
// ...or discard it.
None => drop(command),
}
@ -222,50 +232,79 @@ impl RawCommandQueue {
pub(crate) unsafe fn apply_or_drop_queued(&mut self, world: Option<NonNull<World>>) {
// SAFETY: If this is the command queue on world, world will not be dropped as we have a mutable reference
// If this is not the command queue on world we have exclusive ownership and self will not be mutated
while *self.cursor.as_ref() < self.bytes.as_ref().len() {
let start = *self.cursor.as_ref();
let stop = self.bytes.as_ref().len();
let mut local_cursor = start;
// SAFETY: we are setting the global cursor to the current length to prevent the executing commands from applying
// the remaining commands currently in this list. This is safe.
*self.cursor.as_mut() = stop;
while local_cursor < stop {
// SAFETY: The cursor is either at the start of the buffer, or just after the previous command.
// Since we know that the cursor is in bounds, it must point to the start of a new command.
let meta = unsafe {
self.bytes
.as_mut()
.as_mut_ptr()
.add(*self.cursor.as_ref())
.add(local_cursor)
.cast::<CommandMeta>()
.read_unaligned()
};
// Advance to the bytes just after `meta`, which represent a type-erased command.
// SAFETY: For most types of `Command`, the pointer immediately following the metadata
// is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor
// might be 1 byte past the end of the buffer, which is safe.
unsafe { *self.cursor.as_mut() += std::mem::size_of::<CommandMeta>() };
local_cursor += std::mem::size_of::<CommandMeta>();
// Construct an owned pointer to the command.
// SAFETY: It is safe to transfer ownership out of `self.bytes`, since the increment of `cursor` above
// guarantees that nothing stored in the buffer will get observed after this function ends.
// `cmd` points to a valid address of a stored command, so it must be non-null.
let cmd = unsafe {
OwningPtr::<Unaligned>::new(std::ptr::NonNull::new_unchecked(
self.bytes
.as_mut()
.as_mut_ptr()
.add(*self.cursor.as_ref())
.cast(),
self.bytes.as_mut().as_mut_ptr().add(local_cursor).cast(),
))
};
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
// since they were stored next to each other by `.push()`.
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
// This also advances the cursor past the command. For ZSTs, the cursor will not move.
// At this point, it will either point to the next `CommandMeta`,
// or the cursor will be out of bounds and the loop will end.
unsafe { (meta.consume_command_and_get_size)(cmd, world, self.cursor) };
let result = panic::catch_unwind(AssertUnwindSafe(|| {
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
// since they were stored next to each other by `.push()`.
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
// This also advances the cursor past the command. For ZSTs, the cursor will not move.
// At this point, it will either point to the next `CommandMeta`,
// or the cursor will be out of bounds and the loop will end.
unsafe { (meta.consume_command_and_get_size)(cmd, world, &mut local_cursor) };
}));
if let Err(payload) = result {
// local_cursor now points to the location _after_ the panicked command.
// Add the remaining commands that _would have_ been applied to the
// panic_recovery queue.
//
// This uses `current_stop` instead of `stop` to account for any commands
// that were queued _during_ this panic.
//
// This is implemented in such a way that if apply_or_drop_queued() are nested recursively in,
// an applied Command, the correct command order will be retained.
let panic_recovery = self.panic_recovery.as_mut();
let bytes = self.bytes.as_mut();
let current_stop = bytes.len();
panic_recovery.extend_from_slice(&bytes[local_cursor..current_stop]);
bytes.set_len(start);
*self.cursor.as_mut() = start;
// This was the "top of the apply stack". If we are _not_ at the top of the apply stack,
// when we call`resume_unwind" the caller "closer to the top" will catch the unwind and do this check,
// until we reach the top.
if start == 0 {
bytes.append(panic_recovery);
}
panic::resume_unwind(payload);
}
}
// Reset the buffer, so it can be reused after this function ends.
// SAFETY: `set_len(0)` is always valid.
// Reset the buffer: all commands past the original `start` cursor have been applied.
// SAFETY: we are setting the length of bytes to the original length, minus the length of the original
// list of commands being considered. All bytes remaining in the Vec are still valid, unapplied commands.
unsafe {
self.bytes.as_mut().set_len(0);
*self.cursor.as_mut() = 0;
self.bytes.as_mut().set_len(start);
*self.cursor.as_mut() = start;
};
}
}
@ -287,11 +326,18 @@ impl SystemBuffer for CommandQueue {
let _span_guard = _system_meta.commands_span.enter();
self.apply(world);
}
#[inline]
fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) {
world.commands().append(self);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate as bevy_ecs;
use crate::system::Resource;
use std::{
panic::AssertUnwindSafe,
sync::{
@ -412,10 +458,6 @@ mod test {
queue.apply(&mut world);
}));
// even though the first command panicking.
// the cursor was incremented.
assert!(queue.cursor > 0);
// Even though the first command panicked, it's still ok to push
// more commands.
queue.push(SpawnCommand);
@ -424,6 +466,37 @@ mod test {
assert_eq!(world.entities().len(), 3);
}
#[test]
fn test_command_queue_inner_nested_panic_safe() {
std::panic::set_hook(Box::new(|_| {}));
#[derive(Resource, Default)]
struct Order(Vec<usize>);
let mut world = World::new();
world.init_resource::<Order>();
fn add_index(index: usize) -> impl Command {
move |world: &mut World| world.resource_mut::<Order>().0.push(index)
}
world.commands().add(add_index(1));
world.commands().add(|world: &mut World| {
world.commands().add(add_index(2));
world.commands().add(PanicCommand("I panic!".to_owned()));
world.commands().add(add_index(3));
world.flush_commands();
});
world.commands().add(add_index(4));
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
world.flush_commands();
}));
world.commands().add(add_index(5));
world.flush_commands();
assert_eq!(&world.resource::<Order>().0, &[1, 2, 3, 4, 5]);
}
// NOTE: `CommandQueue` is `Send` because `Command` is send.
// If the `Command` trait gets reworked to be non-send, `CommandQueue`
// should be reworked.

View File

@ -0,0 +1,23 @@
use super::*;
use crate::{self as bevy_ecs};
/// Internal components used by bevy with a fixed component id.
/// Constants are used to skip [`TypeId`] lookups in hot paths.
/// [`ComponentId`] for [`OnAdd`]
pub const ON_ADD: ComponentId = ComponentId::new(0);
/// [`ComponentId`] for [`OnInsert`]
pub const ON_INSERT: ComponentId = ComponentId::new(1);
/// [`ComponentId`] for [`OnRemove`]
pub const ON_REMOVE: ComponentId = ComponentId::new(2);
/// Trigger emitted when a component is added to an entity.
#[derive(Event)]
pub struct OnAdd;
/// Trigger emitted when a component is inserted on to to an entity.
#[derive(Event)]
pub struct OnInsert;
/// Trigger emitted when a component is removed from an entity.
#[derive(Event)]
pub struct OnRemove;

View File

@ -1,10 +1,12 @@
use std::ops::Deref;
use crate::{
archetype::Archetype,
change_detection::MutUntyped,
component::ComponentId,
entity::Entity,
event::{Event, EventId, Events, SendBatchIds},
observer::{Observers, TriggerTargets},
prelude::{Component, QueryState},
query::{QueryData, QueryFilter},
system::{Commands, Query, Resource},
@ -52,6 +54,12 @@ impl<'w> From<&'w mut World> for DeferredWorld<'w> {
}
impl<'w> DeferredWorld<'w> {
/// Reborrow self as a new instance of [`DeferredWorld`]
#[inline]
pub fn reborrow(&mut self) -> DeferredWorld {
DeferredWorld { world: self.world }
}
/// Creates a [`Commands`] instance that pushes to the world's command queue
#[inline]
pub fn commands(&mut self) -> Commands {
@ -65,37 +73,42 @@ impl<'w> DeferredWorld<'w> {
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
#[inline]
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the component
// SAFETY:
// - `as_unsafe_world_cell` is the only thing that is borrowing world
// - `as_unsafe_world_cell` provides mutable permission to everything
// - `&mut self` ensures no other borrows on world data
unsafe { self.world.get_entity(entity)?.get_mut() }
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
#[inline]
#[track_caller]
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
#[inline(never)]
#[cold]
#[track_caller]
fn panic_no_entity(entity: Entity) -> ! {
panic!("Entity {entity:?} does not exist");
}
match self.get_entity_mut(entity) {
Some(entity) => entity,
None => panic_no_entity(entity),
}
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// Returns [`None`] if the `entity` does not exist.
/// Instead of unwrapping the value returned from this function, prefer [`Self::entity_mut`].
#[inline]
pub fn get_entity_mut(&mut self, entity: Entity) -> Option<EntityMut> {
let location = self.entities.get(entity)?;
// SAFETY: `entity` exists and `location` is that entity's location
Some(unsafe { EntityMut::new(UnsafeEntityCell::new(self.world, entity, location)) })
// SAFETY: if the Entity is invalid, the function returns early.
// Additionally, Entities::get(entity) returns the correct EntityLocation if the entity exists.
let entity_cell = UnsafeEntityCell::new(self.as_unsafe_world_cell(), entity, location);
// SAFETY: The UnsafeEntityCell has read access to the entire world.
let entity_ref = unsafe { EntityMut::new(entity_cell) };
Some(entity_ref)
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
#[inline]
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
#[inline(never)]
#[cold]
fn panic_no_entity(entity: Entity) -> ! {
panic!("Entity {entity:?} does not exist");
}
match self.get_entity_mut(entity) {
Some(entity) => entity,
None => panic_no_entity(entity),
}
}
/// Returns [`Query`] for the given [`QueryState`], which is used to efficiently
@ -266,14 +279,17 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub(crate) unsafe fn trigger_on_add(
&mut self,
archetype: &Archetype,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_add {
hook(DeferredWorld { world: self.world }, entity, component_id);
if archetype.has_add_hook() {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_add {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
}
@ -285,14 +301,17 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub(crate) unsafe fn trigger_on_insert(
&mut self,
archetype: &Archetype,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_insert {
hook(DeferredWorld { world: self.world }, entity, component_id);
if archetype.has_insert_hook() {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_insert {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
}
@ -304,15 +323,67 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub(crate) unsafe fn trigger_on_remove(
&mut self,
archetype: &Archetype,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_remove {
hook(DeferredWorld { world: self.world }, entity, component_id);
if archetype.has_remove_hook() {
for component_id in targets {
let hooks =
// SAFETY: Caller ensures that these components exist
unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_remove {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
}
/// Triggers all event observers for [`ComponentId`] in target.
///
/// # Safety
/// Caller must ensure observers listening for `event` can accept ZST pointers
#[inline]
pub(crate) unsafe fn trigger_observers(
&mut self,
event: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
) {
Observers::invoke(self.reborrow(), event, entity, components, &mut ());
}
/// Triggers all event observers for [`ComponentId`] in target.
///
/// # Safety
/// Caller must ensure `E` is accessible as the type represented by `event`
#[inline]
pub(crate) unsafe fn trigger_observers_with_data<E>(
&mut self,
event: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
data: &mut E,
) {
Observers::invoke(self.reborrow(), event, entity, components, data);
}
/// Sends a "global" [`Trigger`] without any targets.
pub fn trigger<T: Event>(&mut self, trigger: impl Event) {
self.commands().trigger(trigger);
}
/// Sends a [`Trigger`] with the given `targets`.
pub fn trigger_targets(&mut self, trigger: impl Event, targets: impl TriggerTargets) {
self.commands().trigger_targets(trigger, targets);
}
/// Gets an [`UnsafeWorldCell`] containing the underlying world.
///
/// # Safety
/// - must only be used to to make non-structural ECS changes
#[inline]
pub(crate) fn as_unsafe_world_cell(&mut self) -> UnsafeWorldCell {
self.world
}
}

View File

@ -4,16 +4,19 @@ use crate::{
change_detection::MutUntyped,
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
entity::{Entities, Entity, EntityLocation},
event::Event,
observer::{Observer, Observers},
query::Access,
removal_detection::RemovedComponentEvents,
storage::Storages,
world::{Mut, World},
system::IntoObserverSystem,
world::{DeferredWorld, Mut, World},
};
use bevy_ptr::{OwningPtr, Ptr};
use std::{any::TypeId, marker::PhantomData};
use thiserror::Error;
use super::{unsafe_world_cell::UnsafeEntityCell, Ref};
use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE};
/// A read-only reference to a particular [`Entity`] and all of its components.
///
@ -876,6 +879,7 @@ impl<'w> EntityWorldMut<'w> {
&mut world.archetypes,
storages,
components,
&world.observers,
old_location.archetype_id,
bundle_info,
false,
@ -898,11 +902,14 @@ impl<'w> EntityWorldMut<'w> {
)
};
if old_archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(entity, bundle_info.iter_components());
}
// SAFETY: all bundle components exist in World
unsafe {
trigger_on_remove_hooks_and_observers(
&mut deferred_world,
old_archetype,
entity,
bundle_info,
);
}
let archetypes = &mut world.archetypes;
@ -1053,6 +1060,7 @@ impl<'w> EntityWorldMut<'w> {
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
location.archetype_id,
bundle_info,
// components from the bundle that are not present on the entity are ignored
@ -1075,11 +1083,14 @@ impl<'w> EntityWorldMut<'w> {
)
};
if old_archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(entity, bundle_info.iter_components());
}
// SAFETY: all bundle components exist in World
unsafe {
trigger_on_remove_hooks_and_observers(
&mut deferred_world,
old_archetype,
entity,
bundle_info,
);
}
let old_archetype = &world.archetypes[location.archetype_id];
@ -1209,10 +1220,11 @@ impl<'w> EntityWorldMut<'w> {
(&*archetype, world.into_deferred())
};
if archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(self.entity, archetype.components());
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(archetype, self.entity, archetype.components());
if archetype.has_remove_observer() {
deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components());
}
}
@ -1276,12 +1288,12 @@ impl<'w> EntityWorldMut<'w> {
world.archetypes[moved_location.archetype_id]
.set_entity_table_row(moved_location.archetype_row, table_row);
}
world.flush_commands();
world.flush();
}
/// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`]
/// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`]
pub fn flush(self) -> Entity {
self.world.flush_commands();
self.world.flush();
self.entity
}
@ -1397,6 +1409,30 @@ impl<'w> EntityWorldMut<'w> {
})
}
}
/// Creates an [`Observer`](crate::observer::Observer) listening for events of type `E` targeting this entity.
/// In order to trigger the callback the entity must also match the query when the event is fired.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.world
.spawn(Observer::new(observer).with_entity(self.entity));
self
}
}
/// SAFETY: all components in the archetype must exist in world
unsafe fn trigger_on_remove_hooks_and_observers(
deferred_world: &mut DeferredWorld,
archetype: &Archetype,
entity: Entity,
bundle_info: &BundleInfo,
) {
deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_components());
if archetype.has_remove_observer() {
deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_components());
}
}
/// A view into a single entity and component in a world, which may either be vacant or occupied.
@ -2292,6 +2328,7 @@ unsafe fn remove_bundle_from_archetype(
archetypes: &mut Archetypes,
storages: &mut Storages,
components: &Components,
observers: &Observers,
archetype_id: ArchetypeId,
bundle_info: &BundleInfo,
intersection: bool,
@ -2362,6 +2399,7 @@ unsafe fn remove_bundle_from_archetype(
let new_archetype_id = archetypes.get_id_or_insert(
components,
observers,
next_table_id,
next_table_components,
next_sparse_set_components,

View File

@ -1,20 +1,25 @@
//! Defines the [`World`] and APIs for accessing it directly.
pub(crate) mod command_queue;
mod component_constants;
mod deferred_world;
mod entity_ref;
pub mod error;
mod identifier;
mod spawn_batch;
pub mod unsafe_world_cell;
pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD};
pub use crate::world::command_queue::CommandQueue;
use crate::{component::ComponentInitializer, entity::EntityHashSet};
pub use crate::{
change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD},
world::command_queue::CommandQueue,
};
pub use component_constants::*;
pub use deferred_world::DeferredWorld;
pub use entity_ref::{
EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef,
OccupiedEntry, VacantEntry,
};
pub use identifier::WorldId;
pub use spawn_batch::*;
use crate::{
@ -25,8 +30,9 @@ use crate::{
Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks,
Components, Tick,
},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityHashSet, EntityLocation},
event::{Event, EventId, Events, SendBatchIds},
observer::Observers,
query::{DebugCheckedUnwrap, QueryData, QueryEntityError, QueryFilter, QueryState},
removal_detection::RemovedComponentEvents,
schedule::{Schedule, ScheduleLabel, Schedules},
@ -43,10 +49,7 @@ use std::{
mem::MaybeUninit,
sync::atomic::{AtomicU32, Ordering},
};
mod identifier;
use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
pub use identifier::WorldId;
use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
/// A [`World`] mutation.
///
@ -110,30 +113,36 @@ pub struct World {
pub(crate) archetypes: Archetypes,
pub(crate) storages: Storages,
pub(crate) bundles: Bundles,
pub(crate) observers: Observers,
pub(crate) removed_components: RemovedComponentEvents,
pub(crate) change_tick: AtomicU32,
pub(crate) last_change_tick: Tick,
pub(crate) last_check_tick: Tick,
pub(crate) last_trigger_id: u32,
pub(crate) command_queue: RawCommandQueue,
}
impl Default for World {
fn default() -> Self {
Self {
let mut world = Self {
id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"),
entities: Entities::new(),
components: Default::default(),
archetypes: Archetypes::new(),
storages: Default::default(),
bundles: Default::default(),
observers: Observers::default(),
removed_components: Default::default(),
// Default value is `1`, and `last_change_tick`s default to `0`, such that changes
// are detected on first system runs and for direct world queries.
change_tick: AtomicU32::new(1),
last_change_tick: Tick::new(0),
last_check_tick: Tick::new(0),
last_trigger_id: 0,
command_queue: RawCommandQueue::new(),
}
};
world.bootstrap();
world
}
}
@ -149,6 +158,14 @@ impl Drop for World {
}
impl World {
/// This performs initialization that _must_ happen for every [`World`] immediately upon creation (such as claiming specific component ids).
/// This _must_ be run as part of constructing a [`World`], before it is returned to the caller.
#[inline]
fn bootstrap(&mut self) {
assert_eq!(ON_ADD, self.init_component::<OnAdd>());
assert_eq!(ON_INSERT, self.init_component::<OnInsert>());
assert_eq!(ON_REMOVE, self.init_component::<OnRemove>());
}
/// Creates a new empty [`World`].
///
/// # Panics
@ -219,15 +236,6 @@ impl World {
&self.bundles
}
/// Creates a [`ComponentInitializer`] for this world.
#[inline]
pub fn component_initializer(&mut self) -> ComponentInitializer {
ComponentInitializer {
components: &mut self.components,
storages: &mut self.storages,
}
}
/// Retrieves this world's [`RemovedComponentEvents`] collection
#[inline]
pub fn removed_components(&self) -> &RemovedComponentEvents {
@ -235,7 +243,7 @@ impl World {
}
/// Creates a new [`Commands`] instance that writes to the world's command queue
/// Use [`World::flush_commands`] to apply all queued commands
/// Use [`World::flush`] to apply all queued commands
#[inline]
pub fn commands(&mut self) -> Commands {
// SAFETY: command_queue is stored on world and always valid while the world exists
@ -502,7 +510,7 @@ impl World {
/// scheme worked out to share an ID space (which doesn't happen by default).
#[inline]
pub fn get_or_spawn(&mut self, entity: Entity) -> Option<EntityWorldMut> {
self.flush_entities();
self.flush();
match self.entities.alloc_at_without_replacement(entity) {
AllocAtWithoutReplacement::Exists(location) => {
// SAFETY: `entity` exists and `location` is that entity's location
@ -895,7 +903,7 @@ impl World {
/// assert_eq!(position.x, 0.0);
/// ```
pub fn spawn_empty(&mut self) -> EntityWorldMut {
self.flush_entities();
self.flush();
let entity = self.entities.alloc();
// SAFETY: entity was just allocated
unsafe { self.spawn_at_empty_internal(entity) }
@ -961,7 +969,7 @@ impl World {
/// assert_eq!(position.x, 2.0);
/// ```
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut {
self.flush_entities();
self.flush();
let change_tick = self.change_tick();
let entity = self.entities.alloc();
let entity_location = {
@ -1092,6 +1100,7 @@ impl World {
/// ```
#[inline]
pub fn despawn(&mut self, entity: Entity) -> bool {
self.flush();
if let Some(entity) = self.get_entity_mut(entity) {
entity.despawn();
true
@ -1113,9 +1122,9 @@ impl World {
/// By clearing this internal state, the world "forgets" about those changes, allowing a new round
/// of detection to be recorded.
///
/// When using `bevy_ecs` as part of the full Bevy engine, this method is added as a system to the
/// main app, to run during `Last`, so you don't need to call it manually. When using `bevy_ecs`
/// as a separate standalone crate however, you need to call this manually.
/// When using `bevy_ecs` as part of the full Bevy engine, this method is called automatically
/// by `bevy_app::App::update` and `bevy_app::SubApp::update`, so you don't need to call it manually.
/// When using `bevy_ecs` as a separate standalone crate however, you do need to call this manually.
///
/// ```
/// # use bevy_ecs::prelude::*;
@ -1743,7 +1752,7 @@ impl World {
I::IntoIter: Iterator<Item = (Entity, B)>,
B: Bundle,
{
self.flush_entities();
self.flush();
let change_tick = self.change_tick();
@ -2029,9 +2038,15 @@ impl World {
}
}
/// Calls both [`World::flush_entities`] and [`World::flush_commands`].
#[inline]
pub fn flush(&mut self) {
self.flush_entities();
self.flush_commands();
}
/// Applies any commands in the world's internal [`CommandQueue`].
/// This does not apply commands from any systems, only those stored in the world.
#[inline]
pub fn flush_commands(&mut self) {
// SAFETY: `self.command_queue` is only de-allocated in `World`'s `Drop`
if !unsafe { self.command_queue.is_empty() } {
@ -2082,6 +2097,13 @@ impl World {
self.last_change_tick
}
/// Returns the id of the last ECS event that was fired.
/// Used internally to ensure observers don't trigger multiple times for the same event.
#[inline]
pub(crate) fn last_trigger_id(&self) -> u32 {
self.last_trigger_id
}
/// Sets [`World::last_change_tick()`] to the specified value during a scope.
/// When the scope terminates, it will return to its old value.
///

View File

@ -27,7 +27,7 @@ where
pub(crate) fn new(world: &'w mut World, iter: I) -> Self {
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
// necessary
world.flush_entities();
world.flush();
let change_tick = world.change_tick();

View File

@ -9,6 +9,7 @@ use crate::{
change_detection::{MutUntyped, Ticks, TicksMut},
component::{ComponentId, ComponentTicks, Components, StorageType, Tick, TickCells},
entity::{Entities, Entity, EntityLocation},
observer::Observers,
prelude::Component,
removal_detection::RemovedComponentEvents,
storage::{Column, ComponentSparseSet, Storages},
@ -231,6 +232,13 @@ impl<'w> UnsafeWorldCell<'w> {
&unsafe { self.world_metadata() }.removed_components
}
/// Retrieves this world's [`Observers`] collection.
pub(crate) unsafe fn observers(self) -> &'w Observers {
// SAFETY:
// - we only access world metadata
&unsafe { self.world_metadata() }.observers
}
/// Retrieves this world's [`Bundles`] collection.
#[inline]
pub fn bundles(self) -> &'w Bundles {
@ -571,6 +579,14 @@ impl<'w> UnsafeWorldCell<'w> {
// - caller ensures that we have permission to access the queue
unsafe { (*self.0).command_queue.clone() }
}
/// # Safety
/// It is the callers responsibility to ensure that there are no outstanding
/// references to `last_trigger_id`.
pub(crate) unsafe fn increment_trigger_id(self) {
// SAFETY: Caller ensure there are no outstanding references
unsafe { (*self.0).last_trigger_id += 1 }
}
}
impl Debug for UnsafeWorldCell<'_> {

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_encase_derive"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Bevy derive macro for encase"
homepage = "https://bevyengine.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.14.0-dev" }
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.14.0" }
encase_derive_impl = "0.8"
[lints]

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gilrs"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Gamepad system made using Gilrs for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,11 +10,11 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_input = { path = "../bevy_input", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_time = { path = "../bevy_time", version = "0.14.0" }
# other
gilrs = "0.10.1"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gizmos"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides gizmos for Bevy Engine"
homepage = "https://bevyengine.org"
@ -14,20 +14,20 @@ webgpu = []
[dependencies]
# Bevy
bevy_pbr = { path = "../bevy_pbr", version = "0.14.0-dev", optional = true }
bevy_sprite = { path = "../bevy_sprite", version = "0.14.0-dev", optional = true }
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_gizmos_macros = { path = "macros", version = "0.14.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
bevy_pbr = { path = "../bevy_pbr", version = "0.14.0", optional = true }
bevy_sprite = { path = "../bevy_sprite", version = "0.14.0", optional = true }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_color = { path = "../bevy_color", version = "0.14.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0" }
bevy_render = { path = "../bevy_render", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_gizmos_macros = { path = "macros", version = "0.14.0" }
bevy_time = { path = "../bevy_time", version = "0.14.0" }
bytemuck = "1.0"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gizmos_macros"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Derive implementations for bevy_gizmos"
homepage = "https://bevyengine.org"
@ -13,7 +13,7 @@ proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.14.0-dev" }
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.14.0" }
syn = "2.0"
proc-macro2 = "1.0"

View File

@ -74,8 +74,8 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);
// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
let position_a = view.inverse_clip_from_world * clip_a;
let position_b = view.inverse_clip_from_world * clip_b;
let position_a = view.world_from_clip * clip_a;
let position_b = view.world_from_clip * clip_b;
let world_distance = length(position_a.xyz - position_b.xyz);
// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gltf"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Bevy Engine GLTF loading"
homepage = "https://bevyengine.org"
@ -12,29 +12,30 @@ keywords = ["bevy"]
dds = ["bevy_render/dds"]
pbr_transmission_textures = ["bevy_pbr/pbr_transmission_textures"]
pbr_multi_layer_material_textures = []
pbr_anisotropy_texture = []
[dependencies]
# bevy
bevy_animation = { path = "../bevy_animation", version = "0.14.0-dev", optional = true }
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_pbr = { path = "../bevy_pbr", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_animation = { path = "../bevy_animation", version = "0.14.0", optional = true }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0" }
bevy_color = { path = "../bevy_color", version = "0.14.1" }
bevy_core = { path = "../bevy_core", version = "0.14.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_pbr = { path = "../bevy_pbr", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
] }
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
bevy_scene = { path = "../bevy_scene", version = "0.14.0-dev", features = [
bevy_render = { path = "../bevy_render", version = "0.14.0" }
bevy_scene = { path = "../bevy_scene", version = "0.14.0", features = [
"bevy_render",
] }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
# other
gltf = { version = "1.4.0", default-features = false, features = [

View File

@ -1086,7 +1086,9 @@ fn load_material(
clearcoat_normal_texture: clearcoat.clearcoat_normal_texture,
anisotropy_strength: anisotropy.anisotropy_strength.unwrap_or_default() as f32,
anisotropy_rotation: anisotropy.anisotropy_rotation.unwrap_or_default() as f32,
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_channel: anisotropy.anisotropy_channel,
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_texture: anisotropy.anisotropy_texture,
..Default::default()
}
@ -1898,11 +1900,14 @@ impl ClearcoatExtension {
struct AnisotropyExtension {
anisotropy_strength: Option<f64>,
anisotropy_rotation: Option<f64>,
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_channel: UvChannel,
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_texture: Option<Handle<Image>>,
}
impl AnisotropyExtension {
#[allow(unused_variables)]
fn parse(
load_context: &mut LoadContext,
document: &Document,
@ -1913,6 +1918,7 @@ impl AnisotropyExtension {
.get("KHR_materials_anisotropy")?
.as_object()?;
#[cfg(feature = "pbr_anisotropy_texture")]
let (anisotropy_channel, anisotropy_texture) = extension
.get("anisotropyTexture")
.and_then(|value| value::from_value::<json::texture::Info>(value.clone()).ok())
@ -1927,7 +1933,9 @@ impl AnisotropyExtension {
Some(AnisotropyExtension {
anisotropy_strength: extension.get("anisotropyStrength").and_then(Value::as_f64),
anisotropy_rotation: extension.get("anisotropyRotation").and_then(Value::as_f64),
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_channel: anisotropy_channel.unwrap_or_default(),
#[cfg(feature = "pbr_anisotropy_texture")]
anisotropy_texture,
})
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_hierarchy"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides hierarchy functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -16,14 +16,14 @@ reflect = ["bevy_ecs/bevy_reflect", "bevy_reflect"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev", optional = true }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev", optional = true }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.14.0", optional = true }
bevy_core = { path = "../bevy_core", version = "0.14.0", optional = true }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
"smallvec",
], optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
smallvec = { version = "1.11", features = ["union", "const_generics"] }

View File

@ -612,6 +612,10 @@ impl<'w> BuildWorldChildren for EntityWorldMut<'w> {
}
fn push_children(&mut self, children: &[Entity]) -> &mut Self {
if children.is_empty() {
return self;
}
let parent = self.id();
if children.contains(&parent) {
panic!("Cannot push entity as a child of itself.");
@ -1242,4 +1246,14 @@ mod tests {
let children = query.get(&world, parent).unwrap();
assert_eq!(**children, [child]);
}
#[test]
fn push_children_does_not_insert_empty_children() {
let mut world = World::new();
let parent = world.spawn_empty().push_children(&[]).id();
let mut query = world.query::<&Children>();
let children = query.get(&world, parent);
assert!(children.is_err());
}
}

View File

@ -91,6 +91,7 @@ pub trait DespawnRecursiveExt {
impl DespawnRecursiveExt for EntityCommands<'_> {
/// Despawns the provided entity and its children.
/// This will emit warnings for any entity that does not exist.
fn despawn_recursive(mut self) {
let entity = self.id();
self.commands().add(DespawnRecursive { entity });
@ -105,6 +106,7 @@ impl DespawnRecursiveExt for EntityCommands<'_> {
impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> {
/// Despawns the provided entity and its children.
/// This will emit warnings for any entity that does not exist.
fn despawn_recursive(self) {
let entity = self.id();

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_input"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides input functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -14,11 +14,11 @@ serialize = ["serde"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"glam",
"smol_str",
] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_internal"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "An internal Bevy crate used to facilitate optional dynamic linking via the 'dynamic_linking' feature"
homepage = "https://bevyengine.org"
@ -48,6 +48,9 @@ zstd = ["bevy_render/zstd"]
# Include tonemapping LUT KTX2 files.
tonemapping_luts = ["bevy_core_pipeline/tonemapping_luts"]
# Include SMAA LUT KTX2 Files
smaa_luts = ["bevy_core_pipeline/smaa_luts"]
# Audio format support (vorbis is enabled by default)
flac = ["bevy_audio/flac"]
mp3 = ["bevy_audio/mp3"]
@ -71,6 +74,7 @@ shader_format_spirv = ["bevy_render/shader_format_spirv"]
serialize = [
"bevy_core/serialize",
"bevy_input/serialize",
"bevy_ecs/serialize",
"bevy_time/serialize",
"bevy_window/serialize",
"bevy_winit?/serialize",
@ -107,6 +111,12 @@ pbr_multi_layer_material_textures = [
"bevy_gltf?/pbr_multi_layer_material_textures",
]
# Anisotropy texture in `StandardMaterial`:
pbr_anisotropy_texture = [
"bevy_pbr?/pbr_anisotropy_texture",
"bevy_gltf?/pbr_anisotropy_texture",
]
# Optimise for WebGL2
webgl = [
"bevy_core_pipeline?/webgl",
@ -187,44 +197,44 @@ bevy_state = ["dep:bevy_state"]
[dependencies]
# bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_state = { path = "../bevy_state", optional = true, version = "0.14.0-dev" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.14.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_ptr = { path = "../bevy_ptr", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_a11y = { path = "../bevy_a11y", version = "0.14.0" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_core = { path = "../bevy_core", version = "0.14.0" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_state = { path = "../bevy_state", optional = true, version = "0.14.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0" }
bevy_input = { path = "../bevy_input", version = "0.14.0" }
bevy_log = { path = "../bevy_log", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_ptr = { path = "../bevy_ptr", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
] }
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.14.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.14.0" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_window = { path = "../bevy_window", version = "0.14.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0" }
# bevy (optional)
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.14.0-dev" }
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", optional = true, version = "0.14.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.14.0-dev" }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.14.0-dev" }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.14.0-dev" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.14.0-dev" }
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.14.0-dev" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.14.0-dev" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.14.0-dev" }
bevy_text = { path = "../bevy_text", optional = true, version = "0.14.0-dev" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.14.0-dev" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.14.0-dev" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.14.0-dev" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.14.0-dev", default-features = false }
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.14.0-dev" }
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.14.0" }
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.14.0" }
bevy_color = { path = "../bevy_color", optional = true, version = "0.14.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.14.0" }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.14.0" }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.14.0" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.14.0" }
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.14.0" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.14.0" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.14.0" }
bevy_text = { path = "../bevy_text", optional = true, version = "0.14.0" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.14.0" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.14.0" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.14.0" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.14.0", default-features = false }
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.14.0" }
[lints]
workspace = true

View File

@ -28,6 +28,8 @@ use bevy_app::{Plugin, PluginGroup, PluginGroupBuilder};
/// * [`AudioPlugin`](crate::audio::AudioPlugin) - with feature `bevy_audio`
/// * [`GilrsPlugin`](crate::gilrs::GilrsPlugin) - with feature `bevy_gilrs`
/// * [`AnimationPlugin`](crate::animation::AnimationPlugin) - with feature `bevy_animation`
/// * [`GizmoPlugin`](crate::gizmos::GizmoPlugin) - with feature `bevy_gizmos`
/// * [`StatesPlugin`](crate::app::StatesPlugin) - with feature `bevy_state`
/// * [`DevToolsPlugin`](crate::dev_tools::DevToolsPlugin) - with feature `bevy_dev_tools`
/// * [`CiTestingPlugin`](crate::dev_tools::ci_testing::CiTestingPlugin) - with feature `bevy_ci_testing`
///

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_log"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides logging for Bevy Engine"
homepage = "https://bevyengine.org"
@ -13,9 +13,9 @@ trace = ["tracing-error"]
trace_tracy_memory = ["dep:tracy-client"]
[dependencies]
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
tracing-subscriber = { version = "0.3.1", features = [
"registry",

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_macro_utils"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "A collection of utils for Bevy Engine"
homepage = "https://bevyengine.org"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_math"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Provides math functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -20,7 +20,7 @@ rand = { version = "0.8", features = [
], default-features = false, optional = true }
smallvec = { version = "1.11" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"glam",
], optional = true }
@ -30,7 +30,7 @@ approx = "0.5"
rand = "0.8"
rand_chacha = "0.3"
# Enable the approx feature when testing.
bevy_math = { path = ".", version = "0.14.0-dev", features = ["approx"] }
bevy_math = { path = ".", version = "0.14.0", features = ["approx"] }
glam = { version = "0.27", features = ["approx"] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_mikktspace"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
authors = [
"Benjamin Wasty <benny.wasty@gmail.com>",

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_pbr"
version = "0.14.0-dev"
version = "0.14.0"
edition = "2021"
description = "Adds PBR rendering to Bevy Engine"
homepage = "https://bevyengine.org"
@ -13,6 +13,7 @@ webgl = []
webgpu = []
pbr_transmission_textures = []
pbr_multi_layer_material_textures = []
pbr_anisotropy_texture = []
shader_format_glsl = ["bevy_render/shader_format_glsl"]
trace = ["bevy_render/trace"]
ios_simulator = ["bevy_render/ios_simulator"]
@ -29,20 +30,20 @@ meshlet_processor = ["meshlet", "dep:meshopt", "dep:metis", "dep:itertools"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.14.0" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0" }
bevy_color = { path = "../bevy_color", version = "0.14.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0" }
bevy_math = { path = "../bevy_math", version = "0.14.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0", features = [
"bevy",
] }
bevy_render = { path = "../bevy_render", version = "0.14.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.14.0" }
bevy_transform = { path = "../bevy_transform", version = "0.14.0" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0" }
bevy_window = { path = "../bevy_window", version = "0.14.0" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0" }
# other
@ -56,7 +57,7 @@ serde = { version = "1", features = ["derive", "rc"], optional = true }
bincode = { version = "1", optional = true }
thiserror = { version = "1", optional = true }
range-alloc = { version = "0.1", optional = true }
meshopt = { version = "0.2.1", optional = true }
meshopt = { version = "0.3.0", optional = true }
metis = { version = "0.2", optional = true }
itertools = { version = "0.13", optional = true }
# direct dependency required for derive macro

Some files were not shown because too many files have changed in this diff Show More