Compare commits

..

151 Commits

Author SHA1 Message Date
François
5c759a1be8
Release 0.14.2 2024-09-06 18:33:25 +02:00
François Mockers
79b248c7ce
fix imports in example ui_texture_slice_flip_and_tile (#15064)
# Objective

- don't use a sub crate in an example

## Solution

- fix imports
2024-09-06 18:33:25 +02:00
ickshonpe
cd9fec3547
UI outlines radius (#15018)
Fixes #13479

This also fixes the gaps you can sometimes observe in outlines
(screenshot from main, not this PR):

<img width="636" alt="outline-gaps"
src="https://github.com/user-attachments/assets/c11dae24-20f5-4aea-8ffc-1894ad2a2b79">

The outline around the last item in each section has vertical gaps.

Draw the outlines with corner radius using the existing border rendering
for uinodes. The outline radius is very simple to calculate. We just
take the computed border radius of the node, and if it's greater than
zero, add it to the distance from the edge of the node to the outer edge
of the node's outline.

---

<img width="634" alt="outlines-radius"
src="https://github.com/user-attachments/assets/1ecda26c-65c5-41ef-87e4-5d9171ddc3ae">

---------

Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
2024-09-05 22:02:42 +02:00
ickshonpe
dd929ad867
UI texture slice texture flipping reimplementation (#15034)
# Objective

Fixes #15032

## Solution

Reimplement support for the `flip_x` and `flip_y` fields.
This doesn't flip the border geometry, I'm not really sure whether that
is desirable or not.
Also fixes a bug that was causing the side and center slices to tile
incorrectly.

### Testing

```
cargo run --example ui_texture_slice_flip_and_tile
```

## Showcase
<img width="787" alt="nearest"
src="https://github.com/user-attachments/assets/bc044bae-1748-42ba-92b5-0500c87264f6">
With tiling need to use nearest filtering to avoid bleeding between the
slices.

---------

Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-09-05 22:01:10 +02:00
ickshonpe
6231ed02dd
Ignore clicks on uinodes outside of rounded corners (#14957)
Fixes #14941

1. Add a `resolved_border_radius` field to `Node` to hold the resolved
border radius values.
2. Remove the border radius calculations from the UI's extraction
functions.
4. Compute the border radius during UI relayouts in `ui_layout_system`
and store them in `Node`.
5. New `pick_rounded_rect` function based on the border radius SDF from
`ui.wgsl`.
6. Use `pick_rounded_rect` in `focus` and `picking_backend` to check if
the pointer is hovering UI nodes with rounded corners.
---

```
cargo run --example button
```

https://github.com/user-attachments/assets/ea951a64-17ef-455e-b5c9-a2e6f6360648

Modified button example with buttons with different corner radius:

```
use bevy::{color::palettes::basic::*, prelude::*, winit::WinitSettings};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // Only run the app when there is user input. This will significantly reduce CPU/GPU use.
        .insert_resource(WinitSettings::desktop_app())
        .add_systems(Startup, setup)
        .add_systems(Update, button_system)
        .run();
}

const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);

fn button_system(
    mut interaction_query: Query<
        (
            &Interaction,
            &mut BackgroundColor,
            &mut BorderColor,
            &Children,
        ),
        (Changed<Interaction>, With<Button>),
    >,
    mut text_query: Query<&mut Text>,
) {
    for (interaction, mut color, mut border_color, children) in &mut interaction_query {
        let mut text = text_query.get_mut(children[0]).unwrap();
        match *interaction {
            Interaction::Pressed => {
                text.sections[0].value = "Press".to_string();
                *color = PRESSED_BUTTON.into();
                border_color.0 = RED.into();
            }
            Interaction::Hovered => {
                text.sections[0].value = "Hover".to_string();
                *color = HOVERED_BUTTON.into();
                border_color.0 = Color::WHITE;
            }
            Interaction::None => {
                text.sections[0].value = "Button".to_string();
                *color = NORMAL_BUTTON.into();
                border_color.0 = Color::BLACK;
            }
        }
    }
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // ui camera
    commands.spawn(Camera2dBundle::default());
    commands
        .spawn(NodeBundle {
            style: Style {
                width: Val::Percent(100.0),
                height: Val::Percent(100.0),
                align_items: AlignItems::Center,
                justify_content: JustifyContent::Center,
                row_gap: Val::Px(10.),
                ..default()
            },
            ..default()
        })
        .with_children(|parent| {
            for border_radius in [
                BorderRadius {
                    top_left: Val::ZERO,
                    ..BorderRadius::MAX
                },
                BorderRadius {
                    top_right: Val::ZERO,
                    ..BorderRadius::MAX
                },
                BorderRadius {
                    bottom_right: Val::ZERO,
                    ..BorderRadius::MAX
                },
                BorderRadius {
                    bottom_left: Val::ZERO,
                    ..BorderRadius::MAX
                },
            ] {
                parent
                    .spawn(ButtonBundle {
                        style: Style {
                            width: Val::Px(150.0),
                            height: Val::Px(65.0),
                            border: UiRect::all(Val::Px(5.0)),
                            // horizontally center child text
                            justify_content: JustifyContent::Center,
                            // vertically center child text
                            align_items: AlignItems::Center,
                            ..default()
                        },
                        border_color: BorderColor(Color::BLACK),
                        border_radius,
                        background_color: NORMAL_BUTTON.into(),
                        ..default()
                    })
                    .with_child(TextBundle::from_section(
                        "Button",
                        TextStyle {
                            font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                            font_size: 40.0,
                            color: Color::srgb(0.9, 0.9, 0.9),
                        },
                    ));
            }
        });
}
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Matty <weatherleymatthew@gmail.com>
2024-09-05 22:01:05 +02:00
ickshonpe
9afe666f6d
UI texture atlas slice shader (#14990)
Fixes https://github.com/bevyengine/bevy/issues/14183

Reimplement the UI texture atlas slicer using a shader.

The problems with #14183 could be fixed more simply by hacking around
with the coordinates and scaling but that way is very fragile and might
get broken again the next time we make changes to the layout
calculations. A shader based solution is more robust, it's impossible
for gaps to appear between the image slices with these changes as we're
only drawing a single quad.

I've not tried any benchmarks yet but it should much more efficient as
well, in the worst cases even hundreds or thousands of times faster.

Maybe could have used the UiMaterialPipeline. I wrote the shader first
and used fat vertices and then realised it wouldn't work that way with a
UiMaterial. If it's rewritten it so it puts all the slice geometry in
uniform buffer, then it might work? Adding the uniform buffer would
probably make the shader more complicated though, so don't know if it's
even worth it. Instancing is another alternative.

The examples are working and it seems to match the old API correctly but
I've not used the texture atlas slicing API for anything before, I
reviewed the PR but that was back in January.

Needs a review by someone who knows the rendering pipeline and wgsl
really well because I don't really have any idea what I'm doing.
2024-09-05 21:39:43 +02:00
ickshonpe
489c1e1a87
Resolve UI outlines using the correct target's viewport size (#14947)
`resolve_outlines_system` wasn't updated when multi-window support was
added and it always uses the size of the primary window when resolving
viewport coords, regardless of the layout's camera target.

Fixes #14945

It's awkward to get the viewport size of the target for an individual
node without walking the tree or adding extra fields to `Node`, so I
removed `resolve_outlines_system` and instead the outline values are
updated in `ui_layout_system`.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-09-05 21:37:24 +02:00
MichiRecRoom
a4ea708b69
Don't require going through bevy_animation::prelude to get to certain items in bevy_animation (#14979)
# Objective
* Fixes https://github.com/bevyengine/bevy/issues/14889

## Solution
Exposes `bevy_animation::{animatable, graph, transition}` to the world.

## Testing
- Did you test these changes? If so, how?
- These changes do not need testing, as they do not modify/add/remove
any functionality.
- ~~Are there any parts that need more testing?~~
- ~~How can other people (reviewers) test your changes? Is there
anything specific they need to know?~~
- ~~If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?~~

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-09-05 21:35:40 +02:00
François Mockers
ff3d632f31
don't use padding for layout (#14944)
# Objective

- Fixes #14792 
- Padding is already handled by taffy, don't handle it also on Bevy side

## Solution

- Remove extra computation added in
https://github.com/bevyengine/bevy/pull/14777
2024-09-05 21:35:40 +02:00
akimakinai
7b33038393
Fix Gizmo joint rendering in webgpu (#14721)
# Objective

- Gizmo rendering on WebGPU has been fixed by #14653, but gizmo joints
still cause error
(https://github.com/bevyengine/bevy/issues/14696#issuecomment-2283689669)
when enabled.

## Solution

- Applies the same fix as #14653 to Gizmo joints.

I'm noob and just copied their solution, please correct me if I did
something wrong.

## Testing

- Tested 2d-gizmos and 3d-gizmos examples in WebGPU on Chrome. No
rendering errors, and the gizmo joints are apparently rendered ok.
2024-09-05 21:35:40 +02:00
Luca Della Vedova
d7b6e81fb2
Fix commands not being Send / Sync in 0.14 (#14392)
# Objective

Fixes Commands not being `Send` or `Sync` anymore in 0.14 by
implementing `Send` and `Sync` for `RawCommandQueue`.

## Solution

Reference discussion in
[discord](https://discord.com/channels/691052431525675048/691052431974465548/1259464518539411570).
It seems that in https://github.com/bevyengine/bevy/pull/13249, when
adding a `RawCommandQueue` variant to the `InternalQueue`, the `Send /
Sync` traits were not implemented for it, which bubbled up all the way
to `Commands` not being `Send / Sync` anymore.
I am not very familiar with the ECS internals so I can't say whether the
`RawCommandQueue` is safe to be shared between threads, but I know for
sure that before the linked PR `Commands` were indeed `Send` and `Sync`
so that PR broke "some workflows" (mandatory
[xkcd](https://xkcd.com/1172/)).

## Testing

This PR itself includes a compile test to make sure `Commands` will
implement `Send` and `Sync`. The test itself fails without the
implementation and succeeds with it.
Furthermore, if I cherry pick the test to a previous release (i.e. 0.13)
it indeed succeeds, showing that this is a regression specific to 0.14.

---------

Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
2024-09-05 21:35:40 +02:00
Nihilistas
6a4b48ba6b
#14143 - fix bevy_ui padding (#14777)
# Objective

fixes #14143

## Solution

- removed the temporary blocker if statement when setting padding in
`Style`
- adjusted the `layout_location` and `layout_size` so they use
`layout.padding` which we already get from Taffy

## Testing

- this is the test code I used:
```rust
use bevy::prelude::*;

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

fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
){
    let font = asset_server.load("fonts/FiraSans-Bold.ttf");
    commands.spawn(Camera2dBundle::default());

    commands
        .spawn(NodeBundle {
            style: Style {
                width: Val::Px(200.),
                height: Val::Px(100.),
                align_items: AlignItems::Center,
                justify_content: JustifyContent::Center,
                align_self: AlignSelf::Center,
                justify_self: JustifySelf::Center,
                ..Default::default()
            },
            background_color: BackgroundColor(Color::srgb(0.,1., 1.)),
            ..Default::default()
        })
        .with_children(|builder| {
            builder.spawn((TextBundle::from_section(
                    "Hello World",
                    TextStyle {
                        font,
                        font_size: 32.0,
                        color: Color::WHITE,
                        },
                ).with_style(Style {
                    padding: UiRect::all(Val::Px(10.)),
                    width: Val::Px(100.),
                    height: Val::Px(100.),
                    ..Default::default()
                }).with_background_color(Color::srgb(1.,0., 0.)),
            ));
            // spawn an image bundle
            builder.spawn(ImageBundle {
                style: Style {
                    padding: UiRect::all(Val::Px(10.)),
                    width: Val::Px(100.),
                    height: Val::Px(100.),
                    ..Default::default()
                },
                image: asset_server.load("square.png").into(),
                ..Default::default()
            });
        });
}
```

- I tested 5 cases: 10px padding from all sides, and 10px padding from
left, right, bottom, and top separately

- **For reviewers**: please check more cases or try to run it on some
more complicated real-world UI

## Showcase

<img width="374" alt="Screenshot 2024-08-16 at 09 28 04"
src="https://github.com/user-attachments/assets/59b85b00-e255-4669-be13-a287ef35d4d9">
<img width="288" alt="Screenshot 2024-08-16 at 09 28 47"
src="https://github.com/user-attachments/assets/170a79b1-ec9c-45f9-82f5-ba7fa4029334">
<img width="274" alt="Screenshot 2024-08-16 at 09 45 16"
src="https://github.com/user-attachments/assets/e3fd9b59-b41f-427d-8c07-5acdf1dc5ecf">
<img width="292" alt="Screenshot 2024-08-16 at 09 45 36"
src="https://github.com/user-attachments/assets/c4f708aa-3f0d-4ff3-b779-0d4ed5f6ba73">
<img width="261" alt="Screenshot 2024-08-16 at 09 45 58"
src="https://github.com/user-attachments/assets/eba1e26f-04ca-4178-87c8-3a79daff3a9a">

---------

Co-authored-by: dpeke <dpekelis@funstage.com>
2024-09-05 21:35:40 +02:00
robtfm
650e7c9eb4
apply finished animations (#14743)
# Objective

fix #14742

## Solution

the issue arises because "finished" animations (where current time >=
last keyframe time) are not applied at all.
when transitioning from a finished animation to another later-indexed
anim, the transition kind-of works because the finished anim is skipped,
then the new anim is applied with a lower weight (weight / total_weight)
when transitioning from a finished animation to another earlier-indexed
anim, the transition is instant as the new anim is applied with 1.0 (as
weight == total_weight for the first applied), then the finished
animation is skipped.

to fix this we can always apply every animation based on the nearest 2
keyframes, and clamp the interpolation between them to [0,1].

pros:
- finished animations can be transitioned out of correctly
- blended animations where some curves have a last-keyframe before the
end of the animation will blend properly
- animations will actually finish on their last keyframe, rather than a
fraction of a render-frame before the end

cons:
- we have to re-apply finished animations every frame whether it's
necessary or not. i can't see a way to avoid this.
2024-09-05 21:35:40 +02:00
eckz
0793d05256
Making DynamicEnum::is_dynamic() return true (#14732)
# Objective

- Right now `DynamicEnum::is_dynamic()` is returning `false`. I don't
think this was expected, since the rest of `Dynamic*` types return
`true`.

## Solution

- Making `DynamicEnum::is_dynamic()` return true

## Testing

- Added an extra unit test to verify that `.is_dynamic()` returns
`true`.
2024-09-05 21:35:40 +02:00
AFKessen
dcb506ea70
Fix 3D Gizmo webgpu rendering (#14653)
# Objective

The changes made in https://github.com/bevyengine/bevy/pull/12252
introduced an previously fixed bug in webgpu rendering.

## Solution

This fix is based on https://github.com/bevyengine/bevy/pull/8910 and
applies the same vertex buffer layout assignment for the LineGizmo
Pipeline.

## Testing

- Tested the 3D Gizmo example in webgpu and webgl environments

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-09-05 21:35:40 +02:00
charlotte
c661da1d42
Fix TAA on camera with viewport (#14582)
# Objective

Closes #14526 

## Solution

The history texture was being created incorrectly with the viewport size
rather than target size. When viewport < target, this meant that the
render attachments would differer in size which causes a wgpu validation
error.

## Testing

Example in linked issue works.
2024-09-05 21:35:40 +02:00
Ben Frankel
216dba23cb
Explicitly order CameraUpdateSystem before UiSystem::Prepare (#14609)
Fixes https://github.com/bevyengine/bevy/issues/14277.

May also fix https://github.com/bevyengine/bevy/issues/14255, needs
verification.

Explicitly order `CameraUpdateSystem` before `UiSystem::Prepare`, so
that when the window resizes, `camera_system` will update the `Camera`'s
viewport size before `ui_layout_system` also reacts to the window resize
and tries to read the new `Camera` viewport size to set UI node sizes
accordingly.

I tested that explicitly ordering `CameraUpdateSystem` _after_ triggers
the buggy behavior, and explicitly ordering it _before_ does not trigger
the buggy behavior or crash the app (which also demonstrates that the
system sets are ambiguous).

---

`CameraUpdateSystem` is now explicitly ordered before
`UiSystem::Prepare` instead of being ambiguous with it.
2024-09-05 21:35:36 +02:00
Brian Reavis
7d63efe4c2
Don’t prepare lights (and shadow map textures) for 2D cameras (#14574)
# Objective

When running the Metal debugger I noticed that 2D cameras have shadow
map textures from `bevy_pbr` built for them. For a 2560x1440 2D camera,
this PR saves about 40mb of texture memory.


![image](https://github.com/user-attachments/assets/925e9392-2721-41bb-83e9-25c84fd563cd)


![image](https://github.com/user-attachments/assets/0cc3c0a9-cbf7-431c-b444-952c28d4e9d0)


## Solution

- Added `With<Camera3d>` filter to the appropriate view queries.

## Testing

- This is a trivial fix (the examples still work)
2024-09-05 21:34:05 +02:00
Lixou
8f17fdafc3
Make AnimationPlayer::start and ::play work accordingly to documentation (#14546)
# Objective

While scrolling through the animation crate, I was confused by the docs
and code for the two methods. One does nothing for resetting an
animation, the other just resets the weights for whatever reason.

## Solution

Made the functions work accordingly to their documentation.
`start` now replays the animation.
And `play` doesn't reset the weight anymore. I have no clue why it
should. `play` is there to don't do anything to an already existing
animation.

## Testing

I tested the current 0.14 code with bevy playground in the Animated Fox
exampled and changed it such that on pressing space, either `play` or
`start` would be called. Neither changed anything.
I then inlined the function for start there and it restarted the
animation, so it should work.

---

## Migration Guide

`AnimationPlayer::start` now correspondingly to its docs restarts a
running animation.
`AnimationPlayer::play` doesn't reset the weight anymore.
2024-09-05 21:34:05 +02:00
Ben Frankel
dff6471049
Fix hue mixing for Lcha and Oklcha (#14468)
# Objective

Fix erroneous hue mixing in `Lcha` and `Oklcha`. Purple + Red == Green
is the current behavior.

## Solution

Use `crate::color_ops::lerp_hue` to handle the wrap-around at 360
degrees, the same way that `Hsla`, `Hsva`, and `Hwba` do it.

## Testing

Game jamming, but tested that the workaround below produces
correct-looking colors in my jam game.
2024-09-05 21:34:05 +02:00
Hannes Karppila
5e2c04516c
Fix repeated animation transition bug (#14411)
# Objective

Fixes #13910

When a transition is over, the animation is stopped. There was a race
condition; if an animation was started while it also had an active
transition, the transition ending would then incorrectly stop the newly
added animation.

## Solution

When starting an animation, cancel any previous transition for the same
animation.

## Testing

The changes were tested manually, mainly by using the `animated_fox`
example. I also tested with changes from
https://github.com/bevyengine/bevy/pull/13909.

I'd like to have an unit test for this as well, but it seems quite
complex to do, as I'm not sure how I would detect an incorrectly paused
animation.

Reviewers can follow the instructions in #13910 to reproduce.

Tested on macos 14.4 (M3 processor) Should be platform-independent,
though.
2024-09-05 21:34:05 +02:00
charlotte
3830e71076
Set scissor on upscale to match camera viewport (#14287)
# Objective

When the user renders multiple cameras to the same output texture, it
can sometimes be confusing what `ClearColorConfig` is necessary for each
camera to avoid overwriting the previous camera's output. This is
particular true in cases where the user uses mixed HDR cameras, which
means that their scene is being rendered to different internal textures.

## Solution

When a view has a configured viewport, set the GPU scissor in the
upscaling node so we don't overwrite areas that were written to by other
cameras.

## Testing

Ran the `split_screen` example.
2024-09-05 21:34:05 +02:00
François
d65eb39277
Release 0.14.1 2024-08-02 20:35:38 +02:00
François
c217238c5e
clippy happy 2024-08-02 20:35:38 +02:00
Alessio Marchi
61c683fb6a
feat: add insert_after and insert_startup_before (#13941)
# Objective

Fixes #13866 

## Solution

Add `insert_before` in **FixedMainScheduleOrder** and
**MainScheduleOrder**, add `insert_startup_before` in
**MainScheduleOrder**, applying the same logic as `insert_after`, except
for parameters naming and insertion index.
2024-08-02 19:55:33 +02:00
James O'Brien
833ee3f577
Skip batching for phase items from other pipelines (#14296)
# Objective

- Fix #14295

## Solution

- Early out when `GFBD::get_index_and_compare_data` returns None.

## Testing

- Tested on a selection of examples including `many_foxes` and
`3d_shapes`.
- Resolved the original issue in `bevy_vector_shapes`.
2024-08-02 19:26:47 +02:00
JJJimbo1
d8886408bf
fix asymmetrical 9-slicing (#14148)
# Objective

Fixes #14147.

## Solution

Modify the slicing checks and algorithm to fully allow asymmetrical
textures to work.
Some opinionated code cleanup.

## Testing

Tested using the ui_texture_slice example and a custom asymmetrical
texture.

Before:

![asymmetrical_texture_slice_before](https://github.com/bevyengine/bevy/assets/88861660/00dafce1-904a-41ac-b5d9-faaf087b0681)

After:

![asymmetrical_texture_slice_after](https://github.com/bevyengine/bevy/assets/88861660/f3d742f3-6157-4d35-b383-aee4b8f6e7d0)

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-08-02 19:26:47 +02:00
Brezak
3a6176b6cb
Properly handle repeated window close requests (#14573)
# Objective

Spamming the window close button on window may trigger a panic.

```
thread 'main' panicked at <Bevy repo>\crates\bevy_ecs\src\system\commands\mod.rs:1320:13:
error[B0003]: Could not insert a bundle (of type `bevy_window:🪟:ClosingWindow`) for entity 0v1#4294967296 because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic when applying buffers for system `bevy_window::system::close_when_requested`!
2024-08-01T15:00:29.742612Z  WARN bevy_ecs::world::command_queue: CommandQueue has un-applied commands being dropped. Did you forget to call SystemState::apply?
Encountered a panic in system `bevy_app::main_schedule::Main::run_main`!
error: process didn't exit successfully: `target\debug\bevy.exe` (exit code: 101)
```

## Solution

Don't panic when trying to insert the `ClosingWindow` component into a
entity.

## Testing

Found and tested on windows. I haven't checked if this bug happens on
linux or macos.
For testing I ran this code:

```rust
use std::{thread, time::Duration};

use bevy::prelude::*;

fn lag() {
    thread::sleep(Duration::from_millis(300));
}

fn main() -> AppExit {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Update, lag)
        .run()
}
```

Then spammed the window close button. The panic no longer occurs.
2024-08-02 19:26:47 +02:00
Tamás Kiss
ebfe545f79
fix issue with phantom ui node children (#14490)
# Objective

The `ui_layout_system` relies on change detection to sync parent-child
relation to taffy. The children need to by synced before node removal to
avoid trying to set deleted nodes as children (due to how the different
queries collect entities). This however may leave nodes that were
removed set as children to other nodes in special cases.

Fixes #11385

## Solution

The solution is simply to re-sync the changed children after the nodes
are removed.

## Testing

Tested with `sickle_ui` where docking zone highlights would end up
glitched when docking was done in a certain manner:
- run the `docking_zone_splits` example
- pop out a tab from the top
- dock the floating panel in the center right
- grab another tab and try to hover the original static docking zone:
the highlight is semi-stuck
- (NOTE: sometimes it worked even without the fix due to scheduling
order not producing the bugged query results)

After the fix, the issue is no longer present.

NOTE: The performance impact should be minimal, as the child sync relies
on change detection. The change detection was also the reason the parent
nodes remained "stuck" with the phantom children if no other update were
done to them.
2024-08-02 19:26:47 +02:00
Sarthak Singh
0886e6a302
Disabled usage of the POLYGON_MODE_LINE gpu feature in the examples (#14402)
Fixes #14353
Fixes #14371

---------

Signed-off-by: Sarthak Singh <sarthak.singh99@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
2024-08-02 19:26:47 +02:00
Giacomo Stevanato
587cffdcde
Fix bevy_render's image dependency version (#14505)
# Objective

- `bevy_render` depends on `image 0.25` but uses `image::ImageReader`
which was added only in `image 0.25.2`
- users that have `image 0.25` in their `Cargo.lock` and update to the
latest `bevy_render` may thus get a compilation due to this (at least I
did)

## Solution

- Properly set the correct minimum version of `image` that `bevy_render`
depends on.
2024-08-02 19:25:42 +02:00
charlotte
27cafdae9b
Fix breaking image 0.25.2 release. (#14421)
Deprecated item breaking ci:
https://github.com/image-rs/image/releases/tag/v0.25.2. See
https://github.com/bevyengine/bevy/actions/runs/10030764981/job/27720434072?pr=14419
for example.
2024-08-02 19:25:42 +02:00
Brian Reavis
680c994100
Fix TextureCache memory leak and add is_empty() method (#14480)
# Objective

Fix a memory leak in `TextureCache` caused by the internal HashMap never
having unused entries cleared.

This isn't a giant memory leak, given the unused entries are simply
empty vectors. Though, if someone goes and resizes a window a bunch, it
can lead to hundreds/thousands of TextureDescriptor keys adding up in
the hashmap – which isn't ideal.

## Solution

- Only retain hashmap entries that still have textures.
- I also added an `is_empty()` method to `TextureCache`, which is useful
for 3rd-party higher-level caches that might have individual caches by
view entity or texture type, for example.

## Testing

- Verified the examples still work (this is a trivial change)
2024-08-02 19:24:54 +02:00
BD103
c4ea4776c4
Fix bevy_winit not building with serialize feature (#14469)
# Objective

- `bevy_winit` fails to build with just the `serialize` feature.
- Caught by [`flag-frenzy`](https://github.com/TheBevyFlock/flag-frenzy)
in [this
run](https://github.com/TheBevyFlock/flag-frenzy/actions/runs/10087486444/job/27891723948),
using the new, nuanced configuration system!

## Solution

- It was failing because `bevy_winit` did not pass the `serialize` flag
to two of its dependencies: `bevy_input` and `bevy_window`.
- To fix this, add these crates to the feature flag.

## Testing

```bash
# On Linux, you must also specify a backend: `x11` or `wayland`.
# You can do this with `-F serialize,x11`, etc.
cargo check -p bevy_winit --no-default-features -F serialize
```
2024-08-02 19:24:54 +02:00
NiseVoid
9daf16bb87
Handle 0 height in prepare_bloom_textures (#14423)
# Objective

- Fix a confusing panic when the viewport width is non-zero and the
height is 0, `prepare_bloom_textures` tries to create a `4294967295x1`
texture.

## Solution

- Avoid dividing by zero
- Apps still crash after this, but now on a more reasonable error about
the zero-size viewport

## Testing

- I isolated and tested the math. A height of 0 sets `mip_height_ratio`
to `inf`, causing the width to explode if it isn't also 0
2024-08-02 19:24:54 +02:00
BD103
2870d89d5c
Fix bevy_gltf PBR features not enabling corresponding bevy_pbr flags (#14486)
# Objective

- `bevy_gltf` does not build with only the
`pbr_multi_layer_material_textures` or `pbr_anisotropy_texture`
features.
- Caught by [`flag-frenzy`](https://github.com/TheBevyFlock/flag-frenzy)
in [this
run](https://github.com/TheBevyFlock/flag-frenzy/actions/runs/10087486444/job/27891723948).

## Solution

- This error was due to the feature not enabling the corresponding
feature in `bevy_pbr`. Adding these flags as a dependency fixes this
error.

## Testing

The following commands fail on `main`, but pass with this PR:

```bash
cargo check -p bevy_gltf --no-default-features -F pbr_multi_layer_material_textures
cargo check -p bevy_gltf --no-default-features -F pbr_anisotropy_texture
```
2024-08-02 19:24:54 +02:00
Al M.
2e577bcdc9
Fix single keyframe animations. (#14344)
# Objective

For clips with more than one curve, only the first was being applied if
there is only one keyframe in it.

## Solution

Continue!
2024-08-02 19:24:54 +02:00
Peter Hayman
6882420c7f
Add some missing reflect attributes (#14259)
# Objective

- Some types are missing reflection attributes, which means we can't use
them in scene serialization etc.
- Effected types
   - `BorderRadius`
   - `AnimationTransitions`
   - `OnAdd`
   - `OnInsert`
   - `OnRemove`
- My use-case for `OnAdd` etc to derive reflect is 'Serializable
Observer Components'. Add the component, save the scene, then the
observer is re-added on scene load.

```rust
#[derive(Reflect)]
struct MySerializeableObserver<T: Event>(#[reflect(ignore)]PhantomData<T>);

impl<T: Event> Component for MySerializeableObserver<T> {
  const STORAGE_TYPE: StorageType  = StorageType::Table;
    fn register_component_hooks(hooks: &mut ComponentHooks) {
      hooks.on_add(|mut world, entity, _| {
        world
          .commands()
          .entity(entity)
          .observe(|_trigger: Trigger<T>| {
            println!("it triggered etc.");
          });
    });
  }
}
```

## Solution

- Add the missing traits

---
2024-08-02 19:20:44 +02:00
Sou1gh0st
df3fcbd116
Fix incorrect function calls to hsv_to_rgb in render debug code. (#14260)
# Objective

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

## Solution

- correct the input parameters at these call sites.

## Testing

1. Use a 3D scene example with PBR lighting and shadows enabled, such as
the `shadow_caster_receiver` and `load_gltf` example, for testing.
2. Enable relevant shader defines in crates/bevy_pbr/src/pbr_material.rs
for the StandardMaterial.
```rust
impl Material for StandardMaterial {
    // ...
    fn specialize(
            _pipeline: &MaterialPipeline<Self>,
            descriptor: &mut RenderPipelineDescriptor,
            _layout: &MeshVertexBufferLayoutRef,
            key: MaterialPipelineKey<Self>,
        ) -> Result<(), SpecializedMeshPipelineError> {
            // ...
            // shader_defs.push("CLUSTERED_FORWARD_DEBUG_Z_SLICES".into());
            // shader_defs.push("CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY".into());
            shader_defs.push("DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES".into());
            // ...
    }
}
``` 

## Showcase
### CLUSTERED_FORWARD_DEBUG_Z_SLICES
- example: examples/3d/shadow_caster_receiver.rs

![Screenshot2024_07_10_143150](https://github.com/bevyengine/bevy/assets/6300263/fbd12712-5cb9-489d-a7d1-ed55f72fb234)

### CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY
- example: examples/3d/shadow_caster_receiver.rs

![Screenshot2024_07_10_143312](https://github.com/bevyengine/bevy/assets/6300263/8eca5d7a-27b6-4ff5-9f8d-d10b49b3f990)

### DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES
For this one, we need to use a large scene and modity the
`CascadeShadowConfigBuilder`, here is a simple patch for the `load_gltf`
example:
```
diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs
index 358446238..9403aa288 100644
--- a/examples/3d/load_gltf.rs
+++ b/examples/3d/load_gltf.rs
@@ -18,7 +18,7 @@ fn main() {
 fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
     commands.spawn((
         Camera3dBundle {
-            transform: Transform::from_xyz(0.7, 0.7, 1.0)
+            transform: Transform::from_xyz(0.7, 0.7, 2.0)
                 .looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
             ..default()
         },
@@ -39,30 +39,40 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
         // We also adjusted the shadow map to be larger since we're
         // only using a single cascade.
         cascade_shadow_config: CascadeShadowConfigBuilder {
-            num_cascades: 1,
-            maximum_distance: 1.6,
+            num_cascades: 5,
+            maximum_distance: 20.0,
             ..default()
         }
         .into(),
         ..default()
     });
+
     commands.spawn(SceneBundle {
         scene: asset_server
             .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
         ..default()
     });
+
+    for i in 1..=10 {
+        commands.spawn(SceneBundle {
+            scene: asset_server
+                .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf")),
+            transform: Transform::from_xyz(i as f32 * 0.5, 0.0, i as f32 * -2.0),
+            ..default()
+        });
+    }
 }
 
 fn animate_light_direction(
     time: Res<Time>,
     mut query: Query<&mut Transform, With<DirectionalLight>>,
 ) {
-    for mut transform in &mut query {
-        transform.rotation = Quat::from_euler(
-            EulerRot::ZYX,
-            0.0,
-            time.elapsed_seconds() * PI / 5.0,
-            -FRAC_PI_4,
-        );
-    }
+    // for mut transform in &mut query {
+    //     transform.rotation = Quat::from_euler(
+    //         EulerRot::ZYX,
+    //         0.0,
+    //         time.elapsed_seconds() * PI / 5.0,
+    //         -FRAC_PI_4,
+    //     );
+    // }
 }
``` 

![Screenshot2024_07_10_145737](https://github.com/bevyengine/bevy/assets/6300263/c5c71894-f9f7-45fa-9b4f-598e324b42d0)

---------

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
2024-08-02 19:19:53 +02:00
François Mockers
295ed1fdb4
fix building cargo_gltf with feature dds (#14360)
# Objective

- Building bevy_gltf with feature dds fails:
```
> cargo build -p bevy_gltf --features dds
   Compiling bevy_core_pipeline v0.15.0-dev (crates/bevy_core_pipeline)
error[E0061]: this function takes 7 arguments but 6 arguments were supplied
   --> crates/bevy_core_pipeline/src/tonemapping/mod.rs:442:5
    |
442 |     Image::from_buffer(
    |     ^^^^^^^^^^^^^^^^^^
...
445 |         bytes,
    |         ----- an argument of type `std::string::String` is missing
    |
note: associated function defined here
   --> crates/bevy_render/src/texture/image.rs:709:12
    |
709 |     pub fn from_buffer(
    |            ^^^^^^^^^^^
help: provide the argument
    |
442 |     Image::from_buffer(/* std::string::String */, bytes, image_type, CompressedImageFormats::NONE, false, image_sampler, RenderAssetUsages::RENDER_WORLD)
    |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For more information about this error, try `rustc --explain E0061`.
error: could not compile `bevy_core_pipeline` (lib) due to 1 previous error
```
- If you're fixing a specific issue, say "Fixes #X".

## Solution

- enable dds feature in bevy_core_pipeline

## Testing

- `cargo build -p bevy_gltf --features dds`
2024-08-02 19:19:24 +02:00
Sludge
420ca6c43c
Make Viewport::default() return a 1x1 viewport (#14372)
# Objective

- The current default viewport crashes bevy due to a wgpu validation
error, this PR fixes that
- Fixes https://github.com/bevyengine/bevy/issues/14355

## Solution

- `Viewport::default()` now returns a 1x1 viewport

## Testing

- I modified the `3d_viewport_to_world` example to use
`Viewport::default()`, and it works as expected (only the top-left pixel
is rendered)
2024-08-02 19:19:14 +02:00
Lura
42412f3500
Fix error/typo in SMAA shader (#14338)
# Objective

- Actually use the value assigned to `d_xz`, like in [the original SMAA
implementation](https://github.com/iryoku/smaa/blob/master/SMAA.hlsl#L960).
This not already being the case was likely a mistake when converting
from HLSL to WGSL

## Solution

- Use `d_xz.x` and `d_xz.y` instead of `d.x` and `d.z`

## Testing

- Quickly tested on Windows 11, `x86_64-pc-windows-gnu` `1.79.0` with
the latest NVIDIA drivers. App runs with SMAA enabled and everything
seems to work as intended
- I didn't observe any major visual difference between this and the
previous version, though this should be more correct as it matches the
original SMAA implementation
2024-08-02 19:19:14 +02:00
BD103
70a0c211ff
Fix bevy_window failing with serialize feature (#14298)
# Objective

- [`flag-frenzy`](https://github.com/TheBevyFlock/flag-frenzy) found an
issue where `bevy_window` would fail to build when its `serialize`
feature is enabled.
- See
[here](https://github.com/TheBevyFlock/flag-frenzy/actions/runs/9924187577/job/27415224405)
for the specific log.

## Solution

- Turns out it was failing because the `bevy_ecs/serialize` feature was
not enabled. This error can be fixed by adding the flag as a dependency.

## Testing

```bash
cargo check -p bevy_window -F serialize
# Or if you're very cool...
flag-frenzy --manifest-path path/to/bevy/Cargo.toml --config config -p bevy_window
```
2024-08-02 19:18:25 +02:00
Alix Bott
728c5b98d4
Fix overflow in RenderLayers::iter_layers (#14264)
# Objective

- Fixes overflow when calling `RenderLayers::iter_layers` on layers of
the form `k * 64 - 1`
- Causes a panic in debug mode, and an infinite iterator in release mode

## Solution

- Use `u64::checked_shr` instead of `>>=`

## Testing

- Added a test case for this: `render_layer_iter_no_overflow`
2024-08-02 19:17:57 +02:00
MiniaczQ
524fb01457
Make initial StateTransition run before PreStartup (#14208)
# Objective

- Fixes #14206 

## Solution

- Run initial `StateTransition` as a startup schedule before
`PreStartup`, instead of running it inside `Startup` as an exclusive
system.

Related discord discussion:

https://discord.com/channels/691052431525675048/692572690833473578/1259543775668207678

## Testing

Reproduction now works correctly:

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

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
enum AppState {
    #[default]
    Menu,
    InGame,
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_state::<AppState>()
        .add_systems(Startup, setup)
        .add_systems(OnEnter(AppState::Menu), enter_menu_state)
        .run();
}

fn setup(mut next_state: ResMut<NextState<AppState>>) {
    next_state.set(AppState::Menu);
}

fn enter_menu_state() {
    println!("Entered menu state");
}
```


![image](https://github.com/bevyengine/bevy/assets/13040204/96d7a533-c439-4c0b-8f15-49f620903ce1)


---

## Changelog

- Initial `StateTransition` runs before `PreStartup` instead of inside
`Startup`.
2024-08-02 19:17:57 +02:00
Periwink
7f3fea9a5b
Allow observer systems to have outputs (#14159)
Fixes https://github.com/bevyengine/bevy/issues/14157

- Update the ObserverSystem traits to accept an `Out` parameter

- Added a test where an observer system has a non-empty output which is
piped into another system

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-08-02 19:16:46 +02:00
Matty
d0583c8b54
Fix swapped docs for Rot2::rotation_to/from_y (#14307)
# Objective

Fixes #14301 

## Solution

Swap them so that they are no longer swapped.
2024-08-02 19:15:56 +02:00
MiniaczQ
4bd56b6da1
Dirty fix for App hanging when windows are invisible on WindowsOS (#14155)
# Objective

- Fixes #14135 

## Solution

- If no windows are visible, app updates will run regardless of redraw
call result.

This a relatively dirty fix, a more robust solution is desired in the
long run:
https://github.com/bevyengine/bevy/issues/1343#issuecomment-770091684

https://discord.com/channels/691052431525675048/1253771396832821270/1258805997011730472
The solution would disconnect rendering from app updates.

## Testing

- `window_settings` now works

## Other platforms

Not a problem on Linux:
https://discord.com/channels/691052431525675048/692572690833473578/1259526650622640160
Not a problem on MacOS:
https://discord.com/channels/691052431525675048/692572690833473578/1259563986148659272
2024-08-02 19:12:05 +02:00
IQuick 143
0e1858bc4f
fix: Possible NaN due to denormalised quaternions in AABB implementations for round shapes. (#14240)
# Objective

With an unlucky denormalised quaternion (or just a regular very
denormalised quaternion), it's possible to obtain NaN values for AABB's
in shapes which rely on an AABB for a disk.

## Solution

Add an additional `.max(Vec3::ZERO)` clamp to get rid of negative values
arising due to numerical errors.
Fixup some unnecessary calculations and improve variable names in
relevant code, aiming for consistency.

## Discussion

These two (nontrivial) lines of code are repeated at least 5 times,
maybe they could be their own method.
2024-08-02 19:12:04 +02:00
Torstein Grindvik
1bc5ecda9b
bevy_input: allow use without bevy_reflect (#14167)
Allow use of `bevy_input` types without needing `bevy_reflect`.

Make `bevy_reflect` within `bevy_input` optional. It's compiled in by
default.
Turn on reflect in dependencies as well when this feature is on.

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

I did a `cargo hack -p bevy_input --each-feature build`.

Signed-off-by: Torstein Grindvik <torstein.grindvik@muybridge.com>
Co-authored-by: Torstein Grindvik <torstein.grindvik@muybridge.com>
2024-08-02 19:11:13 +02:00
Litttle_fish
5d9e44b9dc
disable gpu preprocessing on android with Adreno 730 GPU and earilier (#14176)
# Objective

Fix #14146 

## Solution

Expansion of #13323 , excluded Adreno 730 and earlier.

## Testing

Tested on android device(Adreno 730) that used to crash
2024-08-02 18:58:31 +02:00
Jan Hohenheim
e941264b6f
Optimize unnecessary normalizations for Transform::local_{xyz} (#14171)
Note that `GlobalTransform` already does it like this for `right`,
`left`, etc. so I didn't have to touch that one
2024-08-02 18:58:31 +02:00
Mike
7ed1f6a9b6
use Display for entity id in log_components (#14164)
# Objective

- Cleanup a doubled `Entity` in log components

```
// Before
2024-07-05T19:54:09.082773Z  INFO bevy_ecs::system::commands: Entity Entity { index: 2, generation: 1 }: ["bevy_transform::components::transform::Transform"]

// After
2024-07-05T19:54:09.082773Z  INFO bevy_ecs::system::commands: Entity 2v1: ["bevy_transform::components::transform::Transform"]
```

---------

Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
2024-08-02 18:58:30 +02:00
Mike
c6b80c5664
add entity to error message (#14163)
# Objective

- There was a new warning added about having an unstyled child in the ui
hierarchy. Debugging the new error is pretty hard without any info about
which entity is.

## Solution

- Add the entity id to the warning.

```text
// Before
2024-07-05T19:40:59.904014Z  WARN bevy_ui::layout::ui_surface: Unstyled child in a UI entity hierarchy. You are using an entity without UI components as a child of an entity with UI components, results may be unexpected.

//After
2024-07-05T19:40:59.904014Z  WARN bevy_ui::layout::ui_surface: Unstyled child `3v1` in a UI entity hierarchy. You are using an entity without UI components as a child of an entity with UI components, results may be unexpected.
```

## Changelog

- add entity id to ui surface warning
2024-08-02 18:58:30 +02:00
Brandon Reinhart
4275669b07
impl Reflect + Clone for StateScoped (#14156)
# Objective

- Expand the flexibilty of StateScoped by adding Reflect and Clone
- This lets StateScoped be used in Clone Bundles, for example

```rust
#[derive(Component, Reflect, Clone)]
pub struct StateScoped<S: States>(pub S);
```

Notes:
- States are already Clone.
- Type registration is up to the user, but this is commonly the case
with reflected generic types.

## Testing

- Ran the examples.
2024-08-02 18:58:30 +02:00
François Mockers
a6fde1059c
EmptyPathStream is only used in android/wasm32 (#14200)
# Objective

- `EmptyPathStream` is only used in android and wasm32
- This now makes rust nightly warn

## Solution

- flag the struct to only be present when needed
- also change how `MorphTargetNames` is used because that makes rust
happier?
2024-08-02 18:58:30 +02:00
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
1812 changed files with 74441 additions and 229449 deletions

View File

@ -7,9 +7,9 @@
#
# ## LLD
#
# LLD is a linker from the LLVM project that supports Linux, Windows, macOS, and Wasm. It has the greatest
# LLD is a linker from the LLVM project that supports Linux, Windows, MacOS, and WASM. It has the greatest
# platform support and the easiest installation process. It is enabled by default in this file for Linux
# and Windows. On macOS, the default linker yields higher performance than LLD and is used instead.
# and Windows. On MacOS, the default linker yields higher performance than LLD and is used instead.
#
# To install, please scroll to the corresponding table for your target (eg. `[target.x86_64-pc-windows-msvc]`
# for Windows) and follow the steps under `LLD linker`.
@ -21,18 +21,18 @@
# Mold is a newer linker written by one of the authors of LLD. It boasts even greater performance, specifically
# through its high parallelism, though it only supports Linux.
#
# Mold is disabled by default in this file. If you wish to enable it, follow the installation instructions for
# Mold is disabled by default in this file. If you wish to enable it, follow the installation instructions for
# your corresponding target, disable LLD by commenting out its `-Clink-arg=...` line, and enable Mold by
# *uncommenting* its `-Clink-arg=...` line.
#
# There is a fork of Mold named Sold that supports macOS, but it is unmaintained and is about the same speed as
# There is a fork of Mold named Sold that supports MacOS, but it is unmaintained and is about the same speed as
# the default ld64 linker. For this reason, it is not included in this file.
#
# For more information, please see Mold's repository at <https://github.com/rui314/mold>.
#
# # Nightly configuration
#
# Be warned that the following features require nightly Rust, which is experimental and may contain bugs. If you
# Be warned that the following features require nightly Rust, which is expiremental and may contain bugs. If you
# are having issues, skip this section and use stable Rust instead.
#
# There are a few unstable features that can improve performance. To use them, first install nightly Rust
@ -51,7 +51,7 @@
# crates to share monomorphized generic code, so they do not duplicate work.
#
# In other words, instead of crate 1 generating `Foo<String>` and crate 2 generating `Foo<String>` separately,
# only one crate generates `Foo<String>` and the other adds on to the pre-existing work.
# only one crate generates `Foo<String>` and the other adds on to the pre-exiting work.
#
# Note that you may have some issues with this flag on Windows. If compiling fails due to the 65k symbol limit,
# you may have to disable this setting. For more information and possible solutions to this error, see
@ -83,21 +83,12 @@ rustflags = [
# - Ubuntu: `sudo apt-get install mold clang`
# - Fedora: `sudo dnf install mold clang`
# - Arch: `sudo pacman -S mold clang`
# "-Clink-arg=-fuse-ld=mold",
# "-Clink-arg=-fuse-ld=/usr/bin/mold",
# Nightly
# "-Zshare-generics=y",
# "-Zthreads=0",
]
# Some systems may experience linker performance issues when running doc tests.
# See https://github.com/bevyengine/bevy/issues/12207 for details.
rustdocflags = [
# LLD linker
"-Clink-arg=-fuse-ld=lld",
# Mold linker
# "-Clink-arg=-fuse-ld=mold",
]
[target.x86_64-apple-darwin]
rustflags = [
@ -143,19 +134,18 @@ rustflags = [
# rustup component add llvm-tools
# ```
linker = "rust-lld.exe"
rustdocflags = ["-Clinker=rust-lld.exe"]
rustflags = [
# Nightly
# "-Zshare-generics=n", # This needs to be off if you use dynamic linking on Windows.
# "-Zshare-generics=y",
# "-Zthreads=0",
]
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'.
# In most cases the gains are negligible, but if you are on macOS and have slow compile times you should see significant gains.
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
# [profile.dev]
# debug = 1
# This enables you to run the CI tool using `cargo ci`.
# This is enables you to run the CI tool using `cargo ci`.
# This is not enabled by default, you need to copy this file to `config.toml`.
[alias]
ci = "run --package ci --"

View File

@ -10,4 +10,4 @@ assignees: ''
Provide a link to the documentation and describe how it could be improved. In what ways is it incomplete, incorrect, or misleading?
If you have suggestions on exactly what the new docs should say, feel free to include them here. Alternatively, make the changes yourself and [create a pull request](https://bevyengine.org/learn/contribute/helping-out/writing-docs/) instead.
If you have suggestions on exactly what the new docs should say, feel free to include them here. Or alternatively, make the changes yourself and [create a pull request](https://bevyengine.org/learn/book/contributing/code/) instead.

View File

@ -2,7 +2,7 @@
name: Feature Request
about: Propose a new feature!
title: ''
labels: C-Feature, S-Needs-Triage
labels: C-Enhancement, S-Needs-Triage
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Performance Regression
about: Bevy running slowly after upgrading? Report a performance regression.
title: ''
labels: C-Bug, C-Performance, P-Regression, S-Needs-Triage
labels: C-Bug, C-Performance, C-Regression, S-Needs-Triage
assignees: ''
---

View File

@ -12,7 +12,7 @@
# repository before you can use this action.
#
# This action will only install dependencies when the current operating system is Linux. It will do
# nothing on any other OS (macOS, Windows).
# nothing on any other OS (MacOS, Windows).
name: Install Linux dependencies
description: Installs the dependencies necessary to build Bevy on Linux.
@ -20,19 +20,19 @@ inputs:
alsa:
description: Install alsa (libasound2-dev)
required: false
default: "true"
default: true
udev:
description: Install udev (libudev-dev)
required: false
default: "true"
default: true
wayland:
description: Install Wayland (libwayland-dev)
required: false
default: "false"
default: false
xkb:
description: Install xkb (libxkbcommon-dev)
required: false
default: "false"
default: false
runs:
using: composite
steps:

View File

@ -0,0 +1,39 @@
# Style guide: Engine
## Contributing
For more advice on contributing to the engine, see the [relevant section](../../CONTRIBUTING.md#Contributing-code) of `CONTRIBUTING.md`.
## General guidelines
1. Prefer granular imports over glob imports like `bevy_ecs::prelude::*`.
2. Use a consistent comment style:
1. `///` doc comments belong above `#[derive(Trait)]` invocations.
2. `//` comments should generally go above the line in question, rather than in-line.
3. Avoid `/* */` block comments, even when writing long comments.
4. Use \`variable_name\` code blocks in comments to signify that you're referring to specific types and variables.
5. Start comments with capital letters. End them with a period if they are sentence-like.
3. Use comments to organize long and complex stretches of code that can't sensibly be refactored into separate functions.
4. When using [Bevy error codes](https://bevyengine.org/learn/errors/) include a link to the relevant error on the Bevy website in the returned error message `... See: https://bevyengine.org/learn/errors/#b0003`.
## Rust API guidelines
As a reference for our API development we are using the [Rust API guidelines][Rust API guidelines]. Generally, these should be followed, except for the following areas of disagreement:
### Areas of disagreements
Some areas mentioned in the [Rust API guidelines][Rust API guidelines] we do not agree with. These areas will be expanded whenever we find something else we do not agree with, so be sure to check these from time to time.
> All items have a rustdoc example
- This guideline is too strong and not applicable for everything inside of the Bevy game engine. For functionality that requires more context or needs a more interactive demonstration (such as rendering or input features), make use of the `examples` folder instead.
> Examples use ?, not try!, not unwrap
- This guideline is usually reasonable, but not always required.
> Only smart pointers implement Deref and DerefMut
- Generally a good rule of thumb, but we're probably going to deliberately violate this for single-element wrapper types like `Life(u32)`. The behavior is still predictable and it significantly improves ergonomics / new user comprehension.
[Rust API guidelines]: https://rust-lang.github.io/api-guidelines/about.html

View File

@ -0,0 +1,64 @@
# Style guide: Examples
For more advice on writing examples, see the [relevant section](../../CONTRIBUTING.md#writing-examples) of CONTRIBUTING.md.
## Organization
1. Examples should live in an appropriate subfolder of `/examples`.
2. Examples should be a single file if possible.
3. Assets live in `./assets`. Try to avoid adding new assets unless strictly necessary to keep the repo small. Don't add "large" asset files.
4. Each example should try to follow this order:
1. Imports
2. A `fn main()` block
3. Example logic
5. Try to structure app / plugin construction in the same fashion as the actual code.
6. Examples should typically not have tests, as they are not directly reusable by the Bevy user.
## Stylistic preferences
1. Use simple, descriptive variable names.
1. Avoid names like `MyComponent` in favor of more descriptive terms like `Events`.
2. Prefer single letter differentiators like `EventsA` and `EventsB` to nonsense words like `EventsFoo` and `EventsBar`.
3. Avoid repeating the type of variables in their name where possible. For example, `Color` should be preferred to `ColorComponent`.
2. Prefer glob imports of `bevy::prelude::*` and `bevy::sub_crate::*` over granular imports (for terseness).
3. Use a consistent comment style:
1. `///` doc comments belong above `#[derive(Trait)]` invocations.
2. `//` comments should generally go above the line in question, rather than in-line.
3. Avoid `/* */` block comments, even when writing long comments.
4. Use \`variable_name\` code blocks in comments to signify that you're referring to specific types and variables.
5. Start comments with capital letters; end them with a period if they are sentence-like.
4. Use comments to organize long and complex stretches of code that can't sensibly be refactored into separate functions.
5. Avoid making variables `pub` unless it is needed for your example.
## Code conventions
1. Refactor configurable values ("magic numbers") out into constants with clear names.
2. Prefer `for` loops over `.for_each`. The latter is faster (for now), but it is less clear for beginners, less idiomatic, and less flexible.
3. Use `.single` and `.single_mut` where appropriate.
4. In Queries, prefer `With<T>` filters over actually fetching unused data with `&T`.
5. Prefer disjoint queries using `With` and `Without` over param sets when you need more than one query in a single system.
6. Prefer structs with named fields over tuple structs except in the case of single-field wrapper types.
7. Use enum-labels over string-labels for app / schedule / etc. labels.
## "Feature" examples
These examples demonstrate the usage of specific engine features in clear, minimal ways.
1. Focus on demonstrating exactly one feature in an example
2. Try to keep your names divorced from the context of a specific game, and focused on the feature you are demonstrating.
3. Where they exist, show good alternative approaches to accomplish the same task and explain why you may prefer one over the other.
4. Examples should have a visible effect when run, either in the command line or a graphical window.
## "Game" examples
These examples show how to build simple games in Bevy in a cohesive way.
1. Each of these examples lives in the [/examples/games] folder.
2. Aim for minimum but viable status: the game should be playable and not obviously buggy but does not need to be polished, featureful, or terribly fun.
3. Focus on code quality and demonstrating good, extensible patterns for users.
1. Make good use of enums and states to organize your game logic.
2. Keep components as small as possible but no smaller: all of the data on a component should generally be accessed at once.
3. Keep systems small: they should have a clear single purpose.
4. Avoid duplicating logic across similar entities whenever possible by sharing systems and components.
4. Use `///` doc comments to explain what each function / struct does as if the example were part of a polished production codebase.
5. Arrange your code into modules within the same file to allow for simple code folding / organization.

View File

@ -1,4 +1,5 @@
(
events: [
(300, AppExit),
]
)

View File

@ -1,2 +0,0 @@
(
)

9
.github/example-run/breakout.ron vendored Normal file
View File

@ -0,0 +1,9 @@
(
setup: (
fixed_frame_time: Some(0.03),
),
events: [
(200, Screenshot),
(900, AppExit),
]
)

View File

@ -1,4 +1,5 @@
(
events: [
(900, AppExit),
]
)

9
.github/example-run/load_gltf.ron vendored Normal file
View File

@ -0,0 +1,9 @@
(
setup: (
frame_time: Some(0.03),
),
events: [
(100, Screenshot),
(300, AppExit),
]
)

View File

@ -1,4 +0,0 @@
(
events: [
]
)

View File

@ -1,9 +1,3 @@
{
"MD013": false,
"no-inline-html": {
"allowed_elements": [
"details",
"summary"
]
}
"MD013": false
}

View File

@ -16,26 +16,14 @@
---
## Showcase
## Changelog
> This section is optional. If this PR does not include a visual change or does not add a new feature, you can delete this section.
> This section is optional. If this was a trivial fix, or has no externally-visible impact, you can delete this section.
- Help others understand the result of this PR by showcasing your awesome work!
- If this PR adds a new feature or public API, consider adding a brief pseudo-code snippet of it in action
- If this PR includes a visual change, consider adding a screenshot, GIF, or video
- If you want, you could even include a before/after comparison!
- If the Migration Guide adequately covers the changes, you can delete this section
While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases:
<details>
<summary>Click to view showcase</summary>
```rust
println!("My super cool code.");
```
</details>
- What changed as a result of this PR?
- If applicable, organize changes under "Added", "Changed", or "Fixed" sub-headings
- Stick to one or two sentences. If more detail is needed for a particular change, consider adding it to the "Solution" section
- If you can't summarize the work, your change may be unreasonably large / unrelated. Consider splitting your PR to make it easier to review and merge!
## Migration Guide

View File

@ -8,9 +8,9 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.49.1"
"@playwright/test": "^1.28.1"
},
"dependencies": {
"dotenv": "^16.0.1"
}
}
}

View File

@ -6,7 +6,7 @@ test.beforeEach(async ({ page }) => {
const MAX_TIMEOUT_FOR_TEST = 300_000;
test.describe('Wasm example', () => {
test.describe('WASM example', () => {
test('Wait for success', async ({ page }, testInfo) => {
let start = new Date().getTime();

View File

@ -14,7 +14,7 @@ permissions:
jobs:
comment-on-breaking-change-label:
runs-on: ubuntu-latest
if: github.event.label.name == 'M-Needs-Migration-Guide' && !contains(github.event.pull_request.body, '## Migration Guide')
if: github.event.label.name == 'C-Breaking-Change' && !contains(github.event.pull_request.body, '## Migration Guide')
steps:
- uses: actions/github-script@v7
with:

View File

@ -30,7 +30,7 @@ jobs:
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "missing-examples"
@ -48,21 +48,8 @@ jobs:
return "true"
- run: unzip missing-examples.zip
if: ${{ steps.find-artifact.outputs.result == 'true' }}
- name: "Check if last comment is already from actions"
- name: 'Comment on PR'
if: ${{ steps.find-artifact.outputs.result == 'true' }}
id: check-last-comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR=`cat ./NR`
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot]' ]]
then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
- name: "Comment on PR"
if: ${{ steps.find-artifact.outputs.result == 'true' && steps.check-last-comment.outputs.result == 'false' }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@ -101,7 +88,7 @@ jobs:
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "missing-features"
@ -119,21 +106,8 @@ jobs:
return "true"
- run: unzip missing-features.zip
if: ${{ steps.find-artifact.outputs.result == 'true' }}
- name: "Check if last comment is already from actions"
- name: 'Comment on PR'
if: ${{ steps.find-artifact.outputs.result == 'true' }}
id: check-last-comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR=`cat ./NR`
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot]' ]]
then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
- name: "Comment on PR"
if: ${{ steps.find-artifact.outputs.result == 'true' && steps.check-last-comment.outputs.result == 'false' }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@ -172,7 +146,7 @@ jobs:
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{ github.event.workflow_run.id }},
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "msrv"
@ -190,21 +164,8 @@ jobs:
return "true"
- run: unzip msrv.zip
if: ${{ steps.find-artifact.outputs.result == 'true' }}
- name: "Check if last comment is already from actions"
- name: 'Comment on PR'
if: ${{ steps.find-artifact.outputs.result == 'true' }}
id: check-last-comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR=`cat ./NR`
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot]' ]]
then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
- name: "Comment on PR"
if: ${{ steps.find-artifact.outputs.result == 'true' && steps.check-last-comment.outputs.result == 'false' }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -5,16 +5,13 @@ on:
pull_request:
push:
branches:
- main
- release-*
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_PROFILE_TEST_DEBUG: 0
CARGO_PROFILE_DEV_DEBUG: 0
# If nightly is breaking CI, modify this variable to target a specific nightly version.
NIGHTLY_TOOLCHAIN: nightly
RUSTFLAGS: "-D warnings"
concurrency:
group: ${{github.workflow}}-${{github.ref}}
@ -45,6 +42,7 @@ jobs:
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- test
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0 -D warnings"
ci:
@ -74,7 +72,8 @@ jobs:
run: cargo run -p ci -- lints
miri:
runs-on: macos-latest
# Explicity use MacOS 14 to take advantage of M1 chip.
runs-on: macos-14
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@ -93,15 +92,16 @@ jobs:
components: miri
- name: CI job
# To run the tests one item at a time for troubleshooting, use
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-permissive-provenance -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
run: cargo miri test -p bevy_ecs
env:
# -Zrandomize-layout makes sure we dont rely on the layout of anything that might change
RUSTFLAGS: -Zrandomize-layout
# https://github.com/rust-lang/miri#miri--z-flags-and-environment-variables
# -Zmiri-disable-isolation is needed because our executor uses `fastrand` which accesses system time.
# -Zmiri-permissive-provenance disables warnings against int2ptr casts (since those are used by once_cell)
# -Zmiri-ignore-leaks is necessary because a bunch of tests don't join all threads before finishing.
MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation
MIRIFLAGS: -Zmiri-ignore-leaks -Zmiri-disable-isolation -Zmiri-permissive-provenance
check-compiles:
runs-on: ubuntu-latest
@ -128,79 +128,6 @@ jobs:
- name: Check Compile
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- compile
check-compiles-no-std:
runs-on: ubuntu-latest
timeout-minutes: 30
needs: ci
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
crates/bevy_ecs_compile_fail_tests/target/
crates/bevy_reflect_compile_fail_tests/target/
key: ${{ runner.os }}-cargo-check-compiles-no-std-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-none
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
- name: Check Compile
run: cargo check -p bevy --no-default-features --features default_no_std --target x86_64-unknown-none
check-compiles-no-std-portable-atomic:
runs-on: ubuntu-latest
timeout-minutes: 30
needs: ci
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
crates/bevy_ecs_compile_fail_tests/target/
crates/bevy_reflect_compile_fail_tests/target/
key: ${{ runner.os }}-cargo-check-compiles-no-std-portable-atomic-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: thumbv6m-none-eabi
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
- name: Check Compile
run: cargo check -p bevy --no-default-features --features default_no_std --target thumbv6m-none-eabi
check-compiles-no-std-examples:
runs-on: ubuntu-latest
timeout-minutes: 30
needs: ci
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
crates/bevy_ecs_compile_fail_tests/target/
crates/bevy_reflect_compile_fail_tests/target/
key: ${{ runner.os }}-cargo-check-compiles-no-std-examples-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-none
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
- name: Check Compile
run: cd examples/no_std/library && cargo check --no-default-features --features libm,critical-section --target x86_64-unknown-none
build-wasm:
runs-on: ubuntu-latest
@ -246,7 +173,7 @@ jobs:
- name: Check wasm
run: cargo check --target wasm32-unknown-unknown -Z build-std=std,panic_abort
env:
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory -D warnings"
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory"
markdownlint:
runs-on: ubuntu-latest
@ -259,7 +186,7 @@ jobs:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Run Markdown Lint
uses: super-linter/super-linter/slim@v7.3.0
uses: docker://ghcr.io/github/super-linter:slim-v4
env:
MULTI_STATUS: false
VALIDATE_ALL_CODEBASE: false
@ -280,10 +207,10 @@ jobs:
- name: Taplo info
if: failure()
run: |
echo 'To fix toml fmt, please run `taplo fmt`.'
echo 'To check for a diff, run `taplo fmt --check --diff`.'
echo 'To fix toml fmt, please run `taplo fmt`'
echo 'To check for a diff, run `taplo fmt --check --diff'
echo 'You can find taplo here: https://taplo.tamasfe.dev/'
echo 'Or if you use VSCode, use the `Even Better Toml` extension.'
echo 'Or if you use VSCode, use the `Even Better Toml` extension with 2 spaces'
echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml'
typos:
@ -292,7 +219,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.30.2
uses: crate-ci/typos@v1.21.0
- name: Typos info
if: failure()
run: |
@ -302,6 +229,52 @@ jobs:
echo 'if you use VSCode, you can also install `Typos Spell Checker'
echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode'
run-examples-macos-metal:
# Explicity use MacOS 14 to take advantage of M1 chip.
runs-on: macos-14
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Disable audio
# Disable audio through a patch. on github m1 runners, audio timeouts after 15 minutes
run: git apply --ignore-whitespace tools/example-showcase/disable-audio.patch
- name: Build bevy
# this uses the same command as when running the example to ensure build is reused
run: |
TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome"
- name: Run examples
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
mv screenshot-*.png screenshots-$example_name/
fi
done
mkdir traces && mv trace*.json traces/
mkdir screenshots && mv screenshots-* screenshots/
- name: save traces
uses: actions/upload-artifact@v4
with:
name: example-traces-macos
path: traces
- name: save screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots-macos
path: screenshots
- uses: actions/upload-artifact@v4
if: ${{ failure() && github.event_name == 'pull_request' }}
with:
name: example-run-macos
path: example-run/
check-doc:
runs-on: ubuntu-latest
timeout-minutes: 30
@ -326,7 +299,8 @@ jobs:
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- doc
env:
RUSTFLAGS: "-C debuginfo=0 -D warnings"
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
# This currently report a lot of false positives
# Enable it again once it's fixed - https://github.com/bevyengine/bevy/issues/1983
# - name: Installs cargo-deadlinks
@ -340,7 +314,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: check for missing metadata
id: missing-metadata
run: cargo run -p build-templated-pages -- check-missing examples
@ -375,7 +348,6 @@ jobs:
needs: check-missing-examples-in-docs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: check for missing features
id: missing-features
run: cargo run -p build-templated-pages -- check-missing features
@ -419,7 +391,6 @@ jobs:
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-msrv-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
- name: get MSRV
id: msrv
run: |
@ -449,19 +420,44 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Check for internal Bevy imports
- name: Check for bevy_internal imports
shell: bash
run: |
errors=""
for file in $(find examples tests -name '*.rs' -not -path 'examples/mobile/*'); do
if grep -q "use bevy_" "$file"; then
errors+="ERROR: Detected internal Bevy import in $file\n"
for file in $(find examples tests -name '*.rs'); do
if grep -q "use bevy_internal" "$file"; then
errors+="ERROR: Detected 'use bevy_internal' in $file\n"
fi
done
if [ -n "$errors" ]; then
echo -e "$errors"
echo " Avoid importing internal Bevy crates, they should not be used directly"
echo " Fix the issue by replacing 'bevy_*' with 'bevy'"
echo " Example: 'use bevy::sprite::Mesh2d;' instead of 'bevy_internal::sprite::Mesh2d;'"
echo " Avoid importing bevy_internal, it should not be used directly"
echo " Fix the issue by replacing 'bevy_internal' with 'bevy'"
echo " Example: 'use bevy::sprite::MaterialMesh2dBundle;' instead of 'bevy_internal::sprite::MaterialMesh2dBundle;'"
exit 1
fi
check-cfg:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-check-doc-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
with:
wayland: true
xkb: true
- name: Build and check cfg typos
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- cfg-check

138
.github/workflows/daily.yml vendored Normal file
View File

@ -0,0 +1,138 @@
name: Daily Jobs
on:
schedule:
- cron: '0 12 * * *'
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
build-for-iOS:
if: github.repository == 'bevyengine/bevy'
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Add iOS targets
run: rustup target add aarch64-apple-ios x86_64-apple-ios
- name: Build app for iOS
run: |
cd examples/mobile
make xcodebuild-iphone
mkdir Payload
mv build/Build/Products/Debug-iphoneos/bevy_mobile_example.app Payload
zip -r bevy_mobile_example.zip Payload
mv bevy_mobile_example.zip bevy_mobile_example.ipa
- name: Upload to Browser Stack
run: |
curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "file=@examples/mobile/bevy_mobile_example.ipa" \
-F "custom_id=$GITHUB_RUN_ID"
build-for-Android:
if: github.repository == 'bevyengine/bevy'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Add Android targets
run: rustup target add aarch64-linux-android armv7-linux-androideabi
- name: Install Cargo APK
run: cargo install --force cargo-apk
- name: Build app for Android
run: ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME cargo apk build --package bevy_mobile_example
env:
# This will reduce the APK size from 1GB to ~200MB
CARGO_PROFILE_DEV_DEBUG: false
- name: Upload to Browser Stack
run: |
curl -u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \
-F "file=@target/debug/apk/bevyexample.apk" \
-F "custom_id=$GITHUB_RUN_ID"
nonce:
if: github.repository == 'bevyengine/bevy'
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
result: ${{ steps.nonce.outputs.result }}
steps:
- id: nonce
run: echo "result=${{ github.run_id }}-$(date +%s)" >> $GITHUB_OUTPUT
run:
if: github.repository == 'bevyengine/bevy'
runs-on: ubuntu-latest
timeout-minutes: 30
needs: [nonce, build-for-iOS, build-for-Android]
env:
PERCY_PARALLEL_NONCE: ${{ needs.nonce.outputs.result }}
PERCY_PARALLEL_TOTAL: ${{ strategy.job-total }}
strategy:
matrix:
include:
- device: "iPhone 13"
os_version: "15"
- device: "iPhone 14"
os_version: "16"
- device: "iPhone 15"
os_version: "17"
- device: "Xiaomi Redmi Note 11"
os_version: "11.0"
- device: "Google Pixel 6"
os_version: "12.0"
- device: "Samsung Galaxy S23"
os_version: "13.0"
- device: "Google Pixel 8"
os_version: "14.0"
steps:
- uses: actions/checkout@v4
- name: Run Example
run: |
cd .github/start-mobile-example
npm install
npm install -g @percy/cli@latest
npx percy app:exec --parallel -- npm run mobile
env:
BROWSERSTACK_APP_ID: ${{ github.run_id }}
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
DEVICE: ${{ matrix.device }}
OS_VERSION: ${{ matrix.os_version }}
- name: Save screenshots
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: screenshots-${{ matrix.device }}-${{ matrix.os_version }}
path: .github/start-mobile-example/*.png
check-result:
if: github.repository == 'bevyengine/bevy'
runs-on: ubuntu-latest
timeout-minutes: 30
needs: [run]
steps:
- name: Wait for screenshots comparison
run: |
npm install -g @percy/cli@latest
npx percy build:wait --project dede4209/Bevy-Mobile-Example --commit ${{ github.sha }} --fail-on-changes --pass-if-approved
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}

View File

@ -58,22 +58,8 @@ jobs:
- name: Build docs
env:
# needs to be in sync with [package.metadata.docs.rs]
RUSTFLAGS: --cfg docsrs_dep
RUSTDOCFLAGS: -Zunstable-options --cfg=docsrs --generate-link-to-definition --html-after-content docs-rs/trait-tags.html
run: |
cargo doc \
-Zunstable-options \
-Zrustdoc-scrape-examples \
--all-features \
--workspace \
--no-deps \
--document-private-items \
--exclude ci \
--exclude errors \
--exclude bevy_mobile_example \
--exclude build-wasm-example \
--exclude build-templated-pages \
--exclude example-showcase
RUSTDOCFLAGS: -Zunstable-options --cfg=docsrs
run: cargo doc --all-features --no-deps -p bevy -Zunstable-options -Zrustdoc-scrape-examples
# This adds the following:
# - A top level redirect to the bevy crate documentation
@ -83,7 +69,7 @@ jobs:
run: |
echo "<meta http-equiv=\"refresh\" content=\"0; url=bevy/index.html\">" > target/doc/index.html
echo "dev-docs.bevyengine.org" > target/doc/CNAME
echo $'User-Agent: *\nDisallow: /' > target/doc/robots.txt
echo "User-Agent: *\nDisallow: /" > target/doc/robots.txt
rm target/doc/.lock
- name: Upload site artifact

View File

@ -1,120 +0,0 @@
name: Example Run - PR Comments
# This workflow has write permissions on the repo
# It must not checkout a PR and run untrusted code
# Also requesting write permissions on PR to be able to comment
permissions:
pull-requests: "write"
on:
workflow_run:
workflows: ["Example Run"]
types:
- completed
jobs:
make-macos-screenshots-available:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
branch-name: ${{ steps.branch-name.outputs.result }}
pr-number: ${{ steps.pr-number.outputs.result }}
steps:
- name: "Download artifact"
id: find-artifact
uses: actions/github-script@v7
with:
result-encoding: string
script: |
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifacts = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "screenshots-macos"
});
if (matchArtifacts.length == 0) { return "false" }
var matchArtifact = matchArtifacts[0];
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/screenshots-macos.zip', Buffer.from(download.data));
return "true"
- name: prepare artifact folder
run: |
unzip screenshots-macos.zip
mkdir screenshots
mv screenshots-* screenshots/
- name: save screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots-macos
path: screenshots
- name: branch name
id: branch-name
run: |
echo "result=PR-$(cat PR)-${{ github.event.workflow_run.head_branch }}" >> $GITHUB_OUTPUT
- name: PR number
id: pr-number
run: |
echo "result=$(cat PR)" >> $GITHUB_OUTPUT
compare-macos-screenshots:
name: Compare macOS screenshots
needs: [make-macos-screenshots-available]
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
with:
commit: ${{ github.event.workflow_run.head_sha }}
branch: ${{ needs.make-macos-screenshots-available.outputs.branch-name }}
artifact: screenshots-macos
os: macos
secrets: inherit
comment-on-pr:
name: Comment on PR
runs-on: ubuntu-latest
needs: [make-macos-screenshots-available, compare-macos-screenshots]
if: ${{ always() && needs.compare-macos-screenshots.result == 'failure' }}
steps:
- uses: actions/checkout@v4
- name: "Check if PR already has label"
id: check-label
env:
PR: ${{ needs.make-macos-screenshots-available.outputs.pr-number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ `gh api --jq '.labels.[].name' /repos/bevyengine/bevy/pulls/$PR` =~ "M-Deliberate-Rendering-Change" ]]
then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
- name: "Check if last comment is already from actions"
id: check-last-comment
env:
PR: ${{ needs.make-macos-screenshots-available.outputs.pr-number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ `gh api --jq '.[-1].user.login' /repos/bevyengine/bevy/issues/$PR/comments` == 'github-actions[bot' ]]
then
echo "result=true" >> $GITHUB_OUTPUT
else
echo "result=false" >> $GITHUB_OUTPUT
fi
- name: "Comment on PR"
if: ${{ steps.check-label.outputs.result == 'false' && steps.check-last-comment.outputs.result == 'false' }}
env:
PROJECT: B04F67C0-C054-4A6F-92EC-F599FEC2FD1D
PR: ${{ needs.make-macos-screenshots-available.outputs.pr-number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
LF=$'\n'
COMMENT_BODY="Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke! ${LF}You can review it at https://pixel-eagle.com/project/$PROJECT?filter=PR-$PR ${LF} ${LF}If it's expected, please add the M-Deliberate-Rendering-Change label. ${LF} ${LF}If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it."
gh issue comment $PR --body "$COMMENT_BODY"

View File

@ -1,187 +0,0 @@
name: Example Run
on:
merge_group:
pull_request:
# also run when pushed to main to update reference screenshots
push:
branches:
- main
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_PROFILE_TEST_DEBUG: 0
CARGO_PROFILE_DEV_DEBUG: 0
jobs:
run-examples-macos-metal:
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Disable audio
# Disable audio through a patch. on github m1 runners, audio timeouts after 15 minutes
run: git apply --ignore-whitespace tools/example-showcase/disable-audio.patch
- name: Run examples
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
mv screenshot-*.png screenshots-$example_name/
fi
done
mkdir traces && mv trace*.json traces/
mkdir screenshots && mv screenshots-* screenshots/
- name: save traces
uses: actions/upload-artifact@v4
with:
name: example-traces-macos
path: traces
- name: Save PR number
if: ${{ github.event_name == 'pull_request' }}
run: |
echo ${{ github.event.number }} > ./screenshots/PR
- name: save screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots-macos
path: screenshots
- uses: actions/upload-artifact@v4
if: ${{ failure() && github.event_name == 'pull_request' }}
with:
name: example-run-macos
path: example-run/
compare-macos-screenshots:
if: ${{ github.event_name != 'pull_request' }}
name: Compare Macos screenshots
needs: [run-examples-macos-metal]
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
with:
commit: ${{ github.sha }}
branch: ${{ github.ref_name }}
artifact: screenshots-macos
os: macos
secrets: inherit
run-examples-linux-vulkan:
if: ${{ github.event_name != 'pull_request' }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
# At some point this may be merged into `install-linux-deps`, but for now it is its own step.
- name: Install additional Linux dependencies for Vulkan
run: |
sudo add-apt-repository ppa:kisak/turtle -y
sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-run-examples-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
- name: Run examples
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
mv screenshot-*.png screenshots-$example_name/
fi
done
mkdir traces && mv trace*.json traces/
mkdir screenshots && mv screenshots-* screenshots/
- name: save traces
uses: actions/upload-artifact@v4
with:
name: example-traces-linux
path: traces
- name: save screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots-linux
path: screenshots
- uses: actions/upload-artifact@v4
if: ${{ failure() && github.event_name == 'pull_request' }}
with:
name: example-run-linux
path: example-run/
compare-linux-screenshots:
name: Compare Linux screenshots
needs: [run-examples-linux-vulkan]
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
with:
commit: ${{ github.sha }}
branch: ${{ github.ref_name }}
artifact: screenshots-linux
os: linux
secrets: inherit
run-examples-on-windows-dx12:
if: ${{ github.event_name != 'pull_request' }}
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Run examples
shell: bash
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "statically-linked-dxc,bevy_ci_testing,trace,trace_chrome"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
mv screenshot-*.png screenshots-$example_name/
fi
done
mkdir traces && mv trace*.json traces/
mkdir screenshots && mv screenshots-* screenshots/
- name: save traces
uses: actions/upload-artifact@v4
with:
name: example-traces-windows
path: traces
- name: save screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots-windows
path: screenshots
- uses: actions/upload-artifact@v4
if: ${{ failure() && github.event_name == 'pull_request' }}
with:
name: example-run-windows
path: example-run/
compare-windows-screenshots:
name: Compare Windows screenshots
needs: [run-examples-on-windows-dx12]
uses: ./.github/workflows/send-screenshots-to-pixeleagle.yml
with:
commit: ${{ github.sha }}
branch: ${{ github.ref_name }}
artifact: screenshots-windows
os: windows
secrets: inherit

View File

@ -8,12 +8,9 @@ env:
CARGO_TERM_COLOR: always
jobs:
bump:
ci:
if: github.repository == 'bevyengine/bevy'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
@ -30,9 +27,9 @@ jobs:
# Read the current version from Cargo.toml
current_version=$(cargo metadata --format-version 1 --no-deps | \
jq --raw-output '.packages | .[] | select(.name == "bevy").version')
# Sanity check: current version should be 0.X.Y-dev
if ! grep -q '^0\.[0-9]\+\.[0-9]\+-dev$' <<< "${current_version}"; then
echo "Invalid version (not in 0.X.Y-dev format): ${current_version}"
# Sanity check: current version should be 0.X.Y
if ! grep -q '^0\.[0-9]\+\.[0-9]\+$' <<< "${current_version}"; then
echo "Invalid version (not in 0.X.Y format): ${current_version}"
exit 1
fi
minor_version=$(sed 's/^0\.\([0-9]\+\).*/\1/' <<< "${current_version}")
@ -52,7 +49,7 @@ jobs:
--exclude build-wasm-example
- name: Create PR
uses: peter-evans/create-pull-request@v7
uses: peter-evans/create-pull-request@v6
with:
delete-branch: true
base: "main"

55
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Release
# how to trigger: https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow
on:
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
jobs:
ci:
if: github.repository == 'bevyengine/bevy'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install cargo-release
run: cargo install cargo-release
- name: Setup release
run: |
# Set the commit author to the github-actions bot. See discussion here for more information:
# https://github.com/actions/checkout/issues/13#issuecomment-724415212
# https://github.community/t/github-actions-bot-email-address/17204/6
git config user.name 'Bevy Auto Releaser'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
# release: remove the dev suffix, like going from 0.X.0-dev to 0.X.0
# --workspace: updating all crates in the workspace
# --no-publish: do not publish to crates.io
# --execute: not a dry run
# --no-tag: do not push tag for each new version
# --no-push: do not push the update commits
# --dependent-version upgrade: change 0.X.0-dev in internal dependencies to 0.X.0
# --exclude: ignore those packages
cargo release release \
--workspace \
--no-publish \
--execute \
--no-tag \
--no-confirm \
--no-push \
--dependent-version upgrade \
--exclude ci \
--exclude errors \
--exclude bevy_mobile_example \
--exclude build-wasm-example
- name: Create PR
uses: peter-evans/create-pull-request@v6
with:
delete-branch: true
base: "main"
title: "Preparing Next Release"
body: |
Preparing next release. This PR has been auto-generated.

View File

@ -1,109 +0,0 @@
name: Send Screenshots to Pixel Eagle
on:
workflow_call:
inputs:
artifact:
required: true
type: string
commit:
required: true
type: string
branch:
required: true
type: string
os:
required: true
type: string
env:
# Unfortunately, we can't check secrets in `if:` conditionals. However, Github's own documentation
# suggests a workaround: Putting the secret in an environment variable, and checking that instead.
PIXELEAGLE_TOKEN_EXISTS: ${{ secrets.PIXELEAGLE_TOKEN != '' }}
jobs:
send-to-pixel-eagle:
name: Send screenshots to Pixel Eagle
runs-on: ubuntu-24.04
# Pixel Eagle is irrelevant for most forks, even of those that allow workflows to run. Thus, we
# disable this job for any forks. Any forks where Pixel Eagle is relevant can comment out the
# `if:` conditional below.
if: ${{ github.repository == 'bevyengine/bevy' }}
steps:
- name: Notify user on non-existent token
if: ${{ ! fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }}
run: |
echo "The PIXELEAGLE_TOKEN secret does not exist, so uploading screenshots to Pixel Eagle was skipped." >> $GITHUB_STEP_SUMMARY
- name: Download artifact
if: ${{ fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }}
uses: actions/download-artifact@v4
with:
pattern: ${{ inputs.artifact }}
- name: Send to Pixel Eagle
if: ${{ fromJSON(env.PIXELEAGLE_TOKEN_EXISTS) }}
env:
project: B04F67C0-C054-4A6F-92EC-F599FEC2FD1D
run: |
# Create a new run with its associated metadata
metadata='{"os":"${{ inputs.os }}", "commit": "${{ inputs.commit }}", "branch": "${{ inputs.branch }}"}'
run=`curl https://pixel-eagle.com/$project/runs --json "$metadata" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} | jq '.id'`
SAVEIFS=$IFS
cd ${{ inputs.artifact }}
# Read the hashes of the screenshot for fast comparison when they are equal
IFS=$'\n'
# Build a json array of screenshots and their hashes
hashes='[';
for screenshot in $(find . -type f -name "*.png");
do
name=${screenshot:14}
echo $name
hash=`shasum -a 256 $screenshot | awk '{print $1}'`
hashes="$hashes [\"$name\",\"$hash\"],"
done
hashes=`echo $hashes | rev | cut -c 2- | rev`
hashes="$hashes]"
IFS=$SAVEIFS
# Upload screenshots with unknown hashes
curl https://pixel-eagle.com/$project/runs/$run/hashes --json "$hashes" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} | jq '.[]|[.name] | @tsv' |
while IFS=$'\t' read -r name; do
name=`echo $name | tr -d '"'`
echo "Uploading $name"
curl https://pixel-eagle.com/$project/runs/$run/screenshots -F "data=@./screenshots-$name" -F "screenshot=$name" --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }}
echo
done
IFS=$SAVEIFS
cd ..
# Trigger comparison with the main branch on the same os
curl https://pixel-eagle.com/$project/runs/$run/compare/auto --json '{"os":"<equal>", "branch": "main"}' --oauth2-bearer ${{ secrets.PIXELEAGLE_TOKEN }} > pixeleagle.json
# Log results
compared_with=`cat pixeleagle.json | jq '.to'`
status=0
missing=`cat pixeleagle.json | jq '.missing | length'`
if [ ! $missing -eq 0 ]; then
echo "There are $missing missing screenshots"
echo "::warning title=$missing missing screenshots on ${{ inputs.os }}::https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with"
status=1
fi
diff=`cat pixeleagle.json | jq '.diff | length'`
if [ ! $diff -eq 0 ]; then
echo "There are $diff screenshots with a difference"
echo "::warning title=$diff different screenshots on ${{ inputs.os }}::https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with"
status=1
fi
echo "created run $run: https://pixel-eagle.com/project/$project/run/$run/compare/$compared_with"
exit $status

View File

@ -5,6 +5,7 @@ on:
pull_request:
push:
branches:
- main
- release-*
concurrency:
@ -13,9 +14,6 @@ concurrency:
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_PROFILE_TEST_DEBUG: 0
CARGO_PROFILE_DEV_DEBUG: 0
# If nightly is breaking CI, modify this variable to target a specific nightly version.
NIGHTLY_TOOLCHAIN: nightly
@ -33,7 +31,7 @@ jobs:
with:
path: |
target
key: ${{ runner.os }}-ios-install-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-ios-install-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }}
# TODO: remove x86 target once it always run on arm GitHub runners
- name: Add iOS targets
@ -51,12 +49,6 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- uses: actions/cache@v4
with:
path: |
@ -68,16 +60,115 @@ jobs:
key: ${{ runner.os }}-cargo-build-android-${{ hashFiles('**/Cargo.toml') }}
- name: Install Android targets
run: rustup target add aarch64-linux-android
run: rustup target add aarch64-linux-android armv7-linux-androideabi
- name: Install Cargo NDK
run: cargo install --force cargo-ndk
- name: Install Cargo APK
run: cargo install --force cargo-apk
- name: Build .so file
run: cargo ndk -t arm64-v8a -o android_example/app/src/main/jniLibs build --package bevy_mobile_example
- name: Build APK
run: ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME cargo apk build --package bevy_mobile_example
- name: Build app for Android
run: cd examples/mobile/android_example && chmod +x gradlew && ./gradlew build
run-examples-linux-vulkan:
if: ${{ github.event_name == 'merge_group' }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
# At some point this may be merged into `install-linux-deps`, but for now it is its own step.
- name: Install additional Linux dependencies for Vulkan
run: |
sudo add-apt-repository ppa:kisak/turtle -y
sudo apt-get install --no-install-recommends libxkbcommon-x11-0 xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-run-examples-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
- name: Build bevy
# this uses the same command as when running the example to ensure build is reused
run: |
TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome"
- name: Run examples
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example xvfb-run cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
mv screenshot-*.png screenshots-$example_name/
fi
done
mkdir traces && mv trace*.json traces/
mkdir screenshots && mv screenshots-* screenshots/
- name: save traces
uses: actions/upload-artifact@v4
with:
name: example-traces-linux
path: traces
- name: save screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots-linux
path: screenshots
- uses: actions/upload-artifact@v4
if: ${{ failure() && github.event_name == 'pull_request' }}
with:
name: example-run-linux
path: example-run/
run-examples-on-windows-dx12:
if: ${{ github.event_name == 'merge_group' }}
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Build bevy
shell: bash
# this uses the same command as when running the example to ensure build is reused
run: |
WGPU_BACKEND=dx12 TRACE_CHROME=trace-alien_cake_addict.json CI_TESTING_CONFIG=.github/example-run/alien_cake_addict.ron cargo build --example alien_cake_addict --features "bevy_ci_testing,trace,trace_chrome"
- name: Run examples
shell: bash
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo -n $example_name > last_example_run
echo "running $example_name - "`date`
time WGPU_BACKEND=dx12 TRACE_CHROME=trace-$example_name.json CI_TESTING_CONFIG=$example cargo run --example $example_name --features "bevy_ci_testing,trace,trace_chrome"
sleep 10
if [ `find ./ -maxdepth 1 -name 'screenshot-*.png' -print -quit` ]; then
mkdir screenshots-$example_name
mv screenshot-*.png screenshots-$example_name/
fi
done
mkdir traces && mv trace*.json traces/
mkdir screenshots && mv screenshots-* screenshots/
- name: save traces
uses: actions/upload-artifact@v4
with:
name: example-traces-windows
path: traces
- name: save screenshots
uses: actions/upload-artifact@v4
with:
name: screenshots-windows
path: screenshots
- uses: actions/upload-artifact@v4
if: ${{ failure() && github.event_name == 'pull_request' }}
with:
name: example-run-windows
path: example-run/
run-examples-on-wasm:
if: ${{ github.event_name == 'merge_group' }}
@ -101,6 +192,13 @@ jobs:
target/
key: ${{ runner.os }}-wasm-run-examples-${{ hashFiles('**/Cargo.toml') }}
- name: install xvfb, llvmpipe and lavapipe
run: |
sudo apt-get update -y -qq
sudo add-apt-repository ppa:kisak/turtle -y
sudo apt-get update
sudo apt install -y xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
- name: Install wasm-bindgen
run: cargo install --force wasm-bindgen-cli
@ -111,9 +209,9 @@ jobs:
npx playwright install --with-deps
cd ../..
- name: First Wasm build
- name: First WASM build
run: |
cargo build --release --example testbed_ui --target wasm32-unknown-unknown
cargo build --release --example ui --target wasm32-unknown-unknown
- name: Run examples
shell: bash
@ -145,6 +243,7 @@ jobs:
- name: Build
run: cargo build -p ${{ matrix.crate }} --no-default-features
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0 -D warnings"
build-without-default-features-status:

View File

@ -9,25 +9,6 @@ on:
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
CARGO_PROFILE_TEST_DEBUG: 0
CARGO_PROFILE_DEV_DEBUG: 0
ISSUE_TITLE: Main branch fails to compile on Rust beta.
# The jobs listed here are intentionally skipped when running on forks, for a number of reasons:
#
# * Scheduled workflows run on the base/default branch, with no way (currently) to change this. On
# forks, the base/default branch is usually kept in sync with the main Bevy repository, meaning
# that running this workflow on forks would just be a waste of resources.
#
# * Even if there was a way to change the branch that a scheduled workflow runs on, forks default
# to not having an issue tracker.
#
# * Even in the event that a fork's issue tracker is enabled, most users probably don't want to
# receive automated issues in the event of a compilation failure.
#
# Because of these reasons, this workflow is irrelevant for 99% of forks. Thus, the jobs here will
# be skipped when running on any repository that isn't the main Bevy repository.
jobs:
test:
@ -35,8 +16,6 @@ jobs:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
# Disable this job when running on a fork.
if: github.repository == 'bevyengine/bevy'
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
@ -47,12 +26,11 @@ jobs:
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- test
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0 -D warnings"
lint:
runs-on: ubuntu-latest
# Disable this job when running on a fork.
if: github.repository == 'bevyengine/bevy'
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
@ -70,8 +48,6 @@ jobs:
check-compiles:
runs-on: ubuntu-latest
# Disable this job when running on a fork.
if: github.repository == 'bevyengine/bevy'
timeout-minutes: 30
needs: test
steps:
@ -83,59 +59,53 @@ jobs:
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- compile
close-any-open-issues:
check-doc:
runs-on: ubuntu-latest
needs: ['test', 'lint', 'check-compiles']
permissions:
issues: write
timeout-minutes: 30
steps:
- name: Close issues
run: |
previous_issue_number=$(gh issue list \
--search "$ISSUE_TITLE in:title" \
--json number \
--jq '.[0].number')
if [[ -n $previous_issue_number ]]; then
gh issue close $previous_issue_number \
-r completed \
-c $COMMENT
fi
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@beta
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
with:
wayland: true
xkb: true
- name: Build and check docs
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- doc
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
COMMENT: |
[Last pipeline run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) successfully completed. Closing issue.
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
open-issue:
name: Warn that weekly CI fails
runs-on: ubuntu-latest
needs: [test, lint, check-compiles]
needs: [test, lint, check-compiles, check-doc]
permissions:
issues: write
# We disable this job on forks, because
# Use always() so the job doesn't get canceled if any other jobs fail
if: ${{ github.repository == 'bevyengine/bevy' && always() && contains(needs.*.result, 'failure') }}
# Use always() so the job doesn't get canceled if any other jobs fail
if: ${{ always() && contains(needs.*.result, 'failure') }}
steps:
- name: Create issue
run: |
previous_issue_number=$(gh issue list \
--search "$ISSUE_TITLE in:title" \
--search "$TITLE in:title" \
--json number \
--jq '.[0].number')
if [[ -n $previous_issue_number ]]; then
gh issue comment $previous_issue_number \
--body "Weekly pipeline still fails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
--body "Weekly pipeline still fails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
else
gh issue create \
--title "$ISSUE_TITLE" \
--title "$TITLE" \
--label "$LABELS" \
--body "$BODY"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
TITLE: Main branch fails to compile on Rust beta.
LABELS: C-Bug,S-Needs-Triage
BODY: |
BODY: |
## Weekly CI run has failed.
[The offending run.](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

View File

@ -11,8 +11,6 @@ on:
jobs:
welcome:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/github-script@v7
with:
@ -43,5 +41,5 @@ jobs:
repo: context.repo.repo,
body: `**Welcome**, new contributor!
Please make sure you've read our [contributing guide](https://bevyengine.org/learn/contribute/introduction) and we look forward to reviewing your pull request shortly ✨`
Please make sure you've read our [contributing guide](https://github.com/bevyengine/bevy/blob/main/CONTRIBUTING.md) and we look forward to reviewing your pull request shortly ✨`
})

38
.gitignore vendored
View File

@ -1,34 +1,24 @@
# If your IDE needs additional project specific files, configure git to ignore them:
# https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer
# Rust build artifacts
target
crates/**/target
benches/**/target
tools/**/target
/target
crates/*/target
**/*.rs.bk
rustc-ice-*.txt
# DX12 wgpu backend
dxcompiler.dll
dxil.dll
# Cargo
Cargo.lock
.cargo/config
.cargo/config.toml
# Bevy Assets
assets/**/*.meta
crates/bevy_asset/imported_assets
imported_assets
# Bevy Examples
example_showcase_config.ron
example-showcase-reports/
/.idea
/.vscode
/benches/target
dxcompiler.dll
dxil.dll
# Generated by "examples/scene/scene.rs"
assets/scenes/load_scene_example-new.scn.ron
# Generated by "examples/window/screenshot.rs"
**/screenshot-*.png
assets/**/*.meta
crates/bevy_asset/imported_assets
imported_assets
example_showcase_config.ron
example-showcase-reports/

6946
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,466 @@
# Contributing to Bevy
If you'd like to help build Bevy, start by reading this
[introduction](https://bevyengine.org/learn/contribute/introduction). Thanks for contributing!
Hey, so you're interested in contributing to Bevy!
Feel free to pitch in on whatever interests you and we'll be happy to help you contribute.
Check out our community's [Code of Conduct](https://github.com/bevyengine/bevy/blob/main/CODE_OF_CONDUCT.md) and feel free to say hi on [Discord] if you'd like.
It's a nice place to chat about Bevy development, ask questions, and get to know the other contributors and users in a less formal setting.
Read on if you're looking for:
* The high-level design goals of Bevy.
* Conventions and informal practices we follow when developing Bevy.
* General advice on good open source collaboration practices.
* Concrete ways you can help us, no matter your background or skill level.
We're thrilled to have you along as we build!
## Getting oriented
Bevy, like any general-purpose game engine, is a large project!
It can be a bit overwhelming to start, so here's the bird's-eye view.
The [Bevy Engine Organization](https://github.com/bevyengine) has 4 primary repos:
1. [**`bevy`**](https://github.com/bevyengine/bevy): This is where the engine itself lives. The bulk of development work occurs here.
2. [**`bevy-website`**](https://github.com/bevyengine/bevy-website): Where the [official website](https://bevyengine.org/), release notes, Bevy Book, and Bevy Assets are hosted. It is created using the Zola static site generator.
3. [**`bevy-assets`**](https://github.com/bevyengine/bevy-assets): A collection of community-made tutorials, plugins, crates, games, and tools! Make a PR if you want to showcase your projects there!
4. [**`rfcs`**](https://github.com/bevyengine/rfcs): A place to collaboratively build and reach consensus on designs for large or controversial features.
The `bevy` repo itself contains many smaller subcrates. Most of them can be used by themselves and many of them can be modularly replaced. This enables developers to pick and choose the parts of Bevy that they want to use.
Some crates of interest:
* [**`bevy_ecs`**](./crates/bevy_ecs): The core data model for Bevy. Most Bevy features are implemented on top of it. It is also fully functional as a stand-alone ECS, which can be very valuable if you're looking to integrate it with other game engines or use it for non-game executables.
* [**`bevy_app`**](./crates/bevy_app): The api used to define Bevy Plugins and compose them together into Bevy Apps.
* [**`bevy_tasks`**](./crates/bevy_tasks): Our light-weight async executor. This drives most async and parallel code in Bevy.
* [**`bevy_render`**](./crates/bevy_render): Our core renderer API. It handles interaction with the GPU, such as the creation of Meshes, Textures, and Shaders. It also exposes a modular Render Graph for composing render pipelines. All 2D and 3D render features are implemented on top of this crate.
## What we're trying to build
Bevy is a completely free and open source game engine built in Rust. It currently has the following design goals:
* **Capable**: Offer a complete 2D and 3D feature set.
* **Simple**: Easy for newbies to pick up, but infinitely flexible for power users.
* **Data Focused**: Data-oriented architecture using the Entity Component System paradigm.
* **Modular**: Use only what you need. Replace what you don't like.
* **Fast**: App logic should run quickly, and when possible, in parallel.
* **Productive**: Changes should compile quickly ... waiting isn't fun.
Bevy also currently has the following "development process" goals:
* **Rapid experimentation over API stability**: We need the freedom to experiment and iterate in order to build the best engine we can. This will change over time as APIs prove their staying power.
* **Consistent vision**: The engine needs to feel consistent and cohesive. This takes precedence over democratic and/or decentralized processes. See our [*Bevy Organization doc*](/docs/the_bevy_organization.md) for more details.
* **Flexibility over bureaucracy**: Developers should feel productive and unencumbered by development processes.
* **Focus**: The Bevy Org should focus on building a small number of features excellently over merging every new community-contributed feature quickly. Sometimes this means pull requests will sit unmerged for a long time. This is the price of focus and we are willing to pay it. Fortunately Bevy is modular to its core. 3rd party plugins are a great way to work around this policy.
* **User-facing API ergonomics come first**: Solid user experience should receive significant focus and investment. It should rarely be compromised in the interest of internal implementation details.
* **Modularity over deep integration**: Individual crates and features should be "pluggable" whenever possible. Don't tie crates, features, or types together that don't need to be.
* **Don't merge everything ... don't merge too early**: Every feature we add increases maintenance burden and compile times. Only merge features that are "generally" useful. Don't merge major changes or new features unless we have relative consensus that the design is correct *and* that we have the developer capacity to support it. When possible, make a 3rd party Plugin / crate first, then consider merging once the API has been tested in the wild. Bevy's modular structure means that the only difference between "official engine features" and "third party plugins" is our endorsement and the repo the code lives in. We should take advantage of that whenever possible.
* **Control and consistency over 3rd party code reuse**: Only add a dependency if it is *absolutely* necessary. Every dependency we add decreases our autonomy and consistency. Dependencies also have the potential to increase compile times and risk pulling in sub-dependencies we don't want / need.
* **Don't re-invent every wheel**: As a counter to the previous point, don't re-invent everything at all costs. If there is a crate in the Rust ecosystem that is the "de-facto" standard (ex: wgpu, winit, cpal), we should heavily consider using it. Bevy should be a positive force in the ecosystem. We should drive the improvements we need into these core ecosystem crates.
* **Rust-first**: Engine and user-facing code should optimize and encourage Rust-only workflows. Adding additional languages increases internal complexity, fractures the Bevy ecosystem, and makes it harder for users to understand the engine. Never compromise a Rust interface in the interest of compatibility with other languages.
* **Thoughtful public interfaces over maximal configurability**: Symbols and apis should be private by default. Every public API should be thoughtfully and consistently designed. Don't expose unnecessary internal implementation details. Don't allow users to "shoot themselves in the foot". Favor one "happy path" api over multiple apis for different use cases.
* **Welcome new contributors**: Invest in new contributors. Help them fill knowledge and skill gaps. Don't ever gatekeep Bevy development according to notions of required skills or credentials. Help new developers find their niche.
* **Civil discourse**: We need to collectively discuss ideas and the best ideas *should* win. But conversations need to remain respectful at all times. Remember that we're all in this together. Always follow our [Code of Conduct](https://github.com/bevyengine/bevy/blob/main/CODE_OF_CONDUCT.md).
* **Test what you need to**: Write useful tests. Don't write tests that aren't useful. We *generally* aren't strict about unit testing every line of code. We don't want you to waste your time. But at the same time:
* Most new features should have at least one minimal [example](https://github.com/bevyengine/bevy/tree/main/examples). These also serve as simple integration tests, as they are run as part of our CI process.
* The more complex or "core" a feature is, the more strict we are about unit tests. Use your best judgement here. We will let you know if your pull request needs more tests. We use [Rust's built in testing framework](https://doc.rust-lang.org/book/ch11-01-writing-tests.html).
## The Bevy Organization
The Bevy Organization is the group of people responsible for stewarding the Bevy project. It handles things like merging pull requests, choosing project direction, managing bugs / issues / feature requests, running the Bevy website, controlling access to secrets, defining and enforcing best practices, etc.
Note that you *do not* need to be a member of the Bevy Organization to contribute to Bevy. Community contributors (this means you) can freely open issues, submit pull requests, and review pull requests.
Check out our dedicated [Bevy Organization document](/docs/the_bevy_organization.md) to learn more about how we're organized.
### Classifying PRs
[Labels](https://github.com/bevyengine/bevy/labels) are our primary tool to organize work.
Each label has a prefix denoting its category:
* **D:** Difficulty. In order, these are:
* `D-Trivial`: typos, obviously incorrect one-line bug fixes, code reorganization, renames
* `D-Straightforward`: simple bug fixes and API improvements, docs, test and examples
* `D-Modest`: new features, refactors, challenging bug fixes
* `D-Complex`: rewrites and unusually complex features
* When applied to an issue, these labels reflect the estimated level of expertise (not time) required to fix the issue.
* When applied to a PR, these labels reflect the estimated level of expertise required to *review* the PR.
* The `D-Domain-Expert` and `D-Domain-Agnostic` labels are modifiers, which describe if unusually high or low degrees of domain-specific knowledge are required.
* The `D-Unsafe` label is applied to any code that touches `unsafe` Rust, which requires special skills and scrutiny.
* **X:** Controversiality. In order, these are:
* `X-Uncontroversial`: everyone should agree that this is a good idea
* `X-Contentious`: there's real design thought needed to ensure that this is the right path forward
* `X-Controversial`: there's active disagreement and/or large-scale architectural implications involved
* `X-Blessed`: work that was controversial, but whose controversial (but perhaps not technical) elements have been endorsed by the relevant decision makers.
* **A:** Area (e.g. A-Animation, A-ECS, A-Rendering, ...).
* **C:** Category (e.g. C-Breaking-Change, C-Code-Quality, C-Docs, ...).
* **O:** Operating System (e.g. O-Linux, O-Web, O-Windows, ...).
* **P:** Priority (e.g. P-Critical, P-High, ...)
* Most work is not explicitly categorized by priority: volunteer work mostly occurs on an ad hoc basis depending on contributor interests
* **S:** Status (e.g. S-Blocked, S-Needs-Review, S-Needs-Design, ...).
The rules for how PRs get merged depend on their classification by controversy and difficulty.
More difficult PRs will require more careful review from experts,
while more controversial PRs will require rewrites to reduce the costs involved and/or sign-off from Subject Matter Experts and Maintainers.
When making PRs, try to split out more controversial changes from less controversial ones, in order to make your work easier to review and merge.
It is also a good idea to try and split out simple changes from more complex changes if it is not helpful for them to be reviewed together.
Some things that are reason to apply the [`S-Controversial`] label to a PR:
1. Changes to a project-wide workflow or style.
2. New architecture for a large feature.
3. Serious tradeoffs were made.
4. Heavy user impact.
5. New ways for users to make mistakes (footguns).
6. Adding a dependency.
7. Touching licensing information (due to level of precision required).
8. Adding root-level files (due to the high level of visibility).
Some things that are reason to apply the [`D-Complex`] label to a PR:
1. Introduction or modification of soundness relevant code (for example `unsafe` code).
2. High levels of technical complexity.
3. Large-scale code reorganization.
Examples of PRs that are not [`S-Controversial`] or [`D-Complex`]:
* Fixing dead links.
* Removing dead code or unused dependencies.
* Typo and grammar fixes.
* [Add `Mut::reborrow`](https://github.com/bevyengine/bevy/pull/7114).
* [Add `Res::clone`](https://github.com/bevyengine/bevy/pull/4109).
Examples of PRs that are [`S-Controversial`] but not [`D-Complex`]:
* [Implement and require `#[derive(Component)]` on all component structs](https://github.com/bevyengine/bevy/pull/2254).
* [Use default serde impls for Entity](https://github.com/bevyengine/bevy/pull/6194).
Examples of PRs that are not [`S-Controversial`] but are [`D-Complex`]:
* [Ensure `Ptr`/`PtrMut`/`OwningPtr` are aligned in debug builds](https://github.com/bevyengine/bevy/pull/7117).
* [Replace `BlobVec`'s `swap_scratch` with a `swap_nonoverlapping`](https://github.com/bevyengine/bevy/pull/4853).
Examples of PRs that are both [`S-Controversial`] and [`D-Complex`]:
* [bevy_reflect: Binary formats](https://github.com/bevyengine/bevy/pull/6140).
Some useful pull request queries:
* [PRs which need reviews and are not `D-Complex`](https://github.com/bevyengine/bevy/pulls?q=is%3Apr+-label%3AD-Complex+-label%3AS-Ready-For-Final-Review+-label%3AS-Blocked++).
* [`D-Complex` PRs which need reviews](https://github.com/bevyengine/bevy/pulls?q=is%3Apr+label%3AD-Complex+-label%3AS-Ready-For-Final-Review+-label%3AS-Blocked).
[`S-Controversial`]: https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+label%3AS-Controversial
[`D-Complex`]: https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+label%3AD-Complex
### Prioritizing PRs and issues
We use [Milestones](https://github.com/bevyengine/bevy/milestones) to track issues and PRs that:
* Need to be merged/fixed before the next release. This is generally for extremely bad bugs i.e. UB or important functionality being broken.
* Would have higher user impact and are almost ready to be merged/fixed.
There are also two priority labels: [`P-Critical`](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AP-Critical) and [`P-High`](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AP-High) that can be used to find issues and PRs that need to be resolved urgently.
### Closing PRs and Issues
From time to time, PRs are unsuitable to be merged in a way that cannot be readily fixed.
Rather than leaving these PRs open in limbo indefinitely, they should simply be closed.
This might happen if:
1. The PR is spam or malicious.
2. The work has already been done elsewhere or is otherwise fully obsolete.
3. The PR was successfully adopted.
4. The work is particularly low quality, and the author is resistant to coaching.
5. The work adds features or abstraction of limited value, especially in a way that could easily be recreated outside of the engine.
6. The work has been sitting in review for so long and accumulated so many conflicts that it would be simpler to redo it from scratch.
7. The PR is pointlessly large, and should be broken into multiple smaller PRs for easier review.
PRs that are `S-Adopt-Me` should be left open, but only if they're genuinely more useful to rebase rather than simply use as a reference.
There are several paths for PRs to be closed:
1. Obviously, authors may close their own PRs for any reason at any time.
2. If a PR is clearly spam or malicious, anyone with triage rights is encouraged to close out the PR and report it to Github.
3. If the work has already been done elsewhere, adopted or otherwise obsoleted, anyone with triage rights is encouraged to close out the PR with an explanatory comment.
4. Anyone may nominate a PR for closure, by bringing it to the attention of the author and / or one of the SMEs / maintainers. Let them press the button, but this is generally well-received and helpful.
5. SMEs or maintainers may and are encouraged to unilaterally close PRs that fall into one or more of the remaining categories.
6. In the case of PRs where some members of the community (other than the author) are in favor and some are opposed, any two relevant SMEs or maintainers may act in concert to close the PR.
When closing a PR, check if it has an issue linked.
If it does not, you should strongly consider creating an issue and linking the now-closed PR to help make sure the previous work can be discovered and credited.
## Making changes to Bevy
Most changes don't require much "process". If your change is relatively straightforward, just do the following:
1. A community member (that's you!) creates one of the following:
* [GitHub Discussions]: An informal discussion with the community. This is the place to start if you want to propose a feature or specific implementation.
* [Issue](https://github.com/bevyengine/bevy/issues): A formal way for us to track a bug or feature. Please look for duplicates before opening a new issue and consider starting with a Discussion.
* [Pull Request](https://github.com/bevyengine/bevy/pulls) (or PR for short): A request to merge code changes. This starts our "review process". You are welcome to start with a pull request, but consider starting with an Issue or Discussion for larger changes (or if you aren't certain about a design). We don't want anyone to waste their time on code that didn't have a chance to be merged! But conversely, sometimes PRs are the most efficient way to propose a change. Just use your own judgement here.
2. Other community members review and comment in an ad-hoc fashion. Active subject matter experts may be pulled into a thread using `@mentions`. If your PR has been quiet for a while and is ready for review, feel free to leave a message to "bump" the thread, or bring it up on [Discord](https://discord.gg/bevy) in an appropriate engine development channel.
3. Once they're content with the pull request (design, code quality, documentation, tests), individual reviewers leave "Approved" reviews.
4. After consensus has been reached (typically two approvals from the community or one for extremely simple changes) and CI passes, the [S-Ready-For-Final-Review](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AS-Ready-For-Final-Review) label is added.
5. When they find time, someone with merge rights performs a final code review and queue the PR for merging.
### Complex changes
Individual contributors often lead major new features and reworks. However these changes require more design work and scrutiny. Complex changes like this tend to go through the following lifecycle:
1. A need or opportunity is identified and an issue is made, laying out the general problem.
2. As needed, this is discussed further on that issue thread, in cross-linked [GitHub Discussion] threads, or on [Discord] in the Engine Development channels.
3. Either a Draft Pull Request or an RFC is made. As discussed in the [RFC repo](https://github.com/bevyengine/rfcs), complex features need RFCs, but these can be submitted before or after prototyping work has been started.
4. If feasible, parts that work on their own (even if they're only useful once the full complex change is merged) get split out into individual PRs to make them easier to review.
5. The community as a whole helps improve the Draft PR and/or RFC, leaving comments, making suggestions, and submitting pull requests to the original branch.
6. Once the RFC is merged and/or the Draft Pull Request is transitioned out of draft mode, the [normal change process outlined in the previous section](#making-changes-to-bevy) can begin.
## How you can help
If you've made it to this page, you're probably already convinced that Bevy is a project you'd like to see thrive.
But how can *you* help?
No matter your experience level with Bevy or Rust or your level of commitment, there are ways to meaningfully contribute.
Take a look at the sections that follow to pick a route (or five) that appeal to you.
If you ever find yourself at a loss for what to do, or in need of mentorship or advice on how to contribute to Bevy, feel free to ask in [Discord] and one of our more experienced community members will be happy to help.
### Join a working group
Active initiatives in Bevy are organized into temporary working groups: choosing one of those and asking how to help can be a fantastic way to get up to speed and be immediately useful.
Working groups are public, open-membership groups that work together to tackle a broad-but-scoped initiative.
The work that they do is coordinated in a forum-channel on [Discord](https://discord.gg/bevy), although they also create issues and may use project boards for tangible work that needs to be done.
There are no special requirements to be a member, and no formal membership list or leadership.
Anyone can help, and you should expect to compromise and work together with others to bring a shared vision to life.
Working groups are *spaces*, not clubs.
### Start a working group
When tackling a complex initiative, friends and allies can make things go much more smoothly.
To start a working group:
1. Decide what the working group is going to focus on. This should be tightly focused and achievable!
2. Gather at least 3 people including yourself who are willing to be in the working group.
3. Ping the `@Maintainer` role on Discord in [#engine-dev](https://discord.com/channels/691052431525675048/692572690833473578) announcing your mutual intent and a one or two sentence description of your plans.
The maintainers will briefly evaluate the proposal in consultation with the relevant SMEs and give you a thumbs up or down on whether this is something Bevy can and wants to explore right now.
You don't need a concrete plan at this stage, just a sensible argument for both "why is this something that could be useful to Bevy" and "why there aren't any serious barriers in implementing this in the near future".
If they're in favor, a maintainer will create a forum channel for you and you're off to the races.
Your initial task is writing up a design doc: laying out the scope of work and general implementation strategy.
Here's a [solid example of a design doc](https://github.com/bevyengine/bevy/issues/12365), although feel free to use whatever format works best for your team.
Once that's ready, get a sign-off on the broad vision and goals from the appropriate SMEs and maintainers.
This is the primary review step: maintainers and SMEs should be broadly patient and supportive even if they're skeptical until a proper design doc is in hand to evaluate.
With a sign-off in hand, post the design doc to [Github Discussions](https://github.com/bevyengine/bevy/discussions) with the [`C-Design-Doc` label](https://github.com/bevyengine/bevy/discussions?discussions_q=is%3Aopen+label%3A%22C-Design+Doc%22) for archival purposes and begin work on implementation.
Post PRs that you need review on in your group's forum thread, ask for advice, and share the load.
Controversial PRs are still `S-Controversial`, but with a sign-off-in-priniciple, things should go more smoothly.
If work peters out and the initiative dies, maintainers can wind down working groups (in consultation with SMEs and the working group itself).
This is normal and expected: projects fail for all sorts of reasons!
However, it's important to both keep the number of working groups relatively small and ensure they're active:
they serve a vital role in onboarding new contributors.
Once your implementation work laid out in your initial design doc is complete, it's time to wind down the working group.
Feel free to make another one though to tackle the next step in your grand vision!
### Battle-testing Bevy
Ultimately, Bevy is a tool that's designed to help people make cool games.
By using Bevy, you can help us catch bugs, prioritize new features, polish off the rough edges, and promote the project.
If you need help, don't hesitate to ask for help on [GitHub Discussions], [Discord], or [reddit](https://www.reddit.com/r/bevy). Generally you should prefer asking questions as [GitHub Discussions] as they are more searchable.
When you think you've found a bug, missing documentation, or a feature that would help you make better games, please [file an issue](https://github.com/bevyengine/bevy/issues/new/choose) on the main `bevy` repo.
Do your best to search for duplicate issues, but if you're unsure, open a new issue and link to other related issues on the thread you make.
Once you've made something that you're proud of, feel free to drop a link, video, or screenshot in `#showcase` on [Discord]!
If you release a game on [itch.io](https://itch.io/games/tag-bevy) we'd be thrilled if you tagged it with `bevy`.
### Teaching others
Bevy is still very young, and light on documentation, tutorials, and accumulated expertise.
By helping others with their issues, and teaching them about Bevy, you will naturally learn the engine and codebase in greater depth while also making our community better!
Some of the best ways to do this are:
* Answering questions on [GitHub Discussions], [Discord], and [reddit](https://www.reddit.com/r/bevy).
* Writing tutorials, guides, and other informal documentation and sharing them on [Bevy Assets](https://github.com/bevyengine/bevy-assets).
* Streaming, writing blog posts about creating your game, and creating videos. Share these in the `#devlogs` channel on [Discord]!
### Writing plugins
You can improve Bevy's ecosystem by building your own Bevy Plugins and crates.
Non-trivial, reusable functionality that works well with itself is a good candidate for a plugin.
If it's closer to a snippet or design pattern, you may want to share it with the community on [Discord], Reddit, or [GitHub Discussions] instead.
Check out our [plugin guidelines](https://bevyengine.org/learn/book/plugin-development/) for helpful tips and patterns.
### Fixing bugs
Bugs in Bevy (or the associated website / book) are filed on the issue tracker using the [`C-Bug`](https://github.com/bevyengine/bevy/issues?q=is%3Aissue+is%3Aopen+label%3AC-Bug) label.
If you're looking for an easy place to start, take a look at the [`D-Good-First-Issue`](https://github.com/bevyengine/bevy/issues?q=is%3Aopen+is%3Aissue+label%3AD-Good-First-Issue) label, and feel free to ask questions on that issue's thread in question or on [Discord].
You don't need anyone's permission to try fixing a bug or adding a simple feature, but stating that you'd like to tackle an issue can be helpful to avoid duplicated work.
When you make a pull request that fixes an issue, include a line that says `Fixes #X` (or "Closes"), where `X` is the issue number.
This will cause the issue in question to be closed when your PR is merged.
General improvements to code quality are also welcome!
Bevy can always be safer, better tested, and more idiomatic.
### Writing docs
Like every other large, rapidly developing open source library you've ever used, Bevy's documentation can always use improvement.
This is incredibly valuable, easily distributed work, but requires a bit of guidance:
* Inaccurate documentation is worse than no documentation: prioritize fixing broken docs.
* Bevy is remarkably unstable: before tackling a new major documentation project, check in with the community on Discord or GitHub (making an issue about specific missing docs is a great way to plan) about the stability of that feature and upcoming plans to save yourself heartache.
* Code documentation (doc examples and in the examples folder) is easier to maintain because the compiler will tell us when it breaks.
* Inline documentation should be technical and to the point. Link relevant examples or other explanations if broader context is useful.
* The Bevy book is hosted on the `bevy-website` repo and targeted towards beginners who are just getting to know Bevy (and perhaps Rust!).
* Accepted RFCs are not documentation: they serve only as a record of accepted decisions.
[docs.rs](https://docs.rs/bevy) is built from out of the last release's documentation, which is written right in-line directly above the code it documents.
To view the current docs on `main` before you contribute, clone the `bevy` repo, and run `cargo doc --open` or go to [dev-docs.bevyengine.org](https://dev-docs.bevyengine.org/),
which has the latest API reference built from the repo on every commit made to the `main` branch.
### Writing examples
Most [examples in Bevy](https://github.com/bevyengine/bevy/tree/main/examples) aim to clearly demonstrate a single feature, group of closely related small features, or show how to accomplish a particular task (such as asset loading, creating a custom shader or testing your app).
In rare cases, creating new "game" examples is justified in order to demonstrate new features that open a complex class of functionality in a way that's hard to demonstrate in isolation or requires additional integration testing.
Examples in Bevy should be:
1. **Working:** They must compile and run, and any introduced errors in them should be obvious (through tests, simple results or clearly displayed behavior).
2. **Clear:** They must use descriptive variable names, be formatted, and be appropriately commented. Try your best to showcase best practices when it doesn't obscure the point of the example.
3. **Relevant:** They should explain, through comments or variable names, what they do and how this can be useful to a game developer.
4. **Minimal:** They should be no larger or complex than is needed to meet the goals of the example.
When you add a new example, be sure to update `examples/README.md` with the new example and add it to the root `Cargo.toml` file.
Run `cargo run -p build-templated-pages -- build-example-page` to do this automatically.
Use a generous sprinkling of keywords in your description: these are commonly used to search for a specific example.
See the [example style guide](.github/contributing/example_style_guide.md) to help make sure the style of your example matches what we're already using.
More complex demonstrations of functionality are also welcome, but these should be submitted to [bevy-assets](https://github.com/bevyengine/bevy-assets).
### Reviewing others' work
With the sheer volume of activity in Bevy's community, reviewing others work with the aim of improving it is one of the most valuable things you can do.
You don't need to be an Elder Rustacean to be useful here: anyone can catch missing tests, unclear docs, logic errors, and so on.
If you have specific skills (e.g. advanced familiarity with `unsafe` code, rendering knowledge or web development experience) or personal experience with a problem, try to prioritize those areas to ensure we can get appropriate expertise where we need it.
When you find (or make) a PR that you don't feel comfortable reviewing, but you *can* think of someone who does, consider using Github's "Request review" functionality (in the top-right of the PR screen) to bring the work to their attention.
If they're not a Bevy Org member, you'll need to ping them in the thread directly: that's fine too!
Almost everyone working on Bevy is a volunteer: this should be treated as a gentle nudge, rather than an assignment of work.
Consider checking the Git history for appropriate reviewers, or ask on Discord for suggestions.
Focus on giving constructive, actionable feedback that results in real improvements to code quality or end-user experience.
If you don't understand why an approach was taken, please ask!
Provide actual code suggestions when that is helpful. Small changes work well as comments or in-line suggestions on specific lines of codes.
Larger changes deserve a comment in the main thread, or a pull request to the original author's branch (but please mention that you've made one).
When in doubt about a matter of architectural philosophy, refer back to [*What we're trying to build*](#what-were-trying-to-build) for guidance.
Once you're happy with the work and feel you're reasonably qualified to assess quality in this particular area, leave your `Approved` review on the PR.
If you're new to GitHub, check out the [Pull Request Review documentation](https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/about-pull-request-reviews).
**Anyone** can and should leave reviews ... no special permissions are required!
It's okay to leave an approval even if you aren't 100% confident on all areas of the PR: just be sure to note your limitations.
When maintainers are evaluating the PR to be merged, they'll make sure that there's good coverage on all of the critical areas.
If you can only check that the math is correct, and another reviewer can check everything *but* the math, we're in good shape!
Similarly, if there are areas that would be *good* to fix but aren't severe, please consider leaving an approval.
The author can address them immediately, or spin it out into follow-up issues or PRs.
Large PRs are much more draining for both reviewers and authors, so try to push for a smaller scope with clearly tracked follow-ups.
There are three main places you can check for things to review:
1. Pull requests which are ready and in need of more reviews on [bevy](https://github.com/bevyengine/bevy/pulls?q=is%3Aopen+is%3Apr+-label%3AS-Ready-For-Final-Review+-draft%3A%3Atrue+-label%3AS-Needs-RFC+-reviewed-by%3A%40me+-author%3A%40me).
2. Pull requests on [bevy](https://github.com/bevyengine/bevy/pulls) and the [bevy-website](https://github.com/bevyengine/bevy-website/pulls) repos.
3. [RFCs](https://github.com/bevyengine/rfcs), which need extensive thoughtful community input on their design.
Not even our Project Leads and Maintainers are exempt from reviews and RFCs!
By giving feedback on this work (and related supporting work), you can help us make sure our releases are both high-quality and timely.
Finally, if nothing brings you more satisfaction than seeing every last issue labeled and all resolved issues closed, feel free to message the Project Lead (currently @cart) for a Bevy org role to help us keep things tidy.
As discussed in our [*Bevy Organization doc*](/docs/the_bevy_organization.md), this role only requires good faith and a basic understanding of our development process.
### How to adopt pull requests
Occasionally authors of pull requests get busy or become unresponsive, or project members fail to reply in a timely manner.
This is a natural part of any open source project.
To avoid blocking these efforts, these pull requests may be *adopted*, where another contributor creates a new pull request with the same content.
If there is an old pull request that is without updates, comment to the organization whether it is appropriate to add the
*[S-Adopt-Me](https://github.com/bevyengine/bevy/labels/S-Adopt-Me)* label, to indicate that it can be *adopted*.
If you plan on adopting a PR yourself, you can also leave a comment on the PR asking the author if they plan on returning.
If the author gives permission or simply doesn't respond after a few days, then it can be adopted.
This may sometimes even skip the labeling process since at that point the PR has been adopted by you.
With this label added, it's best practice to fork the original author's branch.
This ensures that they still get credit for working on it and that the commit history is retained.
When the new pull request is ready, it should reference the original PR in the description.
Then notify org members to close the original.
* For example, you can reference the original PR by adding the following to your PR description:
`Adopted #number-original-pull-request`
### Contributing code
Bevy is actively open to code contributions from community members.
If you're new to Bevy, here's the workflow we use:
1. Fork the `bevyengine/bevy` repository on GitHub. You'll need to create a GitHub account if you don't have one already.
2. Make your changes in a local clone of your fork, typically in its own new branch.
1. Try to split your work into separate commits, each with a distinct purpose. Be particularly mindful of this when responding to reviews so it's easy to see what's changed.
2. Tip: [You can set up a global `.gitignore` file](https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files#configuring-ignored-files-for-all-repositories-on-your-computer) to exclude your operating system/text editor's special/temporary files. (e.g. `.DS_Store`, `thumbs.db`, `*~`, `*.swp` or `*.swo`) This allows us to keep the `.gitignore` file in the repo uncluttered.
3. To test CI validations locally, run the `cargo run -p ci` command. This will run most checks that happen in CI, but can take some time. You can also run sub-commands to iterate faster depending on what you're contributing:
* `cargo run -p ci -- lints` - to run formatting and clippy.
* `cargo run -p ci -- test` - to run tests.
* `cargo run -p ci -- doc` - to run doc tests and doc checks.
* `cargo run -p ci -- compile` - to check that everything that must compile still does (examples and benches), and that some that shouldn't still don't ([`crates/bevy_ecs_compile_fail_tests`](./crates/bevy_ecs_compile_fail_tests)).
* to get more information on commands available and what is run, check the [tools/ci crate](./tools/ci).
4. When working with Markdown (`.md`) files, Bevy's CI will check markdown files (like this one) using [markdownlint](https://github.com/DavidAnson/markdownlint).
To locally lint your files using the same workflow as our CI:
1. Install [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli).
2. Run `markdownlint -f -c .github/linters/.markdown-lint.yml .` in the root directory of the Bevy project.
5. When working with Toml (`.toml`) files, Bevy's CI will check toml files using [taplo](https://taplo.tamasfe.dev/): `taplo fmt --check --diff`
1. If you use VSCode, install [Even better toml](https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml) and format your files.
2. If you want to use the cli tool, install [taplo-cli](https://taplo.tamasfe.dev/cli/installation/cargo.html) and run `taplo fmt --check --diff` to check for the formatting. Fix any issues by running `taplo fmt` in the root directory of the Bevy project.
6. Check for typos. Bevy's CI will check for them using [typos](https://github.com/crate-ci/typos).
1. If you use VSCode, install [Typos Spell Checker](https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode).
2. You can also use the cli tool. Install [typos-cli](https://github.com/crate-ci/typos?tab=readme-ov-file#install) and run `typos` to check for typos, and fix them by running `typos -w`.
7. Push your changes to your fork on Github and open a Pull Request.
8. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to a Maintainer or Project Lead's final judgement.
9. When your PR is ready to merge, a Maintainer or Project Lead will review it and suggest final changes. If those changes are minimal they may even apply them directly to speed up merging.
If you end up adding a new official Bevy crate to the `bevy` repo:
1. Add the new crate to the [./tools/publish.sh](./tools/publish.sh) file.
2. Check if a new cargo feature was added, update [cargo_features.md](https://github.com/bevyengine/bevy/blob/main/docs/cargo_features.md) as needed.
When contributing, please:
* Try to loosely follow the workflow in [*Making changes to Bevy*](#making-changes-to-bevy).
* Consult the [style guide](.github/contributing/engine_style_guide.md) to help keep our code base tidy.
* Explain what you're doing and why.
* Document new code with doc comments.
* Include clear, simple tests.
* Add or improve the examples when adding new user-facing functionality.
* Break work into digestible chunks.
* Ask for any help that you need!
Your first PR will be merged in no time!
No matter how you're helping: thanks for contributing to Bevy!
[GitHub Discussions]: https://github.com/bevyengine/bevy/discussions "GitHub Discussions"
[Discord]: https://discord.gg/bevy "Discord"

View File

@ -20,8 +20,8 @@
* Cake from [Kenney's Food Kit](https://www.kenney.nl/assets/food-kit) (CC0 1.0 Universal)
* Ground tile from [Kenney's Tower Defense Kit](https://www.kenney.nl/assets/tower-defense-kit) (CC0 1.0 Universal)
* Game icons from [Kenney's Game Icons](https://www.kenney.nl/assets/game-icons) (CC0 1.0 Universal)
* Space ships from [Kenney's Simple Space Kit](https://www.kenney.nl/assets/simple-space) (CC0 1.0 Universal)
* UI borders from [Kenney's Fantasy UI Borders Kit](https://kenney.nl/assets/fantasy-ui-borders) (CC0 1.0 Universal)
* Space ships from [Kenny's Simple Space Kit](https://www.kenney.nl/assets/simple-space) (CC0 1.0 Universal)
* UI borders from [Kenny's Fantasy UI Borders Kit](https://kenney.nl/assets/fantasy-ui-borders) (CC0 1.0 Universal)
* glTF animated fox from [glTF Sample Models][fox]
* Low poly fox [by PixelMannen] (CC0 1.0 Universal)
* Rigging and animation [by @tomkranis on Sketchfab] ([CC-BY 4.0])
@ -32,7 +32,7 @@
* Epic orchestra music sample, modified to loop, from [Migfus20](https://freesound.org/people/Migfus20/sounds/560449/) ([CC BY 4.0 DEED](https://creativecommons.org/licenses/by/4.0/))
[MorphStressTest]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/MorphStressTest
[fox]: https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/Fox
[fox]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox
[by PixelMannen]: https://opengameart.org/content/fox-and-shiba
[by @tomkranis on Sketchfab]: https://sketchfab.com/models/371dea88d7e04a76af5763f2a36866bc
[CC-BY 4.0]: https://creativecommons.org/licenses/by/4.0/

1314
Cargo.toml

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@ Before contributing or participating in discussions with the community, you shou
### Contributing
If you'd like to help build Bevy, check out the **[Contributor's Guide](https://bevyengine.org/learn/contribute/introduction)**.
If you'd like to help build Bevy, check out the **[Contributor's Guide](https://github.com/bevyengine/bevy/blob/main/CONTRIBUTING.md)**.
For simple problems, feel free to [open an issue](https://github.com/bevyengine/bevy/issues) or
[PR](https://github.com/bevyengine/bevy/pulls) and tackle it yourself!

View File

@ -2,28 +2,23 @@
graph: (
nodes: [
(
node_type: Blend,
mask: 0,
clip: None,
weight: 1.0,
),
(
node_type: Blend,
mask: 0,
clip: None,
weight: 0.5,
),
(
clip: Some(AssetPath("models/animated/Fox.glb#Animation0")),
weight: 1.0,
),
(
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation0")),
mask: 0,
clip: Some(AssetPath("models/animated/Fox.glb#Animation1")),
weight: 1.0,
),
(
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation1")),
mask: 0,
weight: 1.0,
),
(
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation2")),
mask: 0,
clip: Some(AssetPath("models/animated/Fox.glb#Animation2")),
weight: 1.0,
),
],
@ -37,5 +32,4 @@
],
),
root: 0,
mask_groups: {},
)

View File

@ -1,19 +0,0 @@
Crosshair Pack
by Kenney Vleugels (Kenney.nl)
------------------------------
License (Creative Commons Zero, CC0)
http://creativecommons.org/publicdomain/zero/1.0/
You may use these assets in personal and commercial projects.
Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
------------------------------
Donate: http://support.kenney.nl
Follow on Twitter for updates: @KenneyNL (www.twitter.com/kenneynl)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -1,2 +0,0 @@
*
!.gitignore

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,26 +1,37 @@
(
resources: {
"scene::ResourceA": (
score: 1,
score: 2,
),
},
entities: {
4294967296: (
components: {
"bevy_ecs::name::Name": "joe",
"bevy_transform::components::global_transform::GlobalTransform": ((1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)),
"bevy_transform::components::transform::Transform": (
translation: (0.0, 0.0, 0.0),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (1.0, 1.0, 1.0),
translation: (
x: 0.0,
y: 0.0,
z: 0.0
),
rotation: (
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
),
scale: (
x: 1.0,
y: 1.0,
z: 1.0
),
),
"scene::ComponentB": (
value: "hello",
),
"scene::ComponentA": (
x: 1.0,
y: 2.0,
),
"scene::ComponentB": (
value: "hello",
),
},
),
4294967297: (
@ -31,5 +42,5 @@
),
},
),
},
)
}
)

View File

@ -1,43 +0,0 @@
#import bevy_pbr::{
mesh_functions,
view_transformations::position_world_to_clip
}
@group(2) @binding(0) var texture: texture_2d<f32>;
@group(2) @binding(1) var texture_sampler: sampler;
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) world_position: vec4<f32>,
@location(1) color: vec4<f32>,
};
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
// Lookup the tag for the given mesh
let tag = mesh_functions::get_tag(vertex.instance_index);
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
out.clip_position = position_world_to_clip(out.world_position.xyz);
let tex_dim = textureDimensions(texture);
// Find the texel coordinate as derived from the tag
let texel_coord = vec2<u32>(tag % tex_dim.x, tag / tex_dim.x);
out.color = textureLoad(texture, texel_coord, 0);
return out;
}
@fragment
fn fragment(
mesh: VertexOutput,
) -> @location(0) vec4<f32> {
return mesh.color;
}

View File

@ -1,46 +0,0 @@
#import bevy_pbr::forward_io::VertexOutput
#import bevy_pbr::mesh_bindings::mesh
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
struct Color {
base_color: vec4<f32>,
}
// This structure is a mapping from bindless index to the index in the
// appropriate slab
struct MaterialBindings {
material: u32, // 0
color_texture: u32, // 1
color_texture_sampler: u32, // 2
}
#ifdef BINDLESS
@group(2) @binding(0) var<storage> materials: array<MaterialBindings>;
@group(2) @binding(10) var<storage> material_color: binding_array<Color>;
#else // BINDLESS
@group(2) @binding(0) var<uniform> material_color: Color;
@group(2) @binding(1) var material_color_texture: texture_2d<f32>;
@group(2) @binding(2) var material_color_sampler: sampler;
#endif // BINDLESS
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
#ifdef BINDLESS
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
let base_color = material_color[materials[slot].material].base_color;
#else // BINDLESS
let base_color = material_color.base_color;
#endif // BINDLESS
return base_color * textureSampleLevel(
#ifdef BINDLESS
bindless_textures_2d[materials[slot].color_texture],
bindless_samplers_filtering[materials[slot].color_texture_sampler],
#else // BINDLESS
material_color_texture,
material_color_sampler,
#endif // BINDLESS
in.uv,
0.0
);
}

View File

@ -0,0 +1,20 @@
// This shader draws a circle with a given input color
#import bevy_ui::ui_vertex_output::UiVertexOutput
struct CustomUiMaterial {
@location(0) color: vec4<f32>
}
@group(1) @binding(0)
var<uniform> input: CustomUiMaterial;
@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
// the UVs are now adjusted around the middle of the rect.
let uv = in.uv * 2.0 - 1.0;
// circle alpha, the higher the power the harsher the falloff.
let alpha = 1.0 - pow(sqrt(dot(uv, uv)), 100.0);
return vec4<f32>(input.color.rgb, alpha);
}

View File

@ -1,86 +0,0 @@
// This shader, a part of the `clustered_decals` example, shows how to use the
// decal `tag` field to apply arbitrary decal effects.
#import bevy_pbr::{
clustered_forward,
decal::clustered,
forward_io::{VertexOutput, FragmentOutput},
mesh_view_bindings,
pbr_fragment::pbr_input_from_standard_material,
pbr_functions::{alpha_discard, apply_pbr_lighting, main_pass_post_lighting_processing},
}
@fragment
fn fragment(
in: VertexOutput,
@builtin(front_facing) is_front: bool,
) -> FragmentOutput {
// Generate a `PbrInput` struct from the `StandardMaterial` bindings.
var pbr_input = pbr_input_from_standard_material(in, is_front);
// Alpha discard.
pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color);
// Apply the normal decals.
pbr_input.material.base_color = clustered::apply_decal_base_color(
in.world_position.xyz,
in.position.xy,
pbr_input.material.base_color
);
// Here we tint the color based on the tag of the decal.
// We could optionally do other things, such as adjust the normal based on a normal map.
let view_z = clustered::get_view_z(in.world_position.xyz);
let is_orthographic = clustered::view_is_orthographic();
let cluster_index =
clustered_forward::fragment_cluster_index(in.position.xy, view_z, is_orthographic);
var clusterable_object_index_ranges =
clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
var decal_iterator = clustered::clustered_decal_iterator_new(
in.world_position.xyz,
&clusterable_object_index_ranges
);
while (clustered::clustered_decal_iterator_next(&decal_iterator)) {
var decal_base_color = textureSampleLevel(
mesh_view_bindings::clustered_decal_textures[decal_iterator.texture_index],
mesh_view_bindings::clustered_decal_sampler,
decal_iterator.uv,
0.0
);
switch (decal_iterator.tag) {
case 1u: {
// Tint with red.
decal_base_color = vec4(
mix(pbr_input.material.base_color.rgb, vec3(1.0, 0.0, 0.0), 0.5),
decal_base_color.a,
);
}
case 2u: {
// Tint with blue.
decal_base_color = vec4(
mix(pbr_input.material.base_color.rgb, vec3(0.0, 0.0, 1.0), 0.5),
decal_base_color.a,
);
}
default: {}
}
pbr_input.material.base_color = vec4(
mix(pbr_input.material.base_color.rgb, decal_base_color.rgb, decal_base_color.a),
pbr_input.material.base_color.a + decal_base_color.a
);
}
// Apply lighting.
var out: FragmentOutput;
out.color = apply_pbr_lighting(pbr_input);
// Apply in-shader post processing (fog, alpha-premultiply, and also
// tonemapping, debanding if the camera is non-HDR). Note this does not
// include fullscreen postprocessing effects like bloom.
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
return out;
}

View File

@ -7,16 +7,14 @@ layout(location = 2) in vec2 Vertex_Uv;
layout(location = 0) out vec2 v_Uv;
layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 clip_from_world;
// Other attributes exist that can be described here.
// See full definition in: crates/bevy_render/src/view/view.wgsl
// Attributes added here must be in the same order as they are defined
// in view.wgsl, and they must be contiguous starting from the top to
// ensure they have the same layout.
//
// Needing to maintain this mapping yourself is one of the harder parts of using
// GLSL with Bevy. WGSL provides a much better user experience!
} camera_view;
mat4 ViewProj;
mat4 View;
mat4 InverseView;
mat4 Projection;
vec3 WorldPosition;
float width;
float height;
};
struct Mesh {
mat3x4 Model;
@ -43,7 +41,7 @@ mat4 affine_to_square(mat3x4 affine) {
void main() {
v_Uv = Vertex_Uv;
gl_Position = camera_view.clip_from_world
gl_Position = ViewProj
* affine_to_square(Meshes[gl_InstanceIndex].Model)
* vec4(Vertex_Position, 1.0);
}

View File

@ -1,20 +0,0 @@
import super::util::make_polka_dots;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(2) uv: vec2<f32>,
}
struct CustomMaterial {
// Needed for 16-bit alignment on WebGL2
time: vec4<f32>,
}
@group(2) @binding(0) var<uniform> material: CustomMaterial;
@fragment
fn fragment(
mesh: VertexOutput,
) -> @location(0) vec4<f32> {
return make_polka_dots(mesh.uv, material.time.x);
}

View File

@ -1,41 +0,0 @@
//! A shader showing how to use the vertex position data to output the
//! stencil in the right position
// First we import everything we need from bevy_pbr
// A 2d shader would be vevry similar but import from bevy_sprite instead
#import bevy_pbr::{
mesh_functions,
view_transformations::position_world_to_clip
}
struct Vertex {
// This is needed if you are using batching and/or gpu preprocessing
// It's a built in so you don't need to define it in the vertex layout
@builtin(instance_index) instance_index: u32,
// Like we defined for the vertex layout
// position is at location 0
@location(0) position: vec3<f32>,
};
// This is the output of the vertex shader and we also use it as the input for the fragment shader
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) world_position: vec4<f32>,
};
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
// This is how bevy computes the world position
// The vertex.instance_index is very important. Especially if you are using batching and gpu preprocessing
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
out.clip_position = position_world_to_clip(out.world_position.xyz);
return out;
}
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
// Output a red color to represent the stencil of the mesh
return vec4(1.0, 0.0, 0.0, 1.0);
}

View File

@ -1,59 +0,0 @@
// Draws a progress bar with properties defined in CustomUiMaterial
#import bevy_ui::ui_vertex_output::UiVertexOutput
@group(1) @binding(0) var<uniform> color: vec4<f32>;
@group(1) @binding(1) var<uniform> slider: vec4<f32>;
@group(1) @binding(2) var material_color_texture: texture_2d<f32>;
@group(1) @binding(3) var material_color_sampler: sampler;
@group(1) @binding(4) var<uniform> border_color: vec4<f32>;
@fragment
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color;
// half size of the UI node
let half_size = 0.5 * in.size;
// position relative to the center of the UI node
let p = in.uv * in.size - half_size;
// thickness of the border closest to the current position
let b = vec2(
select(in.border_widths.x, in.border_widths.z, 0. < p.x),
select(in.border_widths.y, in.border_widths.w, 0. < p.y)
);
// select radius for the nearest corner
let rs = select(in.border_radius.xy, in.border_radius.wz, 0.0 < p.y);
let radius = select(rs.x, rs.y, 0.0 < p.x);
// distance along each axis from the corner
let d = half_size - abs(p);
// if the distance to the edge from the current position on any axis
// is less than the border width on that axis then the position is within
// the border and we return the border color
if d.x < b.x || d.y < b.y {
// select radius for the nearest corner
let rs = select(in.border_radius.xy, in.border_radius.wz, 0.0 < p.y);
let radius = select(rs.x, rs.y, 0.0 < p.x);
// determine if the point is inside the curved corner and return the corresponding color
let q = radius - d;
if radius < min(max(q.x, q.y), 0.0) + length(vec2(max(q.x, 0.0), max(q.y, 0.0))) {
return vec4(0.0);
} else {
return border_color;
}
}
// sample the texture at this position if it's to the left of the slider value
// otherwise return a fully transparent color
if in.uv.x < slider.x {
let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color;
return output_color;
} else {
return vec4(0.0);
}
}

View File

@ -1,107 +0,0 @@
// The shader that goes with `extended_material_bindless.rs`.
//
// This code demonstrates how to write shaders that are compatible with both
// bindless and non-bindless mode. See the `#ifdef BINDLESS` blocks.
#import bevy_pbr::{
forward_io::{FragmentOutput, VertexOutput},
mesh_bindings::mesh,
pbr_fragment::pbr_input_from_standard_material,
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
}
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
#ifdef BINDLESS
#import bevy_pbr::pbr_bindings::{material_array, material_indices}
#else // BINDLESS
#import bevy_pbr::pbr_bindings::material
#endif // BINDLESS
// Stores the indices of the bindless resources in the bindless resource arrays,
// for the `ExampleBindlessExtension` fields.
struct ExampleBindlessExtendedMaterialIndices {
// The index of the `ExampleBindlessExtendedMaterial` data in
// `example_extended_material`.
material: u32,
// The index of the texture we're going to modulate the base color with in
// the `bindless_textures_2d` array.
modulate_texture: u32,
// The index of the sampler we're going to sample the modulated texture with
// in the `bindless_samplers_filtering` array.
modulate_texture_sampler: u32,
}
// Plain data associated with this example material.
struct ExampleBindlessExtendedMaterial {
// The color that we multiply the base color, base color texture, and
// modulated texture with.
modulate_color: vec4<f32>,
}
#ifdef BINDLESS
// The indices of the bindless resources in the bindless resource arrays, for
// the `ExampleBindlessExtension` fields.
@group(2) @binding(100) var<storage> example_extended_material_indices:
array<ExampleBindlessExtendedMaterialIndices>;
// An array that holds the `ExampleBindlessExtendedMaterial` plain old data,
// indexed by `ExampleBindlessExtendedMaterialIndices.material`.
@group(2) @binding(101) var<storage> example_extended_material:
array<ExampleBindlessExtendedMaterial>;
#else // BINDLESS
// In non-bindless mode, we simply use a uniform for the plain old data.
@group(2) @binding(50) var<uniform> example_extended_material: ExampleBindlessExtendedMaterial;
@group(2) @binding(51) var modulate_texture: texture_2d<f32>;
@group(2) @binding(52) var modulate_sampler: sampler;
#endif // BINDLESS
@fragment
fn fragment(
in: VertexOutput,
@builtin(front_facing) is_front: bool,
) -> FragmentOutput {
#ifdef BINDLESS
// Fetch the material slot. We'll use this in turn to fetch the bindless
// indices from `example_extended_material_indices`.
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
#endif // BINDLESS
// Generate a `PbrInput` struct from the `StandardMaterial` bindings.
var pbr_input = pbr_input_from_standard_material(in, is_front);
// Calculate the UV for the texture we're about to sample.
#ifdef BINDLESS
let uv_transform = material_array[material_indices[slot].material].uv_transform;
#else // BINDLESS
let uv_transform = material.uv_transform;
#endif // BINDLESS
let uv = (uv_transform * vec3(in.uv, 1.0)).xy;
// Multiply the base color by the `modulate_texture` and `modulate_color`.
#ifdef BINDLESS
// Notice how we fetch the texture, sampler, and plain extended material
// data from the appropriate arrays.
pbr_input.material.base_color *= textureSample(
bindless_textures_2d[example_extended_material_indices[slot].modulate_texture],
bindless_samplers_filtering[
example_extended_material_indices[slot].modulate_texture_sampler
],
uv
) * example_extended_material[example_extended_material_indices[slot].material].modulate_color;
#else // BINDLESS
pbr_input.material.base_color *= textureSample(modulate_texture, modulate_sampler, uv) *
example_extended_material.modulate_color;
#endif // BINDLESS
var out: FragmentOutput;
// Apply lighting.
out.color = apply_pbr_lighting(pbr_input);
// Apply in-shader post processing (fog, alpha-premultiply, and also
// tonemapping, debanding if the camera is non-HDR). Note this does not
// include fullscreen postprocessing effects like bloom.
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
return out;
}

View File

@ -3,13 +3,10 @@
// This is the data that lives in the gpu only buffer
@group(0) @binding(0) var<storage, read_write> data: array<u32>;
@group(0) @binding(1) var texture: texture_storage_2d<r32uint, write>;
@compute @workgroup_size(1)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
// We use the global_id to index the array to make sure we don't
// access data used in another workgroup
data[global_id.x] += 1u;
// Write the same data to the texture
textureStore(texture, vec2<i32>(i32(global_id.x), 0), vec4<u32>(data[global_id.x], 0, 0, 0));
}

View File

@ -1,7 +1,6 @@
#import bevy_pbr::forward_io::VertexOutput
#import bevy_pbr::irradiance_volume
#import bevy_pbr::mesh_view_bindings
#import bevy_pbr::clustered_forward
struct VoxelVisualizationIrradianceVolumeInfo {
world_from_voxel: mat4x4<f32>,
@ -26,24 +25,11 @@ fn fragment(mesh: VertexOutput) -> @location(0) vec4<f32> {
let stp_rounded = round(stp - 0.5f) + 0.5f;
let rounded_world_pos = (irradiance_volume_info.world_from_voxel * vec4(stp_rounded, 1.0f)).xyz;
// Look up the irradiance volume range in the cluster list.
let view_z = dot(vec4<f32>(
mesh_view_bindings::view.view_from_world[0].z,
mesh_view_bindings::view.view_from_world[1].z,
mesh_view_bindings::view.view_from_world[2].z,
mesh_view_bindings::view.view_from_world[3].z
), mesh.world_position);
let cluster_index = clustered_forward::fragment_cluster_index(mesh.position.xy, view_z, false);
var clusterable_object_index_ranges =
clustered_forward::unpack_clusterable_object_index_ranges(cluster_index);
// `irradiance_volume_light()` multiplies by intensity, so cancel it out.
// If we take intensity into account, the cubes will be way too bright.
let rgb = irradiance_volume::irradiance_volume_light(
mesh.world_position.xyz,
mesh.world_normal,
&clusterable_object_index_ranges,
) / irradiance_volume_info.intensity;
mesh.world_normal) / irradiance_volume_info.intensity;
return vec4<f32>(rgb, 1.0f);
}

View File

@ -1,48 +0,0 @@
//! Very simple shader used to demonstrate how to get the world position and pass data
//! between the vertex and fragment shader. Also shows the custom vertex layout.
// First we import everything we need from bevy_pbr
// A 2D shader would be very similar but import from bevy_sprite instead
#import bevy_pbr::{
mesh_functions,
view_transformations::position_world_to_clip
}
struct Vertex {
// This is needed if you are using batching and/or gpu preprocessing
// It's a built in so you don't need to define it in the vertex layout
@builtin(instance_index) instance_index: u32,
// Like we defined for the vertex layout
// position is at location 0
@location(0) position: vec3<f32>,
// and color at location 1
@location(1) color: vec4<f32>,
};
// This is the output of the vertex shader and we also use it as the input for the fragment shader
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) world_position: vec4<f32>,
@location(1) color: vec3<f32>,
};
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
// This is how bevy computes the world position
// The vertex.instance_index is very important. Especially if you are using batching and gpu preprocessing
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
out.clip_position = position_world_to_clip(out.world_position.xyz);
// We just use the raw vertex color
out.color = vertex.color.rgb;
return out;
}
@fragment
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
// output the color directly
return vec4(in.color, 1.0);
}

View File

@ -1,36 +0,0 @@
#import bevy_pbr::{
mesh_functions,
view_transformations::position_world_to_clip
}
@group(2) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) world_position: vec4<f32>,
@location(1) color: vec4<f32>,
};
@vertex
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
let tag = mesh_functions::get_tag(vertex.instance_index);
var world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4(vertex.position, 1.0));
out.clip_position = position_world_to_clip(out.world_position.xyz);
out.color = colors[tag];
return out;
}
@fragment
fn fragment(
mesh: VertexOutput,
) -> @location(0) vec4<f32> {
return mesh.color;
}

View File

@ -9,19 +9,19 @@
#import bevy_core_pipeline::tonemapping::tone_mapping
#endif
// Sweep across hues on y axis with value from 0.0 to +15EV across x axis
// Sweep across hues on y axis with value from 0.0 to +15EV across x axis
// quantized into 24 steps for both axis.
fn color_sweep(uv_input: vec2<f32>) -> vec3<f32> {
var uv = uv_input;
let steps = 24.0;
uv.y = uv.y * (1.0 + 1.0 / steps);
let ratio = 2.0;
let h = PI * 2.0 * floor(1.0 + steps * uv.y) / steps;
let L = floor(uv.x * steps * ratio) / (steps * ratio) - 0.5;
var color = vec3(0.0);
if uv.y < 1.0 {
if uv.y < 1.0 {
color = cos(h + vec3(0.0, 1.0, 2.0) * PI * 2.0 / 3.0);
let maxRGB = max(color.r, max(color.g, color.b));
let minRGB = min(color.r, min(color.g, color.b));

View File

@ -1,44 +0,0 @@
fn make_polka_dots(pos: vec2<f32>, time: f32) -> vec4<f32> {
let scaled_pos = pos * 6.0;
let cell = vec2<f32>(fract(scaled_pos.x), fract(scaled_pos.y));
var dist_from_center = distance(cell, vec2<f32>(0.5));
let is_even = (floor(scaled_pos.x) + floor(scaled_pos.y)) % 2.0;
var dot_color = vec3<f32>(0.0);
var is_dot = 0.0;
@if(!PARTY_MODE) {
let color1 = vec3<f32>(1.0, 0.4, 0.8); // pink
let color2 = vec3<f32>(0.6, 0.2, 1.0); // purple
dot_color = mix(color1, color2, is_even);
is_dot = step(dist_from_center, 0.3);
} @else {
let grid_x = floor(scaled_pos.x);
let grid_y = floor(scaled_pos.y);
let wave_speed = 3.0;
let wave_phase = time * wave_speed;
let diagonal_pos = (grid_x + grid_y) * 0.5;
let wave_value = sin(diagonal_pos + wave_phase);
let wave_normalized = (wave_value + 1.0) * 0.5;
let color1 = vec3<f32>(1.0, 0.3, 0.7);
let color2 = vec3<f32>(0.5, 0.1, 1.0);
let intense_color1 = vec3<f32>(1.0, 0.1, 0.9);
let intense_color2 = vec3<f32>(0.8, 0.0, 1.0);
let animated_color1 = mix(color1, intense_color1, wave_normalized);
let animated_color2 = mix(color2, intense_color2, wave_normalized);
dot_color = mix(animated_color1, animated_color2, is_even);
let size_mod = 0.15 * wave_value;
dist_from_center = dist_from_center * (1.0 - size_mod);
// Animate whether something is a dot by position but also time
is_dot = step(dist_from_center, 0.3 + wave_normalized * 0.2);
}
return vec4<f32>(dot_color * is_dot, 1.0);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,105 +1,73 @@
[package]
name = "benches"
edition = "2024"
edition = "2021"
description = "Benchmarks that test Bevy's performance"
publish = false
license = "MIT OR Apache-2.0"
# Do not automatically discover benchmarks, we specify them manually instead.
autobenches = false
[dependencies]
# The primary crate that runs and analyzes our benchmarks. This is a regular dependency because the
# `bench!` macro refers to it in its documentation.
criterion = { version = "0.5.1", features = ["html_reports"] }
[dev-dependencies]
# Bevy crates
bevy_app = { path = "../crates/bevy_app" }
bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] }
bevy_math = { path = "../crates/bevy_math" }
bevy_picking = { path = "../crates/bevy_picking", features = [
"bevy_mesh_picking_backend",
] }
bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] }
bevy_render = { path = "../crates/bevy_render" }
bevy_tasks = { path = "../crates/bevy_tasks" }
bevy_utils = { path = "../crates/bevy_utils" }
bevy_platform = { path = "../crates/bevy_platform", default-features = false, features = [
"std",
] }
# Other crates
glam = "0.29"
glam = "0.27"
rand = "0.8"
rand_chacha = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
bevy_app = { path = "../crates/bevy_app" }
bevy_ecs = { path = "../crates/bevy_ecs", features = ["multi_threaded"] }
bevy_reflect = { path = "../crates/bevy_reflect" }
bevy_tasks = { path = "../crates/bevy_tasks" }
bevy_utils = { path = "../crates/bevy_utils" }
bevy_math = { path = "../crates/bevy_math" }
bevy_render = { path = "../crates/bevy_render" }
# Make `bevy_render` compile on Linux with x11 windowing. x11 vs. Wayland does not matter here
# because the benches do not actually open any windows.
[target.'cfg(target_os = "linux")'.dev-dependencies]
bevy_winit = { path = "../crates/bevy_winit", features = ["x11"] }
[profile.release]
opt-level = 3
lto = true
[lints.clippy]
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
semicolon_if_nothing_returned = "warn"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
needless_lifetimes = "allow"
too_many_arguments = "allow"
nonstandard_macro_braces = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
ref_as_ptr = "warn"
# see: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366966219
too_long_first_doc_paragraph = "allow"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lib]
# This fixes the "Unrecognized Option" error when running commands like
# `cargo bench -- --save-baseline before` by disabling the default benchmark harness.
# See <https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options>
# for more information.
bench = false
[[bench]]
name = "change_detection"
path = "benches/bevy_ecs/change_detection.rs"
harness = false
[[bench]]
name = "ecs"
path = "benches/bevy_ecs/main.rs"
path = "benches/bevy_ecs/benches.rs"
harness = false
[[bench]]
name = "math"
path = "benches/bevy_math/main.rs"
name = "reflect_list"
path = "benches/bevy_reflect/list.rs"
harness = false
[[bench]]
name = "picking"
path = "benches/bevy_picking/main.rs"
name = "reflect_map"
path = "benches/bevy_reflect/map.rs"
harness = false
[[bench]]
name = "reflect"
path = "benches/bevy_reflect/main.rs"
name = "reflect_struct"
path = "benches/bevy_reflect/struct.rs"
harness = false
[[bench]]
name = "render"
path = "benches/bevy_render/main.rs"
name = "parse_reflect_path"
path = "benches/bevy_reflect/path.rs"
harness = false
[[bench]]
name = "tasks"
path = "benches/bevy_tasks/main.rs"
name = "iter"
path = "benches/bevy_tasks/iter.rs"
harness = false
[[bench]]
name = "bezier"
path = "benches/bevy_math/bezier.rs"
harness = false
[[bench]]
name = "torus"
path = "benches/bevy_render/torus.rs"
harness = false
[[bench]]
name = "entity_hash"
path = "benches/bevy_ecs/world/entity_hash.rs"
harness = false

View File

@ -1,35 +1,27 @@
# Bevy Benchmarks
This is a crate with a collection of benchmarks for Bevy.
This is a crate with a collection of benchmarks for Bevy, separate from the rest of the Bevy crates.
## Running benchmarks
## Running the benchmarks
Benchmarks can be run through Cargo:
1. Setup everything you need for Bevy with the [setup guide](https://bevyengine.org/learn/book/getting-started/setup/).
2. Move into the `benches` directory (where this README is located).
```sh
# Run all benchmarks. (This will take a while!)
cargo bench -p benches
```sh
bevy $ cd benches
```
# Just compile the benchmarks, do not run them.
cargo bench -p benches --no-run
3. Run the benchmarks with cargo (This will take a while)
# Run the benchmarks for a specific crate. (See `Cargo.toml` for a complete list of crates
# tracked.)
cargo bench -p benches --bench ecs
```sh
bevy/benches $ cargo bench
```
# Filter which benchmarks are run based on the name. This will only run benchmarks whose name
# contains "name_fragment".
cargo bench -p benches -- name_fragment
If you'd like to only compile the benchmarks (without running them), you can do that like this:
# List all available benchmarks.
cargo bench -p benches -- --list
# Save a baseline to be compared against later.
cargo bench -p benches --save-baseline before
# Compare the current benchmarks against a baseline to find performance gains and regressions.
cargo bench -p benches --baseline before
```
```sh
bevy/benches $ cargo bench --no-run
```
## Criterion

View File

@ -0,0 +1,15 @@
use criterion::criterion_main;
mod components;
mod events;
mod iteration;
mod scheduling;
mod world;
criterion_main!(
components::components_benches,
events::event_benches,
iteration::iterations_benches,
scheduling::scheduling_benches,
world::world_benches,
);

View File

@ -1,13 +1,10 @@
use core::hint::black_box;
use bevy_ecs::{
component::{Component, Mutable},
component::Component,
entity::Entity,
prelude::{Added, Changed, EntityWorldMut, QueryState},
query::QueryFilter,
prelude::{Added, Changed},
world::World,
};
use criterion::{criterion_group, Criterion};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::{prelude::SliceRandom, SeedableRng};
use rand_chacha::ChaCha8Rng;
@ -17,27 +14,15 @@ criterion_group!(
all_changed_detection,
few_changed_detection,
none_changed_detection,
multiple_archetype_none_changed_detection
);
criterion_main!(benches);
macro_rules! modify {
($components:ident;$($index:tt),*) => {
$(
$components.$index.map(|mut v| {
v.0+=1.
});
)*
};
}
#[derive(Component, Default)]
#[component(storage = "Table")]
struct Table(f32);
#[derive(Component, Default)]
#[component(storage = "SparseSet")]
struct Sparse(f32);
#[derive(Component, Default)]
#[component(storage = "Table")]
struct Data<const X: u16>(f32);
trait BenchModify {
fn bench_modify(&mut self) -> f32;
@ -56,7 +41,7 @@ impl BenchModify for Sparse {
}
}
const ENTITIES_TO_BENCH_COUNT: &[u32] = &[5000, 50000];
const RANGE_ENTITIES_TO_BENCH_COUNT: std::ops::Range<u32> = 5..7;
type BenchGroup<'a> = criterion::BenchmarkGroup<'a, criterion::measurement::WallTime>;
@ -70,11 +55,6 @@ fn setup<T: Component + Default>(entity_count: u32) -> World {
black_box(world)
}
// create a cached query in setup to avoid extra costs in each iter
fn generic_filter_query<F: QueryFilter>(world: &mut World) -> QueryState<Entity, F> {
world.query_filtered::<Entity, F>()
}
fn generic_bench<P: Copy>(
bench_group: &mut BenchGroup,
mut benches: Vec<Box<dyn FnMut(&mut BenchGroup, P)>>,
@ -87,17 +67,14 @@ fn generic_bench<P: Copy>(
fn all_added_detection_generic<T: Component + Default>(group: &mut BenchGroup, entity_count: u32) {
group.bench_function(
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|bencher| {
bencher.iter_batched_ref(
|| {
let mut world = setup::<T>(entity_count);
let query = generic_filter_query::<Added<T>>(&mut world);
(world, query)
},
|(world, query)| {
bencher.iter_batched(
|| setup::<T>(entity_count),
|mut world| {
let mut count = 0;
for entity in query.iter(world) {
let mut query = world.query_filtered::<Entity, Added<T>>();
for entity in query.iter(&world) {
black_box(entity);
count += 1;
}
@ -111,9 +88,9 @@ fn all_added_detection_generic<T: Component + Default>(group: &mut BenchGroup, e
fn all_added_detection(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("all_added_detection");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for &entity_count in ENTITIES_TO_BENCH_COUNT {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
generic_bench(
&mut group,
vec![
@ -125,14 +102,14 @@ fn all_added_detection(criterion: &mut Criterion) {
}
}
fn all_changed_detection_generic<T: Component<Mutability = Mutable> + Default + BenchModify>(
fn all_changed_detection_generic<T: Component + Default + BenchModify>(
group: &mut BenchGroup,
entity_count: u32,
) {
group.bench_function(
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|bencher| {
bencher.iter_batched_ref(
bencher.iter_batched(
|| {
let mut world = setup::<T>(entity_count);
world.clear_trackers();
@ -140,12 +117,12 @@ fn all_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
for mut component in query.iter_mut(&mut world) {
black_box(component.bench_modify());
}
let query = generic_filter_query::<Changed<T>>(&mut world);
(world, query)
world
},
|(world, query)| {
|mut world| {
let mut count = 0;
for entity in query.iter(world) {
let mut query = world.query_filtered::<Entity, Changed<T>>();
for entity in query.iter(&world) {
black_box(entity);
count += 1;
}
@ -159,9 +136,9 @@ fn all_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
fn all_changed_detection(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("all_changed_detection");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for &entity_count in ENTITIES_TO_BENCH_COUNT {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
generic_bench(
&mut group,
vec![
@ -173,16 +150,16 @@ fn all_changed_detection(criterion: &mut Criterion) {
}
}
fn few_changed_detection_generic<T: Component<Mutability = Mutable> + Default + BenchModify>(
fn few_changed_detection_generic<T: Component + Default + BenchModify>(
group: &mut BenchGroup,
entity_count: u32,
) {
let ratio_to_modify = 0.1;
let amount_to_modify = (entity_count as f32 * ratio_to_modify) as usize;
group.bench_function(
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|bencher| {
bencher.iter_batched_ref(
bencher.iter_batched(
|| {
let mut world = setup::<T>(entity_count);
world.clear_trackers();
@ -193,11 +170,11 @@ fn few_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
for component in to_modify[0..amount_to_modify].iter_mut() {
black_box(component.bench_modify());
}
let query = generic_filter_query::<Changed<T>>(&mut world);
(world, query)
world
},
|(world, query)| {
for entity in query.iter(world) {
|mut world| {
let mut query = world.query_filtered::<Entity, Changed<T>>();
for entity in query.iter(&world) {
black_box(entity);
}
},
@ -209,9 +186,9 @@ fn few_changed_detection_generic<T: Component<Mutability = Mutable> + Default +
fn few_changed_detection(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("few_changed_detection");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for &entity_count in ENTITIES_TO_BENCH_COUNT {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
generic_bench(
&mut group,
vec![
@ -223,23 +200,23 @@ fn few_changed_detection(criterion: &mut Criterion) {
}
}
fn none_changed_detection_generic<T: Component<Mutability = Mutable> + Default>(
fn none_changed_detection_generic<T: Component + Default>(
group: &mut BenchGroup,
entity_count: u32,
) {
group.bench_function(
format!("{}_entities_{}", entity_count, core::any::type_name::<T>()),
format!("{}_entities_{}", entity_count, std::any::type_name::<T>()),
|bencher| {
bencher.iter_batched_ref(
bencher.iter_batched(
|| {
let mut world = setup::<T>(entity_count);
world.clear_trackers();
let query = generic_filter_query::<Changed<T>>(&mut world);
(world, query)
world
},
|(world, query)| {
|mut world| {
let mut count = 0;
for entity in query.iter(world) {
let mut query = world.query_filtered::<Entity, Changed<T>>();
for entity in query.iter(&world) {
black_box(entity);
count += 1;
}
@ -253,9 +230,9 @@ fn none_changed_detection_generic<T: Component<Mutability = Mutable> + Default>(
fn none_changed_detection(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("none_changed_detection");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for &entity_count in ENTITIES_TO_BENCH_COUNT {
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE_ENTITIES_TO_BENCH_COUNT.map(|i| i * 10_000) {
generic_bench(
&mut group,
vec![
@ -266,113 +243,3 @@ fn none_changed_detection(criterion: &mut Criterion) {
);
}
}
fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {
if i & (1 << B) != 0 {
entity.insert(Data::<B>(1.0));
}
}
fn add_archetypes_entities<T: Component<Mutability = Mutable> + Default>(
world: &mut World,
archetype_count: u16,
entity_count: u32,
) {
for i in 0..archetype_count {
for _j in 0..entity_count {
let mut e = world.spawn(T::default());
insert_if_bit_enabled::<0>(&mut e, i);
insert_if_bit_enabled::<1>(&mut e, i);
insert_if_bit_enabled::<2>(&mut e, i);
insert_if_bit_enabled::<3>(&mut e, i);
insert_if_bit_enabled::<4>(&mut e, i);
insert_if_bit_enabled::<5>(&mut e, i);
insert_if_bit_enabled::<6>(&mut e, i);
insert_if_bit_enabled::<7>(&mut e, i);
insert_if_bit_enabled::<8>(&mut e, i);
insert_if_bit_enabled::<9>(&mut e, i);
insert_if_bit_enabled::<10>(&mut e, i);
insert_if_bit_enabled::<11>(&mut e, i);
insert_if_bit_enabled::<12>(&mut e, i);
insert_if_bit_enabled::<13>(&mut e, i);
insert_if_bit_enabled::<14>(&mut e, i);
insert_if_bit_enabled::<15>(&mut e, i);
}
}
}
fn multiple_archetype_none_changed_detection_generic<
T: Component<Mutability = Mutable> + Default + BenchModify,
>(
group: &mut BenchGroup,
archetype_count: u16,
entity_count: u32,
) {
group.bench_function(
format!(
"{}_archetypes_{}_entities_{}",
archetype_count,
entity_count,
core::any::type_name::<T>()
),
|bencher| {
bencher.iter_batched_ref(
|| {
let mut world = World::new();
add_archetypes_entities::<T>(&mut world, archetype_count, entity_count);
world.clear_trackers();
let mut query = world.query::<(
Option<&mut Data<0>>,
Option<&mut Data<1>>,
Option<&mut Data<2>>,
Option<&mut Data<3>>,
Option<&mut Data<4>>,
Option<&mut Data<5>>,
Option<&mut Data<6>>,
Option<&mut Data<7>>,
Option<&mut Data<8>>,
Option<&mut Data<9>>,
Option<&mut Data<10>>,
Option<&mut Data<11>>,
Option<&mut Data<12>>,
Option<&mut Data<13>>,
Option<&mut Data<14>>,
)>();
for components in query.iter_mut(&mut world) {
// change Data<X> while keeping T unchanged
modify!(components;0,1,2,3,4,5,6,7,8,9,10,11,12,13,14);
}
let query = generic_filter_query::<Changed<T>>(&mut world);
(world, query)
},
|(world, query)| {
let mut count = 0;
for entity in query.iter(world) {
black_box(entity);
count += 1;
}
assert_eq!(0, count);
},
criterion::BatchSize::LargeInput,
);
},
);
}
fn multiple_archetype_none_changed_detection(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("multiple_archetypes_none_changed_detection");
group.warm_up_time(core::time::Duration::from_millis(800));
group.measurement_time(core::time::Duration::from_secs(8));
for archetype_count in [5, 20, 100] {
for entity_count in [10, 100, 1000, 10000] {
multiple_archetype_none_changed_detection_generic::<Table>(
&mut group,
archetype_count,
entity_count,
);
multiple_archetype_none_changed_detection_generic::<Sparse>(
&mut group,
archetype_count,
entity_count,
);
}
}
}

View File

@ -1,6 +1,6 @@
use bevy_ecs::prelude::*;
use bevy::prelude::*;
#[derive(Component, Clone)]
#[derive(Component)]
struct A(f32);
#[derive(Component)]
struct B(f32);
@ -12,18 +12,19 @@ impl Benchmark {
let mut world = World::default();
let entities = world
.spawn_batch(core::iter::repeat_n(A(0.), 10_000))
.collect();
.spawn_batch((0..10000).map(|_| A(0.0)))
.collect::<Vec<_>>();
Self(world, entities)
}
pub fn run(&mut self) {
for entity in &self.1 {
self.0.entity_mut(*entity).insert(B(0.));
self.0.insert_one(*entity, B(0.0)).unwrap();
}
for entity in &self.1 {
self.0.entity_mut(*entity).remove::<B>();
self.0.remove_one::<B>(*entity).unwrap();
}
}
}

View File

@ -1,114 +0,0 @@
#![expect(
dead_code,
reason = "The `Mat4`s in the structs are used to bloat the size of the structs for benchmarking purposes."
)]
use bevy_ecs::prelude::*;
use glam::*;
#[derive(Component, Copy, Clone)]
struct A<const N: usize>(Mat4);
#[derive(Component, Copy, Clone)]
struct B<const N: usize>(Mat4);
#[derive(Component, Copy, Clone)]
struct C<const N: usize>(Mat4);
#[derive(Component, Copy, Clone)]
struct D<const N: usize>(Mat4);
#[derive(Component, Copy, Clone)]
struct E<const N: usize>(Mat4);
#[derive(Component, Copy, Clone)]
struct F<const N: usize>(Mat4);
#[derive(Component, Copy, Clone)]
struct Z<const N: usize>;
pub struct Benchmark(World, Vec<Entity>);
impl Benchmark {
pub fn new() -> Self {
let mut world = World::default();
let mut entities = Vec::with_capacity(10_000);
for _ in 0..10_000 {
entities.push(
world
.spawn((
(
A::<1>(Mat4::from_scale(Vec3::ONE)),
B::<1>(Mat4::from_scale(Vec3::ONE)),
C::<1>(Mat4::from_scale(Vec3::ONE)),
D::<1>(Mat4::from_scale(Vec3::ONE)),
E::<1>(Mat4::from_scale(Vec3::ONE)),
A::<2>(Mat4::from_scale(Vec3::ONE)),
B::<2>(Mat4::from_scale(Vec3::ONE)),
C::<2>(Mat4::from_scale(Vec3::ONE)),
D::<2>(Mat4::from_scale(Vec3::ONE)),
E::<2>(Mat4::from_scale(Vec3::ONE)),
),
(
A::<3>(Mat4::from_scale(Vec3::ONE)),
B::<3>(Mat4::from_scale(Vec3::ONE)),
C::<3>(Mat4::from_scale(Vec3::ONE)),
D::<3>(Mat4::from_scale(Vec3::ONE)),
E::<3>(Mat4::from_scale(Vec3::ONE)),
A::<4>(Mat4::from_scale(Vec3::ONE)),
B::<4>(Mat4::from_scale(Vec3::ONE)),
C::<4>(Mat4::from_scale(Vec3::ONE)),
D::<4>(Mat4::from_scale(Vec3::ONE)),
E::<4>(Mat4::from_scale(Vec3::ONE)),
),
(
A::<5>(Mat4::from_scale(Vec3::ONE)),
B::<5>(Mat4::from_scale(Vec3::ONE)),
C::<5>(Mat4::from_scale(Vec3::ONE)),
D::<5>(Mat4::from_scale(Vec3::ONE)),
E::<5>(Mat4::from_scale(Vec3::ONE)),
A::<6>(Mat4::from_scale(Vec3::ONE)),
B::<6>(Mat4::from_scale(Vec3::ONE)),
C::<6>(Mat4::from_scale(Vec3::ONE)),
D::<6>(Mat4::from_scale(Vec3::ONE)),
E::<6>(Mat4::from_scale(Vec3::ONE)),
),
(
A::<7>(Mat4::from_scale(Vec3::ONE)),
B::<7>(Mat4::from_scale(Vec3::ONE)),
C::<7>(Mat4::from_scale(Vec3::ONE)),
D::<7>(Mat4::from_scale(Vec3::ONE)),
E::<7>(Mat4::from_scale(Vec3::ONE)),
Z::<1>,
Z::<2>,
Z::<3>,
Z::<4>,
Z::<5>,
Z::<6>,
Z::<7>,
),
))
.id(),
);
}
Self(world, entities)
}
pub fn run(&mut self) {
for entity in &self.1 {
self.0.entity_mut(*entity).insert((
F::<1>(Mat4::from_scale(Vec3::ONE)),
F::<2>(Mat4::from_scale(Vec3::ONE)),
F::<3>(Mat4::from_scale(Vec3::ONE)),
F::<4>(Mat4::from_scale(Vec3::ONE)),
F::<5>(Mat4::from_scale(Vec3::ONE)),
F::<6>(Mat4::from_scale(Vec3::ONE)),
F::<7>(Mat4::from_scale(Vec3::ONE)),
));
}
for entity in &self.1 {
self.0
.entity_mut(*entity)
.remove::<(F<1>, F<2>, F<3>, F<4>, F<5>, F<6>, F<7>)>();
self.0
.entity_mut(*entity)
.remove::<(Z<1>, Z<2>, Z<3>, Z<4>, Z<5>, Z<6>, Z<7>)>();
}
}
}

View File

@ -22,7 +22,7 @@ fn setup(system_count: usize) -> (World, Schedule) {
}
fn insert_if_bit_enabled<const B: u16>(entity: &mut EntityWorldMut, i: u16) {
if i & (1 << B) != 0 {
if i & 1 << B != 0 {
entity.insert(A::<B>(1.0));
}
}

View File

@ -1,21 +1,19 @@
mod add_remove;
use criterion::*;
mod add_remove_big_sparse_set;
mod add_remove_big_table;
mod add_remove_sparse_set;
mod add_remove_table;
mod add_remove_very_big_table;
mod archetype_updates;
mod insert_simple;
mod insert_simple_unbatched;
use archetype_updates::*;
use criterion::{criterion_group, Criterion};
criterion_group!(
benches,
components_benches,
add_remove,
add_remove_big,
add_remove_very_big,
insert_simple,
no_archetypes,
added_archetypes,
@ -23,8 +21,8 @@ criterion_group!(
fn add_remove(c: &mut Criterion) {
let mut group = c.benchmark_group("add_remove");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
group.bench_function("table", |b| {
let mut bench = add_remove_table::Benchmark::new();
b.iter(move || bench.run());
@ -38,8 +36,8 @@ fn add_remove(c: &mut Criterion) {
fn add_remove_big(c: &mut Criterion) {
let mut group = c.benchmark_group("add_remove_big");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
group.bench_function("table", |b| {
let mut bench = add_remove_big_table::Benchmark::new();
b.iter(move || bench.run());
@ -51,21 +49,10 @@ fn add_remove_big(c: &mut Criterion) {
group.finish();
}
fn add_remove_very_big(c: &mut Criterion) {
let mut group = c.benchmark_group("add_remove_very_big");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.bench_function("table", |b| {
let mut bench = add_remove_very_big_table::Benchmark::new();
b.iter(move || bench.run());
});
group.finish();
}
fn insert_simple(c: &mut Criterion) {
let mut group = c.benchmark_group("insert_simple");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
group.bench_function("base", |b| {
let mut bench = insert_simple::Benchmark::new();
b.iter(move || bench.run());

View File

@ -1,9 +1,9 @@
use core::hint::black_box;
use bevy_ecs::{component::Component, prelude::*, schedule::ExecutorKind, world::World};
use criterion::{criterion_group, BenchmarkId, Criterion};
use bevy_ecs::{component::Component, prelude::*, world::World};
use bevy_tasks::{ComputeTaskPool, TaskPool};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
criterion_group!(benches, empty_archetypes);
criterion_main!(benches);
#[derive(Component)]
struct A<const N: u16>(f32);
@ -47,12 +47,13 @@ fn for_each(
&A<12>,
)>,
) {
query.iter().for_each(|comp| {
query.for_each(|comp| {
black_box(comp);
});
}
fn par_for_each(
task_pool: Res<ComputeTaskPool>,
query: Query<(
&A<0>,
&A<1>,
@ -69,29 +70,25 @@ fn par_for_each(
&A<12>,
)>,
) {
query.par_iter().for_each(|comp| {
query.par_for_each(&*task_pool, 64, |comp| {
black_box(comp);
});
}
fn setup(parallel: bool, setup: impl FnOnce(&mut Schedule)) -> (World, Schedule) {
let world = World::new();
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.set_executor_kind(match parallel {
true => ExecutorKind::MultiThreaded,
false => ExecutorKind::SingleThreaded,
});
if parallel {
world.insert_resource(ComputeTaskPool(TaskPool::default()));
}
setup(&mut schedule);
(world, schedule)
}
/// create `count` entities with distinct archetypes
fn add_archetypes(world: &mut World, count: u16) {
for i in 0..count {
let mut e = world.spawn_empty();
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
@ -105,49 +102,49 @@ fn add_archetypes(world: &mut World, count: u16) {
e.insert(A::<10>(1.0));
e.insert(A::<11>(1.0));
e.insert(A::<12>(1.0));
if i & (1 << 1) != 0 {
if i & 1 << 1 != 0 {
e.insert(A::<13>(1.0));
}
if i & (1 << 2) != 0 {
if i & 1 << 2 != 0 {
e.insert(A::<14>(1.0));
}
if i & (1 << 3) != 0 {
if i & 1 << 3 != 0 {
e.insert(A::<15>(1.0));
}
if i & (1 << 4) != 0 {
if i & 1 << 4 != 0 {
e.insert(A::<16>(1.0));
}
if i & (1 << 5) != 0 {
if i & 1 << 5 != 0 {
e.insert(A::<18>(1.0));
}
if i & (1 << 6) != 0 {
if i & 1 << 6 != 0 {
e.insert(A::<19>(1.0));
}
if i & (1 << 7) != 0 {
if i & 1 << 7 != 0 {
e.insert(A::<20>(1.0));
}
if i & (1 << 8) != 0 {
if i & 1 << 8 != 0 {
e.insert(A::<21>(1.0));
}
if i & (1 << 9) != 0 {
if i & 1 << 9 != 0 {
e.insert(A::<22>(1.0));
}
if i & (1 << 10) != 0 {
if i & 1 << 10 != 0 {
e.insert(A::<23>(1.0));
}
if i & (1 << 11) != 0 {
if i & 1 << 11 != 0 {
e.insert(A::<24>(1.0));
}
if i & (1 << 12) != 0 {
if i & 1 << 12 != 0 {
e.insert(A::<25>(1.0));
}
if i & (1 << 13) != 0 {
if i & 1 << 13 != 0 {
e.insert(A::<26>(1.0));
}
if i & (1 << 14) != 0 {
if i & 1 << 14 != 0 {
e.insert(A::<27>(1.0));
}
if i & (1 << 15) != 0 {
if i & 1 << 15 != 0 {
e.insert(A::<28>(1.0));
}
}
@ -161,7 +158,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
let mut e = world.spawn_empty();
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
@ -182,7 +179,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|bencher, &_| {
bencher.iter(|| {
schedule.run(&mut world);
});
})
},
);
}
@ -192,7 +189,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
let mut e = world.spawn_empty();
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
@ -213,7 +210,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|bencher, &_| {
bencher.iter(|| {
schedule.run(&mut world);
});
})
},
);
}
@ -223,7 +220,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
});
add_archetypes(&mut world, archetype_count);
world.clear_entities();
let mut e = world.spawn_empty();
let mut e = world.spawn();
e.insert(A::<0>(1.0));
e.insert(A::<1>(1.0));
e.insert(A::<2>(1.0));
@ -244,7 +241,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|bencher, &_| {
bencher.iter(|| {
schedule.run(&mut world);
});
})
},
);
}

View File

@ -1,238 +0,0 @@
use core::hint::black_box;
use benches::bench;
use bevy_ecs::bundle::Bundle;
use bevy_ecs::component::ComponentCloneBehavior;
use bevy_ecs::entity::EntityCloner;
use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::reflect::AppTypeRegistry;
use bevy_ecs::{component::Component, world::World};
use bevy_math::Mat4;
use bevy_reflect::{GetTypeRegistration, Reflect};
use criterion::{criterion_group, Bencher, Criterion, Throughput};
criterion_group!(
benches,
single,
hierarchy_tall,
hierarchy_wide,
hierarchy_many,
);
#[derive(Component, Reflect, Default, Clone)]
struct C1(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C2(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C3(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C4(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C5(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C6(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C7(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C8(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C9(Mat4);
#[derive(Component, Reflect, Default, Clone)]
struct C10(Mat4);
type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10);
/// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to
/// use the [`Reflect`] trait instead of [`Clone`].
fn reflection_cloner<B: Bundle + GetTypeRegistration>(
world: &mut World,
linked_cloning: bool,
) -> EntityCloner {
// Get mutable access to the type registry, creating it if it does not exist yet.
let registry = world.get_resource_or_init::<AppTypeRegistry>();
// Recursively register all components in the bundle to the reflection type registry.
{
let mut r = registry.write();
r.register::<B>();
}
// Recursively register all components in the bundle, then save the component IDs to a list.
// This uses `contributed_components()`, meaning both explicit and required component IDs in
// this bundle are saved.
let component_ids: Vec<_> = world.register_bundle::<B>().contributed_components().into();
let mut builder = EntityCloner::build(world);
// Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`.
for component in component_ids {
builder.override_clone_behavior_with_id(component, ComponentCloneBehavior::reflect());
}
builder.linked_cloning(linked_cloning);
builder.finish()
}
/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
/// bundle `B`.
///
/// The bundle must implement [`Default`], which is used to create the first entity that gets cloned
/// in the benchmark.
///
/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneHandler`] for all
/// components (which is usually [`ComponentCloneHandler::clone_handler()`]). If `clone_via_reflect`
/// is true, it will overwrite the handler for all components in the bundle to be
/// [`ComponentCloneHandler::reflect_handler()`].
fn bench_clone<B: Bundle + Default + GetTypeRegistration>(
b: &mut Bencher,
clone_via_reflect: bool,
) {
let mut world = World::default();
let mut cloner = if clone_via_reflect {
reflection_cloner::<B>(&mut world, false)
} else {
EntityCloner::default()
};
// Spawn the first entity, which will be cloned in the benchmark routine.
let id = world.spawn(B::default()).id();
b.iter(|| {
// clones the given entity
cloner.spawn_clone(&mut world, black_box(id));
world.flush();
});
}
/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
/// bundle `B`.
///
/// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several
/// children. It does so by setting up an entity tree with a given `height` where each entity has a
/// specified number of `children`.
///
/// For example, setting `height` to 5 and `children` to 1 creates a single chain of entities with
/// no siblings. Alternatively, setting `height` to 1 and `children` to 5 will spawn 5 direct
/// children of the root entity.
fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
b: &mut Bencher,
height: usize,
children: usize,
clone_via_reflect: bool,
) {
let mut world = World::default();
let mut cloner = if clone_via_reflect {
reflection_cloner::<B>(&mut world, true)
} else {
let mut builder = EntityCloner::build(&mut world);
builder.linked_cloning(true);
builder.finish()
};
// Make the clone command recursive, so children are cloned as well.
// Spawn the first entity, which will be cloned in the benchmark routine.
let id = world.spawn(B::default()).id();
let mut hierarchy_level = vec![id];
// Set up the hierarchy tree by spawning all children.
for _ in 0..height {
let current_hierarchy_level = hierarchy_level.clone();
hierarchy_level.clear();
for parent in current_hierarchy_level {
for _ in 0..children {
let child_id = world.spawn((B::default(), ChildOf(parent))).id();
hierarchy_level.push(child_id);
}
}
}
b.iter(|| {
cloner.spawn_clone(&mut world, black_box(id));
world.flush();
});
}
// Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This
// constant represents this as an easy array that can be used in a `for` loop.
const SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)];
/// Benchmarks cloning a single entity with 10 components and no children.
fn single(c: &mut Criterion) {
let mut group = c.benchmark_group(bench!("single"));
// We're cloning 1 entity.
group.throughput(Throughput::Elements(1));
for (id, clone_via_reflect) in SCENARIOS {
group.bench_function(id, |b| {
bench_clone::<ComplexBundle>(b, clone_via_reflect);
});
}
group.finish();
}
/// Benchmarks cloning an an entity and its 50 descendents, each with only 1 component.
fn hierarchy_tall(c: &mut Criterion) {
let mut group = c.benchmark_group(bench!("hierarchy_tall"));
// We're cloning both the root entity and its 50 descendents.
group.throughput(Throughput::Elements(51));
for (id, clone_via_reflect) in SCENARIOS {
group.bench_function(id, |b| {
bench_clone_hierarchy::<C1>(b, 50, 1, clone_via_reflect);
});
}
group.finish();
}
/// Benchmarks cloning an an entity and its 50 direct children, each with only 1 component.
fn hierarchy_wide(c: &mut Criterion) {
let mut group = c.benchmark_group(bench!("hierarchy_wide"));
// We're cloning both the root entity and its 50 direct children.
group.throughput(Throughput::Elements(51));
for (id, clone_via_reflect) in SCENARIOS {
group.bench_function(id, |b| {
bench_clone_hierarchy::<C1>(b, 1, 50, clone_via_reflect);
});
}
group.finish();
}
/// Benchmarks cloning a large hierarchy of entities with several children each. Each entity has 10
/// components.
fn hierarchy_many(c: &mut Criterion) {
let mut group = c.benchmark_group(bench!("hierarchy_many"));
// We're cloning 364 entities total. This number was calculated by manually counting the number
// of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :)
group.throughput(Throughput::Elements(364));
for (id, clone_via_reflect) in SCENARIOS {
group.bench_function(id, |b| {
bench_clone_hierarchy::<ComplexBundle>(b, 5, 3, clone_via_reflect);
});
}
group.finish();
}

View File

@ -17,9 +17,9 @@ impl<const SIZE: usize> Benchmark<SIZE> {
}
pub fn run(&mut self) {
let mut reader = self.0.get_cursor();
let mut reader = self.0.get_reader();
for evt in reader.read(&self.0) {
core::hint::black_box(evt);
std::hint::black_box(evt);
}
}
}

View File

@ -1,14 +1,14 @@
use criterion::*;
mod iter;
mod send;
use criterion::{criterion_group, Criterion};
criterion_group!(benches, send, iter);
criterion_group!(event_benches, send, iter);
fn send(c: &mut Criterion) {
let mut group = c.benchmark_group("events_send");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for count in [100, 1000, 10000, 50000] {
group.bench_function(format!("size_4_events_{}", count), |b| {
let mut bench = send::Benchmark::<4>::new(count);
@ -32,8 +32,8 @@ fn send(c: &mut Criterion) {
fn iter(c: &mut Criterion) {
let mut group = c.benchmark_group("events_iter");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for count in [100, 1000, 10000, 50000] {
group.bench_function(format!("size_4_events_{}", count), |b| {
let mut bench = iter::Benchmark::<4>::new(count);

View File

@ -32,7 +32,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
pub fn run(&mut self) {
for _ in 0..self.count {
self.events
.send(core::hint::black_box(BenchEvent([0u8; SIZE])));
.send(std::hint::black_box(BenchEvent([0u8; SIZE])));
}
self.events.update();
}

View File

@ -1,99 +0,0 @@
use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemState;
use core::hint::black_box;
use criterion::*;
use glam::*;
criterion_group!(benches, iter_frag_empty);
#[derive(Component, Default)]
struct Table<const X: usize = 0>(usize);
#[derive(Component, Default)]
#[component(storage = "SparseSet")]
struct Sparse<const X: usize = 0>(usize);
fn flip_coin() -> bool {
rand::random::<bool>()
}
fn iter_frag_empty(c: &mut Criterion) {
let mut group = c.benchmark_group("iter_fragmented(4096)_empty");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.bench_function("foreach_table", |b| {
let mut world = World::new();
spawn_empty_frag_archetype::<Table>(&mut world);
let mut q: SystemState<Query<(Entity, &Table)>> =
SystemState::<Query<(Entity, &Table<0>)>>::new(&mut world);
let query = q.get(&world);
b.iter(move || {
let mut res = 0;
query.iter().for_each(|(e, t)| {
res += e.to_bits();
black_box(t);
});
});
});
group.bench_function("foreach_sparse", |b| {
let mut world = World::new();
spawn_empty_frag_archetype::<Sparse>(&mut world);
let mut q: SystemState<Query<(Entity, &Sparse)>> =
SystemState::<Query<(Entity, &Sparse<0>)>>::new(&mut world);
let query = q.get(&world);
b.iter(move || {
let mut res = 0;
query.iter().for_each(|(e, t)| {
res += e.to_bits();
black_box(t);
});
});
});
group.finish();
fn spawn_empty_frag_archetype<T: Component + Default>(world: &mut World) {
for i in 0..65536 {
let mut e = world.spawn_empty();
if flip_coin() {
e.insert(Table::<1>(0));
}
if flip_coin() {
e.insert(Table::<2>(0));
}
if flip_coin() {
e.insert(Table::<3>(0));
}
if flip_coin() {
e.insert(Table::<4>(0));
}
if flip_coin() {
e.insert(Table::<5>(0));
}
if flip_coin() {
e.insert(Table::<6>(0));
}
if flip_coin() {
e.insert(Table::<7>(0));
}
if flip_coin() {
e.insert(Table::<8>(0));
}
if flip_coin() {
e.insert(Table::<9>(0));
}
if flip_coin() {
e.insert(Table::<10>(0));
}
if flip_coin() {
e.insert(Table::<11>(0));
}
if flip_coin() {
e.insert(Table::<12>(0));
}
e.insert(T::default());
if i != 0 {
e.despawn();
}
}
}
}

View File

@ -17,8 +17,8 @@ pub fn heavy_compute(c: &mut Criterion) {
struct Transform(Mat4);
let mut group = c.benchmark_group("heavy_compute");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
group.bench_function("base", |b| {
ComputeTaskPool::get_or_init(TaskPool::default);

View File

@ -19,15 +19,15 @@ impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
world.spawn_batch(core::iter::repeat_n(
(
world.spawn_batch(
std::iter::repeat((
Transform(Mat4::from_scale(Vec3::ONE)),
Position(Vec3::X),
Rotation(Vec3::X),
Velocity(Vec3::X),
),
10_000,
));
))
.take(10_000),
);
let query = world.query::<(&Velocity, &mut Position)>();
Self(world, query)

View File

@ -19,15 +19,15 @@ impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
world.spawn_batch(core::iter::repeat_n(
(
world.spawn_batch(
std::iter::repeat((
Transform(Mat4::from_scale(Vec3::ONE)),
Position(Vec3::X),
Rotation(Vec3::X),
Velocity(Vec3::X),
),
10_000,
));
))
.take(10_000),
);
let query = world.query::<(&Velocity, &mut Position)>();
Self(world, query)

View File

@ -1,43 +0,0 @@
use bevy_ecs::prelude::*;
use rand::{prelude::SliceRandom, SeedableRng};
use rand_chacha::ChaCha8Rng;
#[derive(Component, Copy, Clone)]
struct TableData(f32);
#[derive(Component, Copy, Clone)]
#[component(storage = "SparseSet")]
struct SparseData(f32);
fn deterministic_rand() -> ChaCha8Rng {
ChaCha8Rng::seed_from_u64(42)
}
pub struct Benchmark<'w>(World, QueryState<(&'w mut TableData, &'w SparseData)>);
impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
let mut v = vec![];
for _ in 0..10000 {
world.spawn((TableData(0.0), SparseData(0.0)));
v.push(world.spawn(TableData(0.)).id());
}
// by shuffling ,randomize the archetype iteration order to significantly deviate from the table order. This maximizes the loss of cache locality during archetype-based iteration.
v.shuffle(&mut deterministic_rand());
for e in v.into_iter() {
world.entity_mut(e).despawn();
}
let query = world.query::<(&mut TableData, &SparseData)>();
Self(world, query)
}
#[inline(never)]
pub fn run(&mut self) {
self.1
.iter_mut(&mut self.0)
.for_each(|(mut v1, v2)| v1.0 += v2.0);
}
}

View File

@ -21,15 +21,15 @@ impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
world.spawn_batch(core::iter::repeat_n(
(
world.spawn_batch(
std::iter::repeat((
Transform(Mat4::from_scale(Vec3::ONE)),
Position(Vec3::X),
Rotation(Vec3::X),
Velocity(Vec3::X),
),
10_000,
));
))
.take(10_000),
);
let query = world.query::<(&Velocity, &mut Position)>();
Self(world, query)

View File

@ -33,8 +33,8 @@ impl<'w> Benchmark<'w> {
pub fn new() -> Self {
let mut world = World::new();
world.spawn_batch(core::iter::repeat_n(
(
world.spawn_batch(
std::iter::repeat((
Transform(Mat4::from_scale(Vec3::ONE)),
Rotation(Vec3::X),
Position::<0>(Vec3::X),
@ -47,9 +47,9 @@ impl<'w> Benchmark<'w> {
Velocity::<3>(Vec3::X),
Position::<4>(Vec3::X),
Velocity::<4>(Vec3::X),
),
10_000,
));
))
.take(10_000),
);
let query = world.query();
Self(world, query)

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