Compare commits

...

43 Commits

Author SHA1 Message Date
Carter Anderson
22e39c4abf Release 0.12.1 2023-11-29 17:10:24 -08:00
Carter Anderson
9ac0d9b50d Allow removing and reloading assets with live handles (#10785)
# Objective

Fixes #10444 

Currently manually removing an asset prevents it from being reloaded
while there are still active handles. Doing so will result in a panic,
because the storage entry has been marked as "empty / None" but the ID
is still assumed to be active by the asset server.

Patterns like `images.remove() -> asset_server.reload()` and
`images.remove() -> images.insert()` would fail if the handle was still
alive.

## Solution

Most of the groundwork for this was already laid in Bevy Asset V2. This
is largely just a matter of splitting out `remove` into two separate
operations:

* `remove_dropped`: remove the stored asset, invalidate the internal
Assets entry (preventing future insertions with the old id), and recycle
the id
* `remove_still_alive`: remove the stored asset, but leave the entry
otherwise untouched (and dont recycle the id).

`remove_still_alive` and `insert` can be called any number of times (in
any order) for an id until `remove_dropped` has been called, which will
invalidate the id.

From a user-facing perspective, there are no API changes and this is non
breaking. The public `Assets::remove` will internally call
`remove_still_alive`. `remove_dropped` can only be called by the
internal "handle management" system.

---

## Changelog

- Fix a bug preventing `Assets::remove` from blocking future inserts for
a specific `AssetIndex`.
2023-11-29 17:06:03 -08:00
robtfm
960af2b2c9 try_insert Aabbs (#10801)
# Objective

avoid panics from `calculate_bounds` systems if entities are despawned
in PostUpdate.

there's a running general discussion (#10166) about command panicking.
in the meantime we may as well fix up some cases where it's clear a
failure to insert is safe.

## Solution

change `.insert(aabb)` to `.try_insert(aabb)`
2023-11-29 17:05:25 -08:00
Gino Valente
220b08d379 Fix nested generics in Reflect derive (#10791)
# Objective

> Issue raised on
[Discord](https://discord.com/channels/691052431525675048/1002362493634629796/1179182488787103776)

Currently the following code fails due to a missing `TypePath` bound:

```rust
#[derive(Reflect)] struct Foo<T>(T);
#[derive(Reflect)] struct Bar<T>(Foo<T>);
#[derive(Reflect)] struct Baz<T>(Bar<Foo<T>>);
```

## Solution

Add `TypePath` to the per-field bounds instead of _just_ the generic
type parameter bounds.

### Related Work

It should be noted that #9046 would help make these kinds of issues
easier to work around and/or avoid entirely.

---

## Changelog

- Fixes missing `TypePath` requirement when deriving `Reflect` on nested
generics
2023-11-29 17:03:36 -08:00
Carter Anderson
8a8532bb53 Print precise and correct watch warnings (and only when necessary) (#10787)
# Objective

Fixes #10401 

## Solution

* Allow sources to register specific processed/unprocessed watch
warnings.
* Specify per-platform watch warnings. This removes the need to cover
all platform cases in one warning message.
* Only register watch warnings for the _processed_ embedded source, as
warning about watching unprocessed embedded isn't helpful.

---

## Changelog

- Asset sources can now register specific watch warnings.
2023-11-29 17:03:15 -08:00
Carter Anderson
a4b3467530 Ensure instance_index push constant is always used in prepass.wgsl (#10706)
# Objective

 Kind of helps #10509 

## Solution

Add a line to `prepass.wgsl` that ensure the `instance_index` push
constant is always used on WebGL 2. This is not a full fix, as the
_second_ a custom shader is used that doesn't use the push constant, the
breakage will resurface. We have satisfying medium term and long term
solutions. This is just a short term hack for 0.12.1 that will make more
cases work. See #10509 for more details.
2023-11-29 17:03:03 -08:00
Helix
d61660806c fix insert_reflect panic caused by clone_value (#10627)
# Objective

- `insert_reflect` relies on `reflect_type_path`, which doesn't gives
the actual type path for object created by `clone_value`, leading to an
unexpected panic. This is a workaround for it.
- Fix #10590 

## Solution

- Tries to get type path from `get_represented_type_info` if get failed
from `reflect_type_path`.

---

## Defect remaining

- `get_represented_type_info` implies a shortage on performance than
using `TypeRegistry`.
2023-11-29 17:02:53 -08:00
Carter Anderson
6d295036f2 Fix GLTF scene dependencies and make full scene renders predictable (#10745)
# Objective

Fixes #10688

There were a number of issues at play:

1. The GLTF loader was not registering Scene dependencies properly. They
were being registered at the root instead of on the scene assets. This
made `LoadedWithDependencies` fire immediately on load.
2. Recursive labeled assets _inside_ of labeled assets were not being
loaded. This only became relevant for scenes after fixing (1) because we
now add labeled assets to the nested scene `LoadContext` instead of the
root load context. I'm surprised nobody has hit this yet. I'm glad I
caught it before somebody hit it.
3. Accessing "loaded with dependencies" state on the Asset Server is
boilerplatey + error prone (because you need to manually query two
states).

## Solution

1. In GltfLoader, use a nested LoadContext for scenes and load
dependencies through that context.
2. In the `AssetServer`, load labeled assets recursively.
3. Added a simple `asset_server.is_loaded_with_dependencies(id)`

I also added some docs to `LoadContext` to help prevent this problem in
the future.

---

## Changelog

- Added `AssetServer::is_loaded_with_dependencies`
- Fixed GLTF Scene dependencies
- Fixed nested labeled assets not being loaded

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-29 17:02:28 -08:00
Zachary Harrold
86d0b8af9d Implement Drop for CommandQueue (#10746)
# Objective

- Fixes #10676, preventing a possible memory leak for commands which
owned resources.

## Solution

Implemented `Drop` for `CommandQueue`. This has been done entirely in
the private API of `CommandQueue`, ensuring no breaking changes. Also
added a unit test, `test_command_queue_inner_drop_early`, based on the
reproduction steps as outlined in #10676.

## Notes

I believe this can be applied to `0.12.1` as well, but I am uncertain of
the process to make that kind of change. Please let me know if there's
anything I can do to help with the back-porting of this change.

---------

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2023-11-29 17:01:57 -08:00
Giacomo Stevanato
f5ccf683d6 Remove a ptr-to-int cast in CommandQueue::apply (#10475)
# Objective

- `CommandQueue::apply` calculates the address of the end of the
internal buffer as a `usize` rather than as a pointer, requiring two
casts of `cursor` to `usize`. Casting pointers to integers is generally
discouraged and may also prevent optimizations. It's also unnecessary
here.

## Solution

- Calculate the end address as a pointer rather than a `usize`.

Small note:

A trivial translation of the old code to use pointers would have
computed `end_addr` as `cursor.add(self.bytes.len())`, which is not
wrong but is an additional `unsafe` operation that also needs to be
properly documented and proven correct. However this operation is
already implemented in the form of the safe `as_mut_ptr_range`, so I
just used that.
2023-11-29 17:01:54 -08:00
Carter Anderson
79da6fd02f AssetMetaMode (#10623)
# Objective

Fixes #10157

## Solution

Add `AssetMetaCheck` resource which can configure when/if asset meta
files will be read:

```rust
app
  // Never attempts to look up meta files. The default meta configuration will be used for each asset.
  .insert_resource(AssetMetaCheck::Never)
  .add_plugins(DefaultPlugins)
```


This serves as a band-aid fix for the issue with wasm's
`HttpWasmAssetReader` creating a bunch of requests for non-existent
meta, which can slow down asset loading (by waiting for the 404
response) and creates a bunch of noise in the logs. This also provides a
band-aid fix for the more serious issue of itch.io deployments returning
403 responses, which results in full failure of asset loads.

If users don't want to include meta files for all deployed assets for
web builds, and they aren't using meta files at all, they should set
this to `AssetMetaCheck::Never`.

If users do want to include meta files for specific assets, they can use
`AssetMetaCheck::Paths`, which will only look up meta for those paths.

Currently, this defaults to `AssetMetaCheck::Always`, which makes this
fully non-breaking for the `0.12.1` release. _**However it _is_ worth
discussing making this `AssetMetaCheck::Never` by default**_, given that
I doubt most people are using meta files without the Asset Processor
enabled. This would be a breaking change, but it would make WASM / Itch
deployments work by default, which is a pretty big win imo. The downside
is that people using meta files _without_ processing would need to
manually enable `AssetMetaCheck::Always`, which is also suboptimal.

When in `AssetMetaCheck::Processed`, the meta check resource is ignored,
as processing requires asset meta files to function.

In general, I don't love adding this knob as things should ideally "just
work" in all cases. But this is the reality of the current situation.

---

## Changelog

- Added `AssetMetaCheck` resource, which can configure when/if asset
meta files will be read
2023-11-29 16:55:39 -08:00
Cameron
0c199dd74d Wait until FixedUpdate can see events before dropping them (#10077)
## Objective
 
Currently, events are dropped after two frames. This cadence wasn't
*chosen* for a specific reason, double buffering just lets events
persist for at least two frames. Events only need to be dropped at a
predictable point so that the event queues don't grow forever (i.e.
events should never cause a memory leak).
 
Events (and especially input events) need to be observable by systems in
`FixedUpdate`, but as-is events are dropped before those systems even
get a chance to see them.
 
## Solution
 
Instead of unconditionally dropping events in `First`, require
`FixedUpdate` to first queue the buffer swap (if the `TimePlugin` has
been installed). This way, events are only dropped after a frame that
runs `FixedUpdate`.
 
## Future Work

In the same way we have independent copies of `Time` for tracking time
in `Main` and `FixedUpdate`, we will need independent copies of `Input`
for tracking press/release status correctly in `Main` and `FixedUpdate`.

--
 
Every run of `FixedUpdate` covers a specific timespan. For example, if
the fixed timestep `Δt` is 10ms, the first three `FixedUpdate` runs
cover `[0ms, 10ms)`, `[10ms, 20ms)`, and `[20ms, 30ms)`.
 
`FixedUpdate` can run many times in one frame. For truly
framerate-independent behavior, each `FixedUpdate` should only see the
events that occurred in its covered timespan, but what happens right now
is the first step in the frame reads all pending events.

Fixing that will require timestamped events.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2023-11-29 16:55:21 -08:00
François
9cfc48145b don't run update before window creation in winit (#10741)
# Objective

- Window size, scale and position are not correct on the first execution
of the systems
- Fixes #10407,  fixes #10642

## Solution

- Don't run `update` before we get a chance to create the window in
winit
- Finish reverting #9826 after #10389
2023-11-29 16:54:08 -08:00
Aldrich Suratos
5c3c8e7189 Fix typo in resolve_outlines_system (#10730)
# Objective

Resolves  #10727.

`outline.width` was being assigned to `node.outline_offset` instead of
`outline.offset`.

## Solution

Changed `.width` to `.offset` in line 413.
2023-11-29 16:53:56 -08:00
Hank Jordan
d5b891ea73 Fix issue with Option serialization (#10705)
# Objective

- Fix #10499 

## Solution

- Use `.get_represented_type_info()` module path and type ident instead
of `.reflect_*` module path and type ident when serializing the `Option`
enum

---

## Changelog

- Fix serialization bug
- Add simple test
  - Add `serde_json` dev dependency
- Add `serde` with `derive` feature dev dependency (wouldn't compile for
me without it)

---------

Co-authored-by: hank <hank@hank.co.in>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2023-11-29 16:53:25 -08:00
robtfm
3cf377c490 Non uniform transmission samples (#10674)
# Objective

fix webgpu+chrome(119) textureSample in non-uniform control flow error

## Solution

modify view transmission texture sampling to use textureSampleLevel.
there are no mips for the view transmission texture, so this doesn't
change the result, but it removes the need for the samples to be in
uniform control flow.

note: in future we may add a mipchain to the transmission texture to
improve the blur effect. if uniformity analysis hasn't improved, this
would require switching to manual derivative calculations (which is
something we plan to do anyway).
2023-11-29 16:53:10 -08:00
ickshonpe
100767560f Make clipped areas of UI nodes non-interactive (#10454)
# Objective

Problems:

* The clipped, non-visible regions of UI nodes are interactive.
* `RelativeCursorPostion` is set relative to the visible part of the
node. It should be relative to the whole node.
* The `RelativeCursorPostion::mouse_over` method returns `true` when the
mouse is over a clipped part of a node.

fixes #10470

## Solution

Intersect a node's bounding rect with its clipping rect before checking
if it contains the cursor.

Added the field `normalized_visible_node_rect` to
`RelativeCursorPosition`. This is set to the bounds of the unclipped
area of the node rect by `ui_focus_system` expressed in normalized
coordinates relative to the entire node.

Instead of checking if the normalized cursor position lies within a unit
square, it instead checks if it is contained by
`normalized_visible_node_rect`.

Added outlines to the `overflow` example that appear when the cursor is
over the visible part of the images, but not the clipped area.

---

## Changelog

* `ui_focus_system` intersects a node's bounding rect with its clipping
rect before checking if mouse over.
* Added the field `normalized_visible_node_rect` to
`RelativeCursorPosition`. This is set to the bounds of the unclipped
area of the node rect by `ui_focus_system` expressed in normalized
coordinates relative to the entire node.
* `RelativeCursorPostion` is calculated relative to the whole node's
position and size, not only the visible part.
* `RelativeCursorPosition::mouse_over` only returns true when the mouse
is over an unclipped region of the UI node.
* Removed the `Deref` and `DerefMut` derives from
`RelativeCursorPosition` as it is no longer a single field struct.
* Added some outlines to the `overflow` example that respond to
`Interaction` changes.

## Migration Guide

The clipped areas of UI nodes are no longer interactive.

`RelativeCursorPostion` is now calculated relative to the whole node's
position and size, not only the visible part. Its `mouse_over` method
only returns true when the cursor is over an unclipped part of the node.

`RelativeCursorPosition` no longer implements `Deref` and `DerefMut`.
2023-11-29 16:53:01 -08:00
robtfm
5581926ad3 derive asset for enums (#10410)
# Objective

allow deriving `Asset` for enums, and for tuple structs.

## Solution

add to the proc macro, generating visitor calls to the variant's data
(if required).

supports unnamed or named field variants, and struct variants when the
struct also derives `Asset`:
```rust
#[derive(Asset, TypePath)]
pub enum MyAssetEnum {
    Unnamed (
        #[dependency]
        Handle<Image>
    ),
    Named {
        #[dependency]
        array_handle: Handle<Image>,
        #[dependency]
        atlas_handles: Vec<Handle<Image>>,
    },
    StructStyle(
        #[dependency]
        VariantStruct
    ),
    Empty,
}

#[derive(Asset, TypePath)]
pub struct VariantStruct {
    // ...
}
```

also extend the struct implementation to support tuple structs: 
```rust
#[derive(Asset, TypePath)]
pub struct MyImageNewtype(
    #[dependency]
    Handle<Image>
);
````

---------

Co-authored-by: Nicola Papale <nico@nicopap.ch>
2023-11-29 16:52:50 -08:00
Carter Anderson
24b4c517d0 Use handles for queued scenes in SceneSpawner (#10619)
Fixes #10482

Store Handles instead of AssetIds for queued Scenes and DynamicScenes in
SceneSpawner.
2023-11-29 16:52:11 -08:00
François
c458093cc3 examples showcase: use patches instead of sed for wasm hacks (#10601)
# Objective

- Fix the asset hack for wasm examples so that they work on the website
- Use patches instead of sed for wasm hacks so that it fails explicitly
when they need to be updated
2023-11-29 16:47:47 -08:00
Michael Leandersson
46cbb8f781 Do not panic when failing to create assets folder (#10613) (#10614)
# Objective

- Allow bevy applications that does not have any assets folder to start
from a read-only directory. (typically installed to a systems folder)

Fixes #10613

## Solution

- warn instead of panic when assets folder creation fails.
2023-11-29 16:47:36 -08:00
MevLyshkin
425c8b8b9f Fix wasm builds with file_watcher enabled (#10589)
# Objective

- Currently, in 0.12 there is an issue that it is not possible to build
bevy for Wasm with feature "file_watcher" enabled. It still would not
compile, but now with proper explanation.
- Fixes https://github.com/bevyengine/bevy/issues/10507


## Solution

- Remove `notify-debouncer-full` dependency on WASM platform entirely.
- Compile with "file_watcher" feature now on platform `wasm32` gives
meaningful compile error.

---

## Changelog

### Fixed

- Compile with "file_watcher" feature now on platform `wasm32` gives
meaningful compile error.
2023-11-29 16:47:23 -08:00
irate
55e74ea8e9 Revert App::run() behavior/Remove winit specific code from bevy_app (#10389)
# Objective
The way `bevy_app` works was changed unnecessarily in #9826 whose
changes should have been specific to `bevy_winit`.
I'm somewhat disappointed that happened and we can see in
https://github.com/bevyengine/bevy/pull/10195 that it made things more
complicated.

Even worse, in #10385 it's clear that this breaks the clean abstraction
over another engine someone built with Bevy!

Fixes #10385.

## Solution

- Move the changes made to `bevy_app` in #9826 to `bevy_winit`
- Revert the changes to `ScheduleRunnerPlugin` and the `run_once` runner
in #10195 as they're no longer necessary.

While this code is breaking relative to `0.12.0`, it reverts the
behavior of `bevy_app` back to how it was in `0.11`.
Due to the nature of the breakage relative to `0.11` I hope this will be
considered for `0.12.1`.
2023-11-29 16:47:08 -08:00
Markus Ort
44fbc2f717 Fix panic when using image in UiMaterial (#10591)
# Objective

- Fix the panic on using Images in UiMaterials due to assets not being
loaded.
- Fixes #10513 

## Solution

- add `let else` statement that `return`s or `continue`s instead of
unwrapping, causing a panic.
2023-11-29 16:46:44 -08:00
Carter Anderson
0dee514e61 Add missing asset load error logs for load_folder and load_untyped (#10578)
# Objective

Fixes #10515 

## Solution

Add missing error logs.
2023-11-29 16:46:21 -08:00
François
efa8c22ff8 support required features in wasm examples showcase (#10577)
# Objective

- Examples with required features fail to build
- If you're fixing a specific issue, say "Fixes #X".

## Solution

- Pass them along when building examples for wasm showcase
- Also mark example `hot_asset_reloading` as not wasm compatible as it
isn't even with the right features enabled
2023-11-29 16:46:09 -08:00
François
387755748c fix example custom_asset_reader on wasm (#10574)
# Objective

- Example `custom_asset_reader` fails to build in wasm
```
$ cargo build --profile release --target wasm32-unknown-unknown --example custom_asset_reader
   Compiling bevy v0.12.0 (/Users/runner/work/bevy-website/bevy-website)
error[E0432]: unresolved import `bevy::asset::io::file`
 --> examples/asset/custom_asset_reader.rs:7:9
  |
7 |         file::FileAssetReader, AssetReader, AssetReaderError, AssetSource, AssetSourceId,
  |  
```

## Solution

- Wrap the platform default asset reader instead of the
`FileAssetReader`
2023-11-29 16:45:57 -08:00
Testare
ce69959a69 Add load_untyped to LoadContext (#10526)
# Objective

Give us the ability to load untyped assets in AssetLoaders.

## Solution

Basically just copied the code from `load`, but used
`asset_server.load_untyped` instead internally.

## Changelog

Added `load_untyped` method to `LoadContext`
2023-11-29 16:45:37 -08:00
BrayMatter
520a09a083 Ensure ExtendedMaterial works with reflection (to enable bevy_egui_inspector integration) (#10548)
# Objective

- Ensure ExtendedMaterial can be referenced in bevy_egui_inspector
correctly

## Solution

Add a more manual `TypePath` implementation to work around bugs in the
derive macro.
2023-11-29 16:45:06 -08:00
irate
a3a7376034 Fix float precision issue in the gizmo shader (#10408)
Fix a precision issue with in the manual near-clipping function.
This only affected lines that span large distances (starting at 100_000~
units) in my testing.

Fixes #10403
2023-11-29 16:44:26 -08:00
ickshonpe
0483b69a79 Improved Text Rendering (#10537)
# Objective

The quality of Bevy's text rendering can vary wildly depending on the
font, font size, pixel alignment and scale factor.

But this situation can be improved dramatically with some small
adjustments.

## Solution

* Text node positions are rounded to the nearest physical pixel before
rendering.
* Each glyph texture has a 1-pixel wide transparent border added along
its edges.

This means font atlases will use more memory because of the extra pixel
of padding for each glyph but it's more than worth it I think (although
glyph size is increased by 2 pixels on both axes, the net increase is 1
pixel as the font texture atlas's padding has been removed).

## Results

Screenshots are from the 'ui' example with a scale factor of 1.5. 

Things can get much uglier with the right font and worst scale
factor<sup>tm</sup>.

### before 
<img width="300" alt="list-bad-text"
src="https://github.com/bevyengine/bevy/assets/27962798/482b384d-8743-4bae-9a65-468ff1b4c301">

### after
<img width="300" alt="good_list_text"
src="https://github.com/bevyengine/bevy/assets/27962798/34323b0a-f714-47ba-9728-a59804987bc8">
 
---

## Changelog
* Font texture atlases are no longer padded.
* Each glyph texture has a 1-pixel wide padding added along its edges.
* Text node positions are rounded to the nearest physical pixel before
rendering.
2023-11-29 16:44:14 -08:00
Carter Anderson
63828621e8 Fix untyped labeled asset loading (#10514)
# Objective

Fixes #10436
Alternative to #10465 

## Solution

`load_untyped_async` / `load_internal` currently has a bug. In
`load_untyped_async`, we pass None into `load_internal` for the
`UntypedHandle` of the labeled asset path. This results in a call to
`get_or_create_path_handle_untyped` with `loader.asset_type_id()`
This is a new code path that wasn't hit prior to the newly added
`load_untyped` because `load_untyped_async` was a private method only
used in the context of the `load_folder` impl (which doesn't have
labels)

The fix required some refactoring to catch that case and defer handle
retrieval. I have also made `load_untyped_async` public as it is now
"ready for public use" and unlocks new scenarios.
2023-11-29 16:43:44 -08:00
Nicola Papale
f78a9460db Fix animations resetting after repeat count (#10540)
# Objective

After #9002, it seems that "single shot" animations were broken. When
completing, they would reset to their initial value. Which is generally
not what you want.

- Fixes #10480

## Solution

Avoid `%`-ing the animation after the number of completions exceeds the
specified one. Instead, we early-return. This is also true when the
player is playing in reverse.

---

## Changelog

- Avoid resetting animations after `Repeat::Never` animation completion.
2023-11-29 16:42:51 -08:00
Waridley
6dc602da44 Prepend root_path to meta path in HttpWasmAssetReader (#10527)
# Objective

Fixes an issue where Bevy will look for `.meta` files in the root of the
server instead of `imported_assets/Default` on the web.

## Solution

`self.root_path.join` was seemingly forgotten in the `read_meta`
function on `HttpWasmAssetReader`, though it was included in the `read`
function. This PR simply adds the missing function call.
2023-11-29 16:42:40 -08:00
Rafał Harabień
e69ab92baf Make AssetLoader/Saver Error type bounds compatible with anyhow::Error (#10493)
# Objective

* In Bevy 0.11 asset loaders used `anyhow::Error` for returning errors.
In Bevy 0.12 `AssetLoader` (and `AssetSaver`) have associated `Error`
type. Unfortunately it's type bounds does not allow `anyhow::Error` to
be used despite migration guide claiming otherwise. This makes migration
to 0.12 more challenging. Solve this by changing type bounds for
associated `Error` type.
* Fix #10350

## Solution

Change associated `Error` type bounds to require `Into<Box<dyn
std::error::Error + Send + Sync + 'static>>` to be implemented instead
of `std::error::Error + Send + Sync + 'static`. Both `anyhow::Error` and
errors generated by `thiserror` seems to be fine with such type bound.

---

## Changelog

### Fixed
* Fixed compatibility with `anyhow::Error` in `AssetLoader` and
`AssetSaver` associated `Error` type
2023-11-29 16:41:48 -08:00
Rob Parrett
6ebc0a2dfb Fix minor issues with custom_asset example (#10337)
# Objective

- Use bevy's re-exported `AsyncReadExt` so users don't think they need
to depend on `futures-lite`.
- Fix a funky  error text
2023-11-29 16:41:32 -08:00
Carter Anderson
b32976504d Fix shader import hot reloading on windows (#10502)
# Objective

Hot reloading shader imports on windows is currently broken due to
inconsistent `/` and `\` usage ('/` is used in the user facing APIs and
`\` is produced by notify-rs (and likely other OS apis).

Fixes #10500

## Solution

Standardize import paths when loading a `Shader`. The correct long term
fix is to standardize AssetPath on `/`-only, but this is the right scope
of fix for a patch release.

---------

Co-authored-by: François <mockersf@gmail.com>
2023-11-29 16:41:02 -08:00
Sludge
26dfe42623 Don't .unwrap() in AssetPath::try_parse (#10452)
# Objective

- The docs on `AssetPath::try_parse` say that it will return an error
when the string is malformed, but it actually just `.unwrap()`s the
result.

## Solution

- Use `?` instead of unwrapping the result.
2023-11-29 16:40:48 -08:00
Aevyrie
4667d80243 Allow registering boxed systems (#10378)
# Objective

- Allow registration of one-shot systems when those systems have already
been `Box`ed.
- Needed for `bevy_eventlisteners` which allows adding event listeners
with callbacks in normal systems. The current one shot system
implementation requires systems be registered from an exclusive system,
and that those systems be passed in as types that implement
`IntoSystem`. However, the eventlistener callback crate allows users to
define their callbacks in normal systems, by boxing the system and
deferring initialization to an exclusive system.

## Solution

- Separate the registration of the system from the boxing of the system.
This is non-breaking, and adds a new method.

---

## Changelog

- Added `World::register_boxed_system` to allow registration of
already-boxed one shot systems.
2023-11-29 16:40:36 -08:00
kayh
e0a532cb4b Fix bevy_pbr shader function name (#10423)
# Objective

Fix a shader error that happens when using pbr morph targets.

## Solution

Fix the function name in the `prepass.wgsl` shader, which is incorrectly
prefixed with `morph::` (added in
61bad4eb57 (diff-97e4500f0a36bc6206d7b1490c8dd1a69459ee39dc6822eb9b2f7b160865f49fR42)).

This section of the shader is only enabled when using morph targets, so
it seems like there are no tests / examples using it?
2023-11-29 16:40:19 -08:00
François
e8ba68d702 UI Materials: ignore entities with a BackgroundColor component (#10434)
# Objective

- Entities with both a `BackgroundColor` and a
`Handle<CustomUiMaterial>` are extracted by both pipelines and results
in entities being overwritten in the render world
- Fixes #10431 

## Solution

- Ignore entities with `BackgroundColor` when extracting ui material
entities, and document that limit
2023-11-29 16:40:05 -08:00
François
07d6a12f46 UI Material: each material should have its own buffer (#10422)
# Objective

- When having several UI Material, nodes are not correctly placed

## Solution

- have a buffer per material
2023-11-29 16:39:47 -08:00
François
e6695d5e20 ui material: fix right border width (#10421)
# Objective

- When writing a custom UI material, the right border doesn't have the
correct value

## Solution

- Fix the calculation
2023-11-29 16:39:26 -08:00
97 changed files with 1244 additions and 671 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "bevy"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
categories = ["game-engines", "graphics", "gui", "rendering"]
description = "A refreshingly simple data-driven game engine and app framework"
@ -276,8 +276,8 @@ file_watcher = ["bevy_internal/file_watcher"]
embedded_watcher = ["bevy_internal/embedded_watcher"]
[dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.0", default-features = false, optional = true }
bevy_internal = { path = "crates/bevy_internal", version = "0.12.0", default-features = false }
bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.1", default-features = false, optional = true }
bevy_internal = { path = "crates/bevy_internal", version = "0.12.1", default-features = false }
[dev-dependencies]
rand = "0.8.0"
@ -1109,7 +1109,7 @@ required-features = ["file_watcher"]
name = "Hot Reloading of Assets"
description = "Demonstrates automatic reloading of assets when modified on disk"
category = "Assets"
wasm = true
wasm = false
[[example]]
name = "asset_processing"

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_animation"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides animation functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,14 +10,14 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_time = { path = "../bevy_time", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_time = { path = "../bevy_time", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }

View File

@ -190,15 +190,20 @@ impl PlayingAnimation {
self.elapsed += delta;
self.seek_time += delta * self.speed;
if (self.seek_time > clip_duration && self.speed > 0.0)
|| (self.seek_time < 0.0 && self.speed < 0.0)
{
self.completions += 1;
}
let over_time = self.speed > 0.0 && self.seek_time >= clip_duration;
let under_time = self.speed < 0.0 && self.seek_time < 0.0;
if over_time || under_time {
self.completions += 1;
if self.is_finished() {
return;
}
}
if self.seek_time >= clip_duration {
self.seek_time %= clip_duration;
}
// Note: assumes delta is never lower than -clip_duration
if self.seek_time < 0.0 {
self.seek_time += clip_duration;
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_app"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides core App functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -16,11 +16,11 @@ bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
[dependencies]
# bevy
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
# other
serde = { version = "1.0", features = ["derive"], optional = true }

View File

@ -190,6 +190,7 @@ impl Default for App {
app.init_resource::<AppTypeRegistry>();
app.add_plugins(MainSchedulePlugin);
app.add_event::<AppExit>();
#[cfg(feature = "bevy_ci_testing")]
@ -309,14 +310,6 @@ impl App {
panic!("App::run() was called from within Plugin::build(), which is not allowed.");
}
if app.plugins_state() == PluginsState::Ready {
// If we're already ready, we finish up now and advance one frame.
// This prevents black frames during the launch transition on iOS.
app.finish();
app.cleanup();
app.update();
}
let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
(runner)(app);
}
@ -986,20 +979,14 @@ impl App {
}
fn run_once(mut app: App) {
let plugins_state = app.plugins_state();
if plugins_state != PluginsState::Cleaned {
while app.plugins_state() == PluginsState::Adding {
#[cfg(not(target_arch = "wasm32"))]
bevy_tasks::tick_global_task_pools_on_main_thread();
}
app.finish();
app.cleanup();
while app.plugins_state() == PluginsState::Adding {
#[cfg(not(target_arch = "wasm32"))]
bevy_tasks::tick_global_task_pools_on_main_thread();
}
app.finish();
app.cleanup();
// if plugins where cleaned before the runner start, an update already ran
if plugins_state != PluginsState::Cleaned {
app.update();
}
app.update();
}
/// An event that indicates the [`App`] should exit. This will fully exit the app process at the

View File

@ -84,12 +84,7 @@ impl Plugin for ScheduleRunnerPlugin {
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
match run_mode {
RunMode::Once => {
// if plugins where cleaned before the runner start, an update already ran
if plugins_state != PluginsState::Cleaned {
app.update();
}
}
RunMode::Once => app.update(),
RunMode::Loop { wait } => {
let mut tick = move |app: &mut App,
wait: Option<Duration>|

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_asset"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides asset functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -18,13 +18,13 @@ asset_processor = []
watch = []
[dependencies]
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset_macros = { path = "macros", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset_macros = { path = "macros", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
async-broadcast = "0.5"
async-fs = "1.5"
@ -38,10 +38,9 @@ parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] }
ron = "0.8"
serde = { version = "1", features = ["derive"] }
thiserror = "1.0"
notify-debouncer-full = { version = "0.3.1", optional = true }
[target.'cfg(target_os = "android")'.dependencies]
bevy_winit = { path = "../bevy_winit", version = "0.12.0" }
bevy_winit = { path = "../bevy_winit", version = "0.12.1" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
@ -49,5 +48,8 @@ web-sys = { version = "0.3", features = ["Request", "Window", "Response"] }
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
notify-debouncer-full = { version = "0.3.1", optional = true }
[dev-dependencies]
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }

View File

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

View File

@ -1,6 +1,6 @@
use bevy_macro_utils::BevyManifest;
use proc_macro::{Span, TokenStream};
use quote::quote;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Path};
pub(crate) fn bevy_asset_path() -> syn::Path {
@ -41,33 +41,71 @@ fn derive_dependency_visitor_internal(
ast: &DeriveInput,
bevy_asset_path: &Path,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let mut field_visitors = Vec::new();
if let Data::Struct(data_struct) = &ast.data {
for field in &data_struct.fields {
if field
.attrs
.iter()
.any(|a| a.path().is_ident(DEPENDENCY_ATTRIBUTE))
{
if let Some(field_ident) = &field.ident {
field_visitors.push(quote! {
#bevy_asset_path::VisitAssetDependencies::visit_dependencies(&self.#field_ident, visit);
});
}
}
}
} else {
return Err(syn::Error::new(
Span::call_site().into(),
"Asset derive currently only works on structs",
));
}
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
let visit_dep = |to_read| quote!(#bevy_asset_path::VisitAssetDependencies::visit_dependencies(#to_read, visit););
let is_dep_attribute = |a: &syn::Attribute| a.path().is_ident(DEPENDENCY_ATTRIBUTE);
let field_has_dep = |f: &syn::Field| f.attrs.iter().any(is_dep_attribute);
let body = match &ast.data {
Data::Struct(data_struct) => {
let fields = data_struct.fields.iter();
let field_visitors = fields.enumerate().filter(|(_, f)| field_has_dep(f));
let field_visitors = field_visitors.map(|(i, field)| match &field.ident {
Some(ident) => visit_dep(quote!(&self.#ident)),
None => {
let index = syn::Index::from(i);
visit_dep(quote!(&self.#index))
}
});
Some(quote!( #(#field_visitors)* ))
}
Data::Enum(data_enum) => {
let variant_has_dep = |v: &syn::Variant| v.fields.iter().any(field_has_dep);
let any_case_required = data_enum.variants.iter().any(variant_has_dep);
let cases = data_enum.variants.iter().filter(|v| variant_has_dep(v));
let cases = cases.map(|variant| {
let ident = &variant.ident;
let fields = &variant.fields;
let field_visitors = fields.iter().enumerate().filter(|(_, f)| field_has_dep(f));
let field_visitors = field_visitors.map(|(i, field)| match &field.ident {
Some(ident) => visit_dep(quote!(#ident)),
None => {
let ident = format_ident!("member{i}");
visit_dep(quote!(#ident))
}
});
let fields = match fields {
syn::Fields::Named(fields) => {
let named = fields.named.iter().map(|f| f.ident.as_ref());
quote!({ #(#named,)* .. })
}
syn::Fields::Unnamed(fields) => {
let named = (0..fields.unnamed.len()).map(|i| format_ident!("member{i}"));
quote!( ( #(#named,)* ) )
}
syn::Fields::Unit => unreachable!("Can't pass filter is_dep_attribute"),
};
quote!(Self::#ident #fields => {
#(#field_visitors)*
})
});
any_case_required.then(|| quote!(match self { #(#cases)*, _ => {} }))
}
Data::Union(_) => {
return Err(syn::Error::new(
Span::call_site().into(),
"Asset derive currently doesn't work on unions",
));
}
};
// prevent unused variable warning in case there are no dependencies
let visit = if field_visitors.is_empty() {
let visit = if body.is_none() {
quote! { _visit }
} else {
quote! { visit }
@ -76,7 +114,7 @@ fn derive_dependency_visitor_internal(
Ok(quote! {
impl #impl_generics #bevy_asset_path::VisitAssetDependencies for #struct_name #type_generics #where_clause {
fn visit_dependencies(&self, #visit: &mut impl FnMut(#bevy_asset_path::UntypedAssetId)) {
#(#field_visitors)*
#body
}
}
})

View File

@ -1,4 +1,4 @@
use crate as bevy_asset;
use crate::{self as bevy_asset, LoadState};
use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle};
use bevy_ecs::{
prelude::EventWriter,
@ -87,12 +87,11 @@ pub struct LoadedUntypedAsset {
// PERF: do we actually need this to be an enum? Can we just use an "invalid" generation instead
#[derive(Default)]
enum Entry<A: Asset> {
/// None is an indicator that this entry does not have live handles.
#[default]
None,
Some {
value: Option<A>,
generation: u32,
},
/// Some is an indicator that there is a live handle active for the entry at this [`AssetIndex`]
Some { value: Option<A>, generation: u32 },
}
/// Stores [`Asset`] values in a Vec-like storage identified by [`AssetIndex`].
@ -151,7 +150,26 @@ impl<A: Asset> DenseAssetStorage<A> {
}
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
pub(crate) fn remove(&mut self, index: AssetIndex) -> Option<A> {
/// This will recycle the id and allow new entries to be inserted.
pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option<A> {
self.remove_internal(index, |dense_storage| {
dense_storage.storage[index.index as usize] = Entry::None;
dense_storage.allocator.recycle(index);
})
}
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
/// This will _not_ recycle the id. New values with the current ID can still be inserted. The ID will
/// not be reused until [`DenseAssetStorage::remove_dropped`] is called.
pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option<A> {
self.remove_internal(index, |_| {})
}
fn remove_internal(
&mut self,
index: AssetIndex,
removed_action: impl FnOnce(&mut Self),
) -> Option<A> {
self.flush();
let value = match &mut self.storage[index.index as usize] {
Entry::None => return None,
@ -166,8 +184,7 @@ impl<A: Asset> DenseAssetStorage<A> {
}
}
};
self.storage[index.index as usize] = Entry::None;
self.allocator.recycle(index);
removed_action(self);
value
}
@ -393,11 +410,25 @@ impl<A: Asset> Assets<A> {
pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
let id: AssetId<A> = id.into();
match id {
AssetId::Index { index, .. } => self.dense_storage.remove(index),
AssetId::Index { index, .. } => self.dense_storage.remove_still_alive(index),
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid),
}
}
/// Removes (and returns) the [`Asset`] with the given `id`, if its exists.
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
pub(crate) fn remove_dropped(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
let id: AssetId<A> = id.into();
let result = match id {
AssetId::Index { index, .. } => self.dense_storage.remove_dropped(index),
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid),
};
if result.is_some() {
self.queued_events.push(AssetEvent::Removed { id });
}
result
}
/// Returns `true` if there are no assets in this collection.
pub fn is_empty(&self) -> bool {
self.dense_storage.is_empty() && self.hash_map.is_empty()
@ -466,16 +497,21 @@ impl<A: Asset> Assets<A> {
let mut not_ready = Vec::new();
while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
let id = drop_event.id;
if !assets.contains(id.typed()) {
not_ready.push(drop_event);
continue;
}
if drop_event.asset_server_managed {
if infos.process_handle_drop(id.untyped(TypeId::of::<A>())) {
assets.remove(id.typed());
let untyped = id.untyped(TypeId::of::<A>());
if let Some(info) = infos.get(untyped) {
if info.load_state == LoadState::Loading
|| info.load_state == LoadState::NotLoaded
{
not_ready.push(drop_event);
continue;
}
}
if infos.process_handle_drop(untyped) {
assets.remove_dropped(id.typed());
}
} else {
assets.remove(id.typed());
assets.remove_dropped(id.typed());
}
}
// TODO: this is _extremely_ inefficient find a better fix

View File

@ -66,7 +66,12 @@ impl EmbeddedAssetRegistry {
Box::new(MemoryAssetReader {
root: processed_dir.clone(),
})
});
})
// Note that we only add a processed watch warning because we don't want to warn
// noisily about embedded watching (which is niche) when users enable file watching.
.with_processed_watch_warning(
"Consider enabling the `embedded_watcher` cargo feature.",
);
#[cfg(feature = "embedded_watcher")]
{

View File

@ -6,6 +6,7 @@ mod file_asset;
#[cfg(not(feature = "multi-threaded"))]
mod sync_file_asset;
use bevy_log::warn;
#[cfg(feature = "file_watcher")]
pub use file_watcher::*;
@ -44,12 +45,12 @@ impl FileAssetReader {
/// See `get_base_path` below.
pub fn new<P: AsRef<Path>>(path: P) -> Self {
let root_path = Self::get_base_path().join(path.as_ref());
std::fs::create_dir_all(&root_path).unwrap_or_else(|e| {
panic!(
if let Err(e) = std::fs::create_dir_all(&root_path) {
warn!(
"Failed to create root directory {:?} for file asset reader: {:?}",
root_path, e
)
});
);
}
Self { root_path }
}

View File

@ -1,3 +1,10 @@
#[cfg(all(feature = "file_watcher", target_arch = "wasm32"))]
compile_error!(
"The \"file_watcher\" feature for hot reloading does not work \
on WASM.\nDisable \"file_watcher\" \
when compiling to WASM"
);
#[cfg(target_os = "android")]
pub mod android;
pub mod embedded;

View File

@ -128,6 +128,8 @@ pub struct AssetSourceBuilder {
+ Sync,
>,
>,
pub watch_warning: Option<&'static str>,
pub processed_watch_warning: Option<&'static str>,
}
impl AssetSourceBuilder {
@ -156,23 +158,31 @@ impl AssetSourceBuilder {
if watch {
let (sender, receiver) = crossbeam_channel::unbounded();
match self.watcher.as_mut().and_then(|w|(w)(sender)) {
match self.watcher.as_mut().and_then(|w| (w)(sender)) {
Some(w) => {
source.watcher = Some(w);
source.event_receiver = Some(receiver);
},
None => warn!("{id} does not have an AssetWatcher configured. Consider enabling the `file_watcher` feature. Note that Web and Android do not currently support watching assets."),
}
None => {
if let Some(warning) = self.watch_warning {
warn!("{id} does not have an AssetWatcher configured. {warning}");
}
}
}
}
if watch_processed {
let (sender, receiver) = crossbeam_channel::unbounded();
match self.processed_watcher.as_mut().and_then(|w|(w)(sender)) {
match self.processed_watcher.as_mut().and_then(|w| (w)(sender)) {
Some(w) => {
source.processed_watcher = Some(w);
source.processed_event_receiver = Some(receiver);
},
None => warn!("{id} does not have a processed AssetWatcher configured. Consider enabling the `file_watcher` feature. Note that Web and Android do not currently support watching assets."),
}
None => {
if let Some(warning) = self.processed_watch_warning {
warn!("{id} does not have a processed AssetWatcher configured. {warning}");
}
}
}
}
Some(source)
@ -238,6 +248,18 @@ impl AssetSourceBuilder {
self
}
/// Enables a warning for the unprocessed source watcher, which will print when watching is enabled and the unprocessed source doesn't have a watcher.
pub fn with_watch_warning(mut self, warning: &'static str) -> Self {
self.watch_warning = Some(warning);
self
}
/// Enables a warning for the processed source watcher, which will print when watching is enabled and the processed source doesn't have a watcher.
pub fn with_processed_watch_warning(mut self, warning: &'static str) -> Self {
self.processed_watch_warning = Some(warning);
self
}
/// Returns a builder containing the "platform default source" for the given `path` and `processed_path`.
/// For most platforms, this will use [`FileAssetReader`](crate::io::file::FileAssetReader) / [`FileAssetWriter`](crate::io::file::FileAssetWriter),
/// but some platforms (such as Android) have their own default readers / writers / watchers.
@ -248,7 +270,8 @@ impl AssetSourceBuilder {
.with_watcher(AssetSource::get_default_watcher(
path.to_string(),
Duration::from_millis(300),
));
))
.with_watch_warning(AssetSource::get_default_watch_warning());
if let Some(processed_path) = processed_path {
default
.with_processed_reader(AssetSource::get_default_reader(processed_path.to_string()))
@ -257,6 +280,7 @@ impl AssetSourceBuilder {
processed_path.to_string(),
Duration::from_millis(300),
))
.with_processed_watch_warning(AssetSource::get_default_watch_warning())
} else {
default
}
@ -428,6 +452,16 @@ impl AssetSource {
}
}
/// Returns the default non-existent [`AssetWatcher`] warning for the current platform.
pub fn get_default_watch_warning() -> &'static str {
#[cfg(target_arch = "wasm32")]
return "Web does not currently support watching assets.";
#[cfg(target_os = "android")]
return "Android does not currently support watching assets.";
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
return "Consider enabling the `file_watcher` feature.";
}
/// Returns a builder function for this platform's default [`AssetWatcher`]. `path` is the relative path to
/// the asset root. This will return [`None`] if this platform does not support watching assets by default.
/// `file_debounce_time` is the amount of time to wait (and debounce duplicate events) before returning an event.

View File

@ -77,7 +77,7 @@ impl AssetReader for HttpWasmAssetReader {
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
let meta_path = get_meta_path(path);
let meta_path = get_meta_path(&self.root_path.join(path));
Ok(self.fetch_bytes(meta_path).await?)
})
}

View File

@ -45,10 +45,12 @@ use bevy_app::{App, First, MainScheduleOrder, Plugin, PostUpdate};
use bevy_ecs::{
reflect::AppTypeRegistry,
schedule::{IntoSystemConfigs, IntoSystemSetConfigs, ScheduleLabel, SystemSet},
system::Resource,
world::FromWorld,
};
use bevy_log::error;
use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath};
use bevy_utils::HashSet;
use std::{any::TypeId, sync::Arc};
/// Provides "asset" loading and processing functionality. An [`Asset`] is a "runtime value" that is loaded from an [`AssetSource`],
@ -97,6 +99,20 @@ pub enum AssetMode {
Processed,
}
/// Configures how / if meta files will be checked. If an asset's meta file is not checked, the default meta for the asset
/// will be used.
// TODO: To avoid breaking Bevy 0.12 users in 0.12.1, this is a Resource. In Bevy 0.13 this should be changed to a field on AssetPlugin (if it is still needed).
#[derive(Debug, Default, Clone, Resource)]
pub enum AssetMetaCheck {
/// Always check if assets have meta files. If the meta does not exist, the default meta will be used.
#[default]
Always,
/// Only look up meta files for the provided paths. The default meta will be used for any paths not contained in this set.
Paths(HashSet<AssetPath<'static>>),
/// Never check if assets have meta files and always use the default meta. If meta files exist, they will be ignored and the default meta will be used.
Never,
}
impl Default for AssetPlugin {
fn default() -> Self {
Self {
@ -139,9 +155,16 @@ impl Plugin for AssetPlugin {
AssetMode::Unprocessed => {
let mut builders = app.world.resource_mut::<AssetSourceBuilders>();
let sources = builders.build_sources(watch, false);
app.insert_resource(AssetServer::new(
let meta_check = app
.world
.get_resource::<AssetMetaCheck>()
.cloned()
.unwrap_or_else(AssetMetaCheck::default);
app.insert_resource(AssetServer::new_with_meta_check(
sources,
AssetServerMode::Unprocessed,
meta_check,
watch,
));
}
@ -157,6 +180,7 @@ impl Plugin for AssetPlugin {
sources,
processor.server().data.loaders.clone(),
AssetServerMode::Processed,
AssetMetaCheck::Always,
watch,
))
.insert_resource(processor)
@ -166,9 +190,10 @@ impl Plugin for AssetPlugin {
{
let mut builders = app.world.resource_mut::<AssetSourceBuilders>();
let sources = builders.build_sources(false, watch);
app.insert_resource(AssetServer::new(
app.insert_resource(AssetServer::new_with_meta_check(
sources,
AssetServerMode::Processed,
AssetMetaCheck::Always,
watch,
));
}
@ -1180,4 +1205,35 @@ mod tests {
// running schedule does not error on ambiguity between the 2 uses_assets systems
app.world.run_schedule(Update);
}
// validate the Asset derive macro for various asset types
#[derive(Asset, TypePath)]
pub struct TestAsset;
#[allow(dead_code)]
#[derive(Asset, TypePath)]
pub enum EnumTestAsset {
Unnamed(#[dependency] Handle<TestAsset>),
Named {
#[dependency]
handle: Handle<TestAsset>,
#[dependency]
vec_handles: Vec<Handle<TestAsset>>,
#[dependency]
embedded: TestAsset,
},
StructStyle(#[dependency] TestAsset),
Empty,
}
#[derive(Asset, TypePath)]
pub struct StructTestAsset {
#[dependency]
handle: Handle<TestAsset>,
#[dependency]
embedded: TestAsset,
}
#[derive(Asset, TypePath)]
pub struct TupleTestAsset(#[dependency] Handle<TestAsset>);
}

View File

@ -5,8 +5,8 @@ use crate::{
Settings,
},
path::AssetPath,
Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId,
UntypedHandle,
Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, LoadedUntypedAsset,
UntypedAssetId, UntypedHandle,
};
use bevy_ecs::world::World;
use bevy_utils::{BoxedFuture, CowArc, HashMap, HashSet};
@ -28,7 +28,7 @@ pub trait AssetLoader: Send + Sync + 'static {
/// The settings type used by this [`AssetLoader`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
type Error: std::error::Error + Send + Sync + 'static;
type Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
fn load<'a>(
&'a self,
@ -90,7 +90,9 @@ where
.expect("Loader settings should exist")
.downcast_ref::<L::Settings>()
.expect("AssetLoader settings should match the loader type");
let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context).await?;
let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
.await
.map_err(|error| error.into())?;
Ok(load_context.finish(asset, Some(meta)).into())
})
}
@ -351,6 +353,13 @@ impl<'a> LoadContext<'a> {
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
///
/// # Warning
///
/// This will not assign dependencies to the given `asset`. If adding an asset
/// with dependencies generated from calls such as [`LoadContext::load`], use
/// [`LoadContext::labeled_asset_scope`] or [`LoadContext::begin_labeled_asset`] to generate a
/// new [`LoadContext`] to track the dependencies for the labeled asset.
///
/// See [`AssetPath`] for more on labeled assets.
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
self.labeled_asset_scope(label, |_| asset)
@ -460,6 +469,21 @@ impl<'a> LoadContext<'a> {
handle
}
/// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset without knowing its type.
pub fn load_untyped<'b>(
&mut self,
path: impl Into<AssetPath<'b>>,
) -> Handle<LoadedUntypedAsset> {
let path = path.into().to_owned();
let handle = if self.should_load_dependencies {
self.asset_server.load_untyped(path)
} else {
self.asset_server.get_or_create_path_handle(path, None)
};
self.dependencies.insert(handle.id().untyped());
handle
}
/// Loads the [`Asset`] of type `A` at the given `path` with the given [`AssetLoader::Settings`] settings `S`. This is a "deferred"
/// load. If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log
/// and the asset load will fail.

View File

@ -115,7 +115,7 @@ impl<'a> AssetPath<'a> {
///
/// This will return a [`ParseAssetPathError`] if `asset_path` is in an invalid format.
pub fn try_parse(asset_path: &'a str) -> Result<AssetPath<'a>, ParseAssetPathError> {
let (source, path, label) = Self::parse_internal(asset_path).unwrap();
let (source, path, label) = Self::parse_internal(asset_path)?;
Ok(Self {
source: match source {
Some(source) => AssetSourceId::Name(CowArc::Borrowed(source)),

View File

@ -13,7 +13,7 @@ use crate::{
get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta,
AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal,
},
AssetLoadError, AssetPath, AssetServer, AssetServerMode, DeserializeMetaError,
AssetLoadError, AssetMetaCheck, AssetPath, AssetServer, AssetServerMode, DeserializeMetaError,
MissingAssetLoaderForExtensionError,
};
use bevy_ecs::prelude::*;
@ -72,7 +72,12 @@ impl AssetProcessor {
// The asset processor uses its own asset server with its own id space
let mut sources = source.build_sources(false, false);
sources.gate_on_processor(data.clone());
let server = AssetServer::new(sources, AssetServerMode::Processed, false);
let server = AssetServer::new_with_meta_check(
sources,
AssetServerMode::Processed,
AssetMetaCheck::Always,
false,
);
Self { server, data }
}

View File

@ -138,7 +138,7 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
.saver
.save(writer, saved_asset, &settings.saver_settings)
.await
.map_err(|error| ProcessError::AssetSaveError(Box::new(error)))?;
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
Ok(output_settings)
})
}

View File

@ -14,7 +14,7 @@ pub trait AssetSaver: Send + Sync + 'static {
/// The type of [`AssetLoader`] used to load this [`Asset`]
type OutputLoader: AssetLoader;
/// The type of [error](`std::error::Error`) which could be encountered by this saver.
type Error: std::error::Error + Send + Sync + 'static;
type Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
/// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
/// `asset` is saved.
@ -53,7 +53,9 @@ impl<S: AssetSaver> ErasedAssetSaver for S {
.downcast_ref::<S::Settings>()
.expect("AssetLoader settings should match the loader type");
let saved_asset = SavedAsset::<S::Asset>::from_loaded(asset).unwrap();
self.save(writer, saved_asset, settings).await?;
if let Err(err) = self.save(writer, saved_asset, settings).await {
return Err(err.into());
}
Ok(())
})
}

View File

@ -104,6 +104,7 @@ impl AssetInfos {
),
type_name,
)
.unwrap()
}
#[allow(clippy::too_many_arguments)]
@ -116,7 +117,7 @@ impl AssetInfos {
path: Option<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
loading: bool,
) -> Result<UntypedHandle, MissingHandleProviderError> {
) -> Result<UntypedHandle, GetOrCreateHandleInternalError> {
let provider = handle_providers
.get(&type_id)
.ok_or(MissingHandleProviderError(type_id))?;
@ -151,11 +152,13 @@ impl AssetInfos {
) -> (Handle<A>, bool) {
let result = self.get_or_create_path_handle_internal(
path,
TypeId::of::<A>(),
Some(TypeId::of::<A>()),
loading_mode,
meta_transform,
);
let (handle, should_load) = unwrap_with_context(result, std::any::type_name::<A>());
// it is ok to unwrap because TypeId was specified above
let (handle, should_load) =
unwrap_with_context(result, std::any::type_name::<A>()).unwrap();
(handle.typed_unchecked(), should_load)
}
@ -167,20 +170,25 @@ impl AssetInfos {
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> (UntypedHandle, bool) {
let result =
self.get_or_create_path_handle_internal(path, type_id, loading_mode, meta_transform);
unwrap_with_context(result, type_name)
let result = self.get_or_create_path_handle_internal(
path,
Some(type_id),
loading_mode,
meta_transform,
);
// it is ok to unwrap because TypeId was specified above
unwrap_with_context(result, type_name).unwrap()
}
/// Retrieves asset tracking data, or creates it if it doesn't exist.
/// Returns true if an asset load should be kicked off
pub fn get_or_create_path_handle_internal(
pub(crate) fn get_or_create_path_handle_internal(
&mut self,
path: AssetPath<'static>,
type_id: TypeId,
type_id: Option<TypeId>,
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> Result<(UntypedHandle, bool), MissingHandleProviderError> {
) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> {
match self.path_to_id.entry(path.clone()) {
Entry::Occupied(entry) => {
let id = *entry.get();
@ -211,6 +219,9 @@ impl AssetInfos {
// We must create a new strong handle for the existing id and ensure that the drop of the old
// strong handle doesn't remove the asset from the Assets collection
info.handle_drops_to_skip += 1;
let type_id = type_id.ok_or(
GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified,
)?;
let provider = self
.handle_providers
.get(&type_id)
@ -227,6 +238,8 @@ impl AssetInfos {
HandleLoadingMode::NotLoading => false,
HandleLoadingMode::Request | HandleLoadingMode::Force => true,
};
let type_id = type_id
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
let handle = Self::create_handle_internal(
&mut self.infos,
&self.handle_providers,
@ -247,6 +260,10 @@ impl AssetInfos {
self.infos.get(&id)
}
pub(crate) fn contains_key(&self, id: UntypedAssetId) -> bool {
self.infos.contains_key(&id)
}
pub(crate) fn get_mut(&mut self, id: UntypedAssetId) -> Option<&mut AssetInfo> {
self.infos.get_mut(&id)
}
@ -624,13 +641,23 @@ pub(crate) enum HandleLoadingMode {
#[error("Cannot allocate a handle because no handle provider exists for asset type {0:?}")]
pub struct MissingHandleProviderError(TypeId);
fn unwrap_with_context<T>(
result: Result<T, MissingHandleProviderError>,
/// An error encountered during [`AssetInfos::get_or_create_path_handle_internal`].
#[derive(Error, Debug)]
pub(crate) enum GetOrCreateHandleInternalError {
#[error(transparent)]
MissingHandleProviderError(#[from] MissingHandleProviderError),
#[error("Handle does not exist but TypeId was not specified.")]
HandleMissingButTypeIdNotSpecified,
}
pub(crate) fn unwrap_with_context<T>(
result: Result<T, GetOrCreateHandleInternalError>,
type_name: &'static str,
) -> T {
) -> Option<T> {
match result {
Ok(value) => value,
Err(_) => {
Ok(value) => Some(value),
Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None,
Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => {
panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \
Make sure you have called app.init_asset::<{type_name}>()")
}

View File

@ -12,7 +12,7 @@ use crate::{
MetaTransform, Settings,
},
path::AssetPath,
Asset, AssetEvent, AssetHandleProvider, AssetId, Assets, DeserializeMetaError,
Asset, AssetEvent, AssetHandleProvider, AssetId, AssetMetaCheck, Assets, DeserializeMetaError,
ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId, UntypedHandle,
};
use bevy_ecs::prelude::*;
@ -54,6 +54,7 @@ pub(crate) struct AssetServerData {
asset_event_receiver: Receiver<InternalAssetEvent>,
sources: AssetSources,
mode: AssetServerMode,
meta_check: AssetMetaCheck,
}
/// The "asset mode" the server is currently in.
@ -69,13 +70,37 @@ impl AssetServer {
/// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`] storage will watch for changes to
/// asset sources and hot-reload them.
pub fn new(sources: AssetSources, mode: AssetServerMode, watching_for_changes: bool) -> Self {
Self::new_with_loaders(sources, Default::default(), mode, watching_for_changes)
Self::new_with_loaders(
sources,
Default::default(),
mode,
AssetMetaCheck::Always,
watching_for_changes,
)
}
/// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`] storage will watch for changes to
/// asset sources and hot-reload them.
pub fn new_with_meta_check(
sources: AssetSources,
mode: AssetServerMode,
meta_check: AssetMetaCheck,
watching_for_changes: bool,
) -> Self {
Self::new_with_loaders(
sources,
Default::default(),
mode,
meta_check,
watching_for_changes,
)
}
pub(crate) fn new_with_loaders(
sources: AssetSources,
loaders: Arc<RwLock<AssetLoaders>>,
mode: AssetServerMode,
meta_check: AssetMetaCheck,
watching_for_changes: bool,
) -> Self {
let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
@ -85,6 +110,7 @@ impl AssetServer {
data: Arc::new(AssetServerData {
sources,
mode,
meta_check,
asset_event_sender,
asset_event_receiver,
loaders,
@ -280,8 +306,11 @@ impl AssetServer {
handle
}
/// Asynchronously load an asset that you do not know the type of statically. If you _do_ know the type of the asset,
/// you should use [`AssetServer::load`]. If you don't know the type of the asset, but you can't use an async method,
/// consider using [`AssetServer::load_untyped`].
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
pub(crate) async fn load_untyped_async<'a>(
pub async fn load_untyped_async<'a>(
&self,
path: impl Into<AssetPath<'a>>,
) -> Result<UntypedHandle, AssetLoadError> {
@ -346,7 +375,10 @@ impl AssetServer {
)
.into(),
}),
Err(_) => server.send_asset_event(InternalAssetEvent::Failed { id }),
Err(err) => {
error!("{err}");
server.send_asset_event(InternalAssetEvent::Failed { id });
}
}
})
.detach();
@ -379,35 +411,53 @@ impl AssetServer {
e
})?;
let (handle, should_load) = match input_handle {
// This contains Some(UntypedHandle), if it was retrievable
// If it is None, that is because it was _not_ retrievable, due to
// 1. The handle was not already passed in for this path, meaning we can't just use that
// 2. The asset has not been loaded yet, meaning there is no existing Handle for it
// 3. The path has a label, meaning the AssetLoader's root asset type is not the path's asset type
//
// In the None case, the only course of action is to wait for the asset to load so we can allocate the
// handle for that type.
//
// TODO: Note that in the None case, multiple asset loads for the same path can happen at the same time
// (rather than "early out-ing" in the "normal" case)
// This would be resolved by a universal asset id, as we would not need to resolve the asset type
// to generate the ID. See this issue: https://github.com/bevyengine/bevy/issues/10549
let handle_result = match input_handle {
Some(handle) => {
// if a handle was passed in, the "should load" check was already done
(handle, true)
Some((handle, true))
}
None => {
let mut infos = self.data.infos.write();
infos.get_or_create_path_handle_untyped(
let result = infos.get_or_create_path_handle_internal(
path.clone(),
loader.asset_type_id(),
loader.asset_type_name(),
path.label().is_none().then(|| loader.asset_type_id()),
HandleLoadingMode::Request,
meta_transform,
)
);
unwrap_with_context(result, loader.asset_type_name())
}
};
if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
return Err(AssetLoadError::RequestedHandleTypeMismatch {
path: path.into_owned(),
requested: handle.type_id(),
actual_asset_name: loader.asset_type_name(),
loader_name: loader.type_name(),
});
}
if !should_load && !force {
return Ok(handle);
}
let handle = if let Some((handle, should_load)) = handle_result {
if path.label().is_none() && handle.type_id() != loader.asset_type_id() {
return Err(AssetLoadError::RequestedHandleTypeMismatch {
path: path.into_owned(),
requested: handle.type_id(),
actual_asset_name: loader.asset_type_name(),
loader_name: loader.type_name(),
});
}
if !should_load && !force {
return Ok(handle);
}
Some(handle)
} else {
None
};
// if the handle result is None, we definitely need to load the asset
let (base_handle, base_path) = if path.label().is_some() {
let mut infos = self.data.infos.write();
@ -421,7 +471,7 @@ impl AssetServer {
);
(base_handle, base_path)
} else {
(handle.clone(), path.clone())
(handle.clone().unwrap(), path.clone())
};
if let Some(meta_transform) = base_handle.meta_transform() {
@ -432,26 +482,24 @@ impl AssetServer {
.load_with_meta_loader_and_reader(&base_path, meta, &*loader, &mut *reader, true, false)
.await
{
Ok(mut loaded_asset) => {
if let Some(label) = path.label_cow() {
if !loaded_asset.labeled_assets.contains_key(&label) {
return Err(AssetLoadError::MissingLabel {
base_path,
label: label.to_string(),
});
Ok(loaded_asset) => {
let final_handle = if let Some(label) = path.label_cow() {
match loaded_asset.labeled_assets.get(&label) {
Some(labeled_asset) => labeled_asset.handle.clone(),
None => {
return Err(AssetLoadError::MissingLabel {
base_path,
label: label.to_string(),
});
}
}
}
for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
self.send_asset_event(InternalAssetEvent::Loaded {
id: labeled_asset.handle.id(),
loaded_asset: labeled_asset.asset,
});
}
self.send_asset_event(InternalAssetEvent::Loaded {
id: base_handle.id(),
loaded_asset,
});
Ok(handle)
} else {
// if the path does not have a label, the handle must exist at this point
handle.unwrap()
};
self.send_loaded_asset(base_handle.id(), loaded_asset);
Ok(final_handle)
}
Err(err) => {
self.send_asset_event(InternalAssetEvent::Failed {
@ -462,6 +510,16 @@ impl AssetServer {
}
}
/// Sends a load event for the given `loaded_asset` and does the same recursively for all
/// labeled assets.
fn send_loaded_asset(&self, id: UntypedAssetId, mut loaded_asset: ErasedLoadedAsset) {
for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset);
}
self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset });
}
/// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded.
pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
let server = self.clone();
@ -623,7 +681,10 @@ impl AssetServer {
)
.into(),
}),
Err(_) => server.send_asset_event(InternalAssetEvent::Failed { id }),
Err(err) => {
error!("Failed to load folder. {err}");
server.send_asset_event(InternalAssetEvent::Failed { id });
},
}
})
.detach();
@ -646,6 +707,9 @@ impl AssetServer {
}
/// Retrieves the main [`LoadState`] of a given asset `id`.
///
/// Note that this is "just" the root asset load state. To check if an asset _and_ its recursive
/// dependencies have loaded, see [`AssetServer::is_loaded_with_dependencies`].
pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
self.data.infos.read().get(id.into()).map(|i| i.load_state)
}
@ -676,6 +740,13 @@ impl AssetServer {
.unwrap_or(RecursiveDependencyLoadState::NotLoaded)
}
/// Returns true if the asset and all of its dependencies (recursive) have been loaded.
pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
let id = id.into();
self.load_state(id) == LoadState::Loaded
&& self.recursive_dependency_load_state(id) == RecursiveDependencyLoadState::Loaded
}
/// Returns an active handle for the given path, if the asset at the given path has already started loading,
/// or is still "alive".
pub fn get_handle<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Option<Handle<A>> {
@ -691,6 +762,12 @@ impl AssetServer {
self.data.infos.read().get_id_handle(id)
}
/// Returns `true` if the given `id` corresponds to an asset that is managed by this [`AssetServer`].
/// Otherwise, returns false.
pub fn is_managed(&self, id: impl Into<UntypedAssetId>) -> bool {
self.data.infos.read().contains_key(id.into())
}
/// Returns an active untyped handle for the given path, if the asset at the given path has already started loading,
/// or is still "alive".
pub fn get_handle_untyped<'a>(&self, path: impl Into<AssetPath<'a>>) -> Option<UntypedHandle> {
@ -776,44 +853,57 @@ impl AssetServer {
AssetServerMode::Processed { .. } => source.processed_reader()?,
};
let reader = asset_reader.read(asset_path.path()).await?;
match asset_reader.read_meta_bytes(asset_path.path()).await {
Ok(meta_bytes) => {
// TODO: this isn't fully minimal yet. we only need the loader
let minimal: AssetMetaMinimal = ron::de::from_bytes(&meta_bytes).map_err(|e| {
AssetLoadError::DeserializeMeta {
path: asset_path.clone_owned(),
error: Box::new(DeserializeMetaError::DeserializeMinimal(e)),
}
})?;
let loader_name = match minimal.asset {
AssetActionMinimal::Load { loader } => loader,
AssetActionMinimal::Process { .. } => {
return Err(AssetLoadError::CannotLoadProcessedAsset {
path: asset_path.clone_owned(),
})
}
AssetActionMinimal::Ignore => {
return Err(AssetLoadError::CannotLoadIgnoredAsset {
path: asset_path.clone_owned(),
})
}
};
let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
AssetLoadError::DeserializeMeta {
path: asset_path.clone_owned(),
error: Box::new(e),
}
})?;
let read_meta = match &self.data.meta_check {
AssetMetaCheck::Always => true,
AssetMetaCheck::Paths(paths) => paths.contains(asset_path),
AssetMetaCheck::Never => false,
};
Ok((meta, loader, reader))
if read_meta {
match asset_reader.read_meta_bytes(asset_path.path()).await {
Ok(meta_bytes) => {
// TODO: this isn't fully minimal yet. we only need the loader
let minimal: AssetMetaMinimal =
ron::de::from_bytes(&meta_bytes).map_err(|e| {
AssetLoadError::DeserializeMeta {
path: asset_path.clone_owned(),
error: Box::new(DeserializeMetaError::DeserializeMinimal(e)),
}
})?;
let loader_name = match minimal.asset {
AssetActionMinimal::Load { loader } => loader,
AssetActionMinimal::Process { .. } => {
return Err(AssetLoadError::CannotLoadProcessedAsset {
path: asset_path.clone_owned(),
})
}
AssetActionMinimal::Ignore => {
return Err(AssetLoadError::CannotLoadIgnoredAsset {
path: asset_path.clone_owned(),
})
}
};
let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
AssetLoadError::DeserializeMeta {
path: asset_path.clone_owned(),
error: Box::new(e),
}
})?;
Ok((meta, loader, reader))
}
Err(AssetReaderError::NotFound(_)) => {
let loader = self.get_path_asset_loader(asset_path).await?;
let meta = loader.default_meta();
Ok((meta, loader, reader))
}
Err(err) => Err(err.into()),
}
Err(AssetReaderError::NotFound(_)) => {
let loader = self.get_path_asset_loader(asset_path).await?;
let meta = loader.default_meta();
Ok((meta, loader, reader))
}
Err(err) => Err(err.into()),
} else {
let loader = self.get_path_asset_loader(asset_path).await?;
let meta = loader.default_meta();
Ok((meta, loader, reader))
}
}

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_core"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides core functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -11,12 +11,12 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0", features = ["bevy_reflect"] }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1", features = ["bevy_reflect"] }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# other
bytemuck = "1.5"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_core_pipeline"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
authors = [
"Bevy Contributors <bevyengine@gmail.com>",
@ -19,17 +19,17 @@ tonemapping_luts = ["bevy_render/ktx2", "bevy_render/zstd"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1" }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
serde = { version = "1", features = ["derive"] }
bitflags = "2.3"

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_diagnostic"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides diagnostic functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -14,12 +14,12 @@ dynamic_linking = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_time = { path = "../bevy_time", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_time = { path = "../bevy_time", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# MacOS
[target.'cfg(all(target_os="macos"))'.dependencies]

View File

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

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_ecs"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Bevy Engine's entity component system"
homepage = "https://bevyengine.org"
@ -15,11 +15,11 @@ multi-threaded = ["bevy_tasks/multi-threaded"]
default = ["bevy_reflect"]
[dependencies]
bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", optional = true }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_ecs_macros = { path = "macros", version = "0.12.0" }
bevy_ptr = { path = "../bevy_ptr", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", optional = true }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_ecs_macros = { path = "macros", version = "0.12.1" }
async-channel = "1.4"
event-listener = "2.5"

View File

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

View File

@ -749,8 +749,30 @@ impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
}
}
/// A system that calls [`Events::update`] once per frame.
pub fn event_update_system<T: Event>(mut events: ResMut<Events<T>>) {
#[doc(hidden)]
#[derive(Resource, Default)]
pub struct EventUpdateSignal(bool);
/// A system that queues a call to [`Events::update`].
pub fn event_queue_update_system(signal: Option<ResMut<EventUpdateSignal>>) {
if let Some(mut s) = signal {
s.0 = true;
}
}
/// A system that calls [`Events::update`].
pub fn event_update_system<T: Event>(
signal: Option<ResMut<EventUpdateSignal>>,
mut events: ResMut<Events<T>>,
) {
if let Some(mut s) = signal {
// If we haven't got a signal to update the events, but we *could* get such a signal
// return early and update the events later.
if !std::mem::replace(&mut s.0, false) {
return;
}
}
events.update();
}

View File

@ -189,15 +189,18 @@ fn insert_reflect(
type_registry: &TypeRegistry,
component: Box<dyn Reflect>,
) {
let type_info = component.reflect_type_path();
let type_info = component
.get_represented_type_info()
.expect("component should represent a type.");
let type_path = type_info.type_path();
let Some(mut entity) = world.get_entity_mut(entity) else {
panic!("error[B0003]: Could not insert a reflected component (of type {}) for entity {entity:?} because it doesn't exist in this World.", component.reflect_type_path());
panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity:?} because it doesn't exist in this World.");
};
let Some(type_registration) = type_registry.get_with_type_path(type_info) else {
panic!("Could not get type registration (for component type {}) because it doesn't exist in the TypeRegistry.", component.reflect_type_path());
let Some(type_registration) = type_registry.get_with_type_path(type_path) else {
panic!("Could not get type registration (for component type {type_path}) because it doesn't exist in the TypeRegistry.");
};
let Some(reflect_component) = type_registration.data::<ReflectComponent>() else {
panic!("Could not get ReflectComponent data (for component type {}) because it doesn't exist in this TypeRegistration.", component.reflect_type_path());
panic!("Could not get ReflectComponent data (for component type {type_path}) because it doesn't exist in this TypeRegistration.");
};
reflect_component.insert(&mut entity, &*component);
}
@ -346,17 +349,22 @@ mod tests {
let mut commands = system_state.get_mut(&mut world);
let entity = commands.spawn_empty().id();
let entity2 = commands.spawn_empty().id();
let boxed_reflect_component_a = Box::new(ComponentA(916)) as Box<dyn Reflect>;
let boxed_reflect_component_a_clone = boxed_reflect_component_a.clone_value();
commands
.entity(entity)
.insert_reflect(boxed_reflect_component_a);
commands
.entity(entity2)
.insert_reflect(boxed_reflect_component_a_clone);
system_state.apply(&mut world);
assert_eq!(
world.entity(entity).get::<ComponentA>(),
Some(&ComponentA(916))
world.entity(entity2).get::<ComponentA>()
);
}

View File

@ -9,8 +9,11 @@ struct CommandMeta {
/// SAFETY: The `value` must point to a value of type `T: Command`,
/// where `T` is some specific type that was used to produce this metadata.
///
/// `world` is optional to allow this one function pointer to perform double-duty as a drop.
///
/// Returns the size of `T` in bytes.
apply_command_and_get_size: unsafe fn(value: OwningPtr<Unaligned>, world: &mut World) -> usize,
consume_command_and_get_size:
unsafe fn(value: OwningPtr<Unaligned>, world: &mut Option<&mut World>) -> usize,
}
/// Densely and efficiently stores a queue of heterogenous types implementing [`Command`].
@ -53,11 +56,16 @@ impl CommandQueue {
}
let meta = CommandMeta {
apply_command_and_get_size: |command, world| {
// SAFETY: According to the invariants of `CommandMeta.apply_command_and_get_size`,
consume_command_and_get_size: |command, world| {
// SAFETY: According to the invariants of `CommandMeta.consume_command_and_get_size`,
// `command` must point to a value of type `C`.
let command: C = unsafe { command.read_unaligned() };
command.apply(world);
match world {
// Apply command to the provided world...
Some(world) => command.apply(world),
// ...or discard it.
None => drop(command),
}
std::mem::size_of::<C>()
},
};
@ -97,18 +105,26 @@ impl CommandQueue {
// flush the previously queued entities
world.flush();
// Pointer that will iterate over the entries of the buffer.
let mut cursor = self.bytes.as_mut_ptr();
self.apply_or_drop_queued(Some(world));
}
// The address of the end of the buffer.
let end_addr = cursor as usize + self.bytes.len();
/// If `world` is [`Some`], this will apply the queued [commands](`Command`).
/// If `world` is [`None`], this will drop the queued [commands](`Command`) (without applying them).
/// This clears the queue.
#[inline]
fn apply_or_drop_queued(&mut self, mut world: Option<&mut World>) {
// The range of pointers of the filled portion of `self.bytes`.
let bytes_range = self.bytes.as_mut_ptr_range();
// Pointer that will iterate over the entries of the buffer.
let mut cursor = bytes_range.start;
// Reset the buffer, so it can be reused after this function ends.
// In the loop below, ownership of each command will be transferred into user code.
// SAFETY: `set_len(0)` is always valid.
unsafe { self.bytes.set_len(0) };
while (cursor as usize) < end_addr {
while cursor < bytes_range.end {
// SAFETY: The cursor is either at the start of the buffer, or just after the previous command.
// Since we know that the cursor is in bounds, it must point to the start of a new command.
let meta = unsafe { cursor.cast::<CommandMeta>().read_unaligned() };
@ -127,7 +143,7 @@ impl CommandQueue {
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
// since they were stored next to each other by `.push()`.
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
let size = unsafe { (meta.apply_command_and_get_size)(cmd, world) };
let size = unsafe { (meta.consume_command_and_get_size)(cmd, &mut world) };
// Advance the cursor past the command. For ZSTs, the cursor will not move.
// At this point, it will either point to the next `CommandMeta`,
// or the cursor will be out of bounds and the loop will end.
@ -138,6 +154,12 @@ impl CommandQueue {
}
}
impl Drop for CommandQueue {
fn drop(&mut self) {
self.apply_or_drop_queued(None);
}
}
#[cfg(test)]
mod test {
use super::*;
@ -188,6 +210,27 @@ mod test {
assert_eq!(drops_b.load(Ordering::Relaxed), 1);
}
/// Asserts that inner [commands](`Command`) are dropped on early drop of [`CommandQueue`].
/// Originally identified as an issue in [#10676](https://github.com/bevyengine/bevy/issues/10676)
#[test]
fn test_command_queue_inner_drop_early() {
let mut queue = CommandQueue::default();
let (dropcheck_a, drops_a) = DropCheck::new();
let (dropcheck_b, drops_b) = DropCheck::new();
queue.push(dropcheck_a);
queue.push(dropcheck_b);
assert_eq!(drops_a.load(Ordering::Relaxed), 0);
assert_eq!(drops_b.load(Ordering::Relaxed), 0);
drop(queue);
assert_eq!(drops_a.load(Ordering::Relaxed), 1);
assert_eq!(drops_b.load(Ordering::Relaxed), 1);
}
struct SpawnCommand;
impl Command for SpawnCommand {

View File

@ -55,10 +55,18 @@ impl World {
&mut self,
system: S,
) -> SystemId {
self.register_boxed_system(Box::new(IntoSystem::into_system(system)))
}
/// Similar to [`Self::register_system`], but allows passing in a [`BoxedSystem`].
///
/// This is useful if the [`IntoSystem`] implementor has already been turned into a
/// [`System`](crate::system::System) trait object and put in a [`Box`].
pub fn register_boxed_system(&mut self, system: BoxedSystem) -> SystemId {
SystemId(
self.spawn(RegisteredSystem {
initialized: false,
system: Box::new(IntoSystem::into_system(system)),
system,
})
.id(),
)

View File

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

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gizmos"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides gizmos for Bevy Engine"
homepage = "https://bevyengine.org"
@ -13,15 +13,15 @@ webgl = []
[dependencies]
# Bevy
bevy_pbr = { path = "../bevy_pbr", version = "0.12.0", optional = true }
bevy_sprite = { path = "../bevy_sprite", version = "0.12.0", optional = true }
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_pbr = { path = "../bevy_pbr", version = "0.12.1", optional = true }
bevy_sprite = { path = "../bevy_sprite", version = "0.12.1", optional = true }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }

View File

@ -28,6 +28,8 @@ struct VertexOutput {
@location(0) color: vec4<f32>,
};
const EPSILON: f32 = 4.88e-04;
@vertex
fn vertex(vertex: VertexInput) -> VertexOutput {
var positions = array<vec3<f32>, 6>(
@ -79,7 +81,6 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
if line_gizmo.depth_bias >= 0. {
depth = clip.z * (1. - line_gizmo.depth_bias);
} else {
let epsilon = 4.88e-04;
// depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
// and when equal to 0.0, it is exactly equal to depth.
// the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0
@ -87,7 +88,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
// of this value means nothing can be in front of this
// The reason this uses an exponential function is that it makes it much easier for the
// user to chose a value that is convenient for them
depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - epsilon));
depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON));
}
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
@ -101,8 +102,10 @@ fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
// Interpolate a towards b until it's at the near plane.
let distance_a = a.z - a.w;
let distance_b = b.z - b.w;
let t = distance_a / (distance_a - distance_b);
return a + (b - a) * t;
// Add an epsilon to the interpolator to ensure that the point is
// not just behind the clip plane due to floating-point imprecision.
let t = distance_a / (distance_a - distance_b) + EPSILON;
return mix(a, b, t);
}
return a;
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gltf"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Bevy Engine GLTF loading"
homepage = "https://bevyengine.org"
@ -13,22 +13,22 @@ pbr_transmission_textures = []
[dependencies]
# bevy
bevy_animation = { path = "../bevy_animation", version = "0.12.0", optional = true }
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_pbr = { path = "../bevy_pbr", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_scene = { path = "../bevy_scene", version = "0.12.0", features = ["bevy_render"] }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_animation = { path = "../bevy_animation", version = "0.12.1", optional = true }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_pbr = { path = "../bevy_pbr", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_scene = { path = "../bevy_scene", version = "0.12.1", features = ["bevy_render"] }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# other
gltf = { version = "1.3.0", default-features = false, features = [

View File

@ -545,7 +545,7 @@ async fn load_gltf<'a, 'b, 'c>(
let mut world = World::default();
let mut node_index_to_entity_map = HashMap::new();
let mut entity_to_skin_index_map = HashMap::new();
let mut scene_load_context = load_context.begin_labeled_asset();
world
.spawn(SpatialBundle::INHERITED_IDENTITY)
.with_children(|parent| {
@ -554,6 +554,7 @@ async fn load_gltf<'a, 'b, 'c>(
&node,
parent,
load_context,
&mut scene_load_context,
&mut node_index_to_entity_map,
&mut entity_to_skin_index_map,
&mut active_camera_found,
@ -606,8 +607,8 @@ async fn load_gltf<'a, 'b, 'c>(
joints: joint_entities,
});
}
let scene_handle = load_context.add_labeled_asset(scene_label(&scene), Scene::new(world));
let loaded_scene = scene_load_context.finish(Scene::new(world), None);
let scene_handle = load_context.add_loaded_labeled_asset(scene_label(&scene), loaded_scene);
if let Some(name) = scene.name() {
named_scenes.insert(name.to_string(), scene_handle.clone());
@ -853,9 +854,11 @@ fn load_material(
}
/// Loads a glTF node.
#[allow(clippy::too_many_arguments)]
fn load_node(
gltf_node: &gltf::Node,
world_builder: &mut WorldChildBuilder,
root_load_context: &LoadContext,
load_context: &mut LoadContext,
node_index_to_entity_map: &mut HashMap<usize, Entity>,
entity_to_skin_index_map: &mut HashMap<Entity, usize>,
@ -942,7 +945,9 @@ fn load_node(
// added when iterating over all the gltf materials (since the default material is
// not explicitly listed in the gltf).
// It also ensures an inverted scale copy is instantiated if required.
if !load_context.has_labeled_asset(&material_label) {
if !root_load_context.has_labeled_asset(&material_label)
&& !load_context.has_labeled_asset(&material_label)
{
load_material(&material, load_context, is_scale_inverted);
}
@ -1074,6 +1079,7 @@ fn load_node(
if let Err(err) = load_node(
&child,
parent,
root_load_context,
load_context,
node_index_to_entity_map,
entity_to_skin_index_map,

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_hierarchy"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides hierarchy functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -13,12 +13,12 @@ trace = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# other
smallvec = { version = "1.6", features = ["serde", "union", "const_generics"] }

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_internal"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "An internal Bevy crate used to facilitate optional dynamic linking via the 'dynamic_linking' feature"
homepage = "https://bevyengine.org"
@ -120,36 +120,36 @@ embedded_watcher = ["bevy_asset?/embedded_watcher"]
[dependencies]
# bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_input = { path = "../bevy_input", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_time = { path = "../bevy_time", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_window = { path = "../bevy_window", version = "0.12.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
bevy_input = { path = "../bevy_input", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_ptr = { path = "../bevy_ptr", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_time = { path = "../bevy_time", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_window = { path = "../bevy_window", version = "0.12.1" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
# bevy (optional)
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.12.0" }
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.12.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.12.0" }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.12.0" }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.12.0" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.12.0" }
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.12.0" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.12.0" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.12.0" }
bevy_text = { path = "../bevy_text", optional = true, version = "0.12.0" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.12.0" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.12.0" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.12.0" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.12.0", default-features = false }
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.12.1" }
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.12.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.12.1" }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.12.1" }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.12.1" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.12.1" }
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.12.1" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.12.1" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.12.1" }
bevy_text = { path = "../bevy_text", optional = true, version = "0.12.1" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.12.1" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.12.1" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.12.1" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.12.1", default-features = false }

View File

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

View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_math"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides math functionality for Bevy Engine"
homepage = "https://bevyengine.org"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_mikktspace"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
authors = ["Benjamin Wasty <benny.wasty@gmail.com>", "David Harvey-Macaulay <alteous@outlook.com>", "Layl Bongers <LaylConway@users.noreply.github.com>"]
description = "Mikkelsen tangent space algorithm"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_pbr"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Adds PBR rendering to Bevy Engine"
homepage = "https://bevyengine.org"
@ -14,17 +14,17 @@ pbr_transmission_textures = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_window = { path = "../bevy_window", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_window = { path = "../bevy_window", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
# other
bitflags = "2.3"

View File

@ -1,5 +1,5 @@
use bevy_asset::{Asset, Handle};
use bevy_reflect::TypePath;
use bevy_reflect::{impl_type_path, Reflect};
use bevy_render::{
mesh::MeshVertexBufferLayout,
render_asset::RenderAssets,
@ -97,12 +97,17 @@ pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {
/// When used with `StandardMaterial` as the base, all the standard material fields are
/// present, so the `pbr_fragment` shader functions can be called from the extension shader (see
/// the `extended_material` example).
#[derive(Asset, Clone, TypePath)]
#[derive(Asset, Clone, Reflect)]
#[reflect(type_path = false)]
pub struct ExtendedMaterial<B: Material, E: MaterialExtension> {
pub base: B,
pub extension: E,
}
// We don't use the `TypePath` derive here due to a bug where `#[reflect(type_path = false)]`
// causes the `TypePath` derive to not generate an implementation.
impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial<B: Material, E: MaterialExtension>);
impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
type Data = (<B as AsBindGroup>::Data, <E as AsBindGroup>::Data);

View File

@ -39,7 +39,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
var out: VertexOutput;
#ifdef MORPH_TARGETS
var vertex = morph::morph_vertex(vertex_no_morph);
var vertex = morph_vertex(vertex_no_morph);
#else
var vertex = vertex_no_morph;
#endif
@ -107,6 +107,12 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
// See https://github.com/gfx-rs/naga/issues/2416
out.instance_index = get_instance_index(vertex_no_morph.instance_index);
#endif
#ifdef BASE_INSTANCE_WORKAROUND
// Hack: this ensures the push constant is always used, which works around this issue:
// https://github.com/bevyengine/bevy/issues/10509
// This can be removed when wgpu 0.19 is released
out.position.x += min(f32(get_instance_index(0u)), 0.0);
#endif
return out;
}

View File

@ -88,6 +88,13 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
out.instance_index = get_instance_index(vertex_no_morph.instance_index);
#endif
#ifdef BASE_INSTANCE_WORKAROUND
// Hack: this ensures the push constant is always used, which works around this issue:
// https://github.com/bevyengine/bevy/issues/10509
// This can be removed when wgpu 0.19 is released
out.position.x += min(f32(get_instance_index(0u)), 0.0);
#endif
return out;
}

View File

@ -53,10 +53,11 @@ fn specular_transmissive_light(world_position: vec4<f32>, frag_coord: vec3<f32>,
}
fn fetch_transmissive_background_non_rough(offset_position: vec2<f32>, frag_coord: vec3<f32>) -> vec4<f32> {
var background_color = textureSample(
var background_color = textureSampleLevel(
view_bindings::view_transmission_texture,
view_bindings::view_transmission_sampler,
offset_position,
0.0
);
#ifdef DEPTH_PREPASS
@ -152,10 +153,11 @@ fn fetch_transmissive_background(offset_position: vec2<f32>, frag_coord: vec3<f3
let modified_offset_position = offset_position + rotated_spiral_offset * blur_intensity * (1.0 - f32(pixel_checkboard) * 0.1);
// Sample the view transmission texture at the offset position + noise offset, to get the background color
var sample = textureSample(
var sample = textureSampleLevel(
view_bindings::view_transmission_texture,
view_bindings::view_transmission_sampler,
modified_offset_position,
0.0
);
#ifdef DEPTH_PREPASS

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_ptr"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Utilities for working with untyped pointers in a more safe way"
homepage = "https://bevyengine.org"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_reflect"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Dynamically interact with rust types"
homepage = "https://bevyengine.org"
@ -18,12 +18,12 @@ documentation = ["bevy_reflect_derive/documentation"]
[dependencies]
# bevy
bevy_math = { path = "../bevy_math", version = "0.12.0", features = [
bevy_math = { path = "../bevy_math", version = "0.12.1", features = [
"serialize",
], optional = true }
bevy_reflect_derive = { path = "bevy_reflect_derive", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_ptr = { path = "../bevy_ptr", version = "0.12.0" }
bevy_reflect_derive = { path = "bevy_reflect_derive", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_ptr = { path = "../bevy_ptr", version = "0.12.1" }
# other
erased-serde = "0.3"
@ -42,6 +42,8 @@ smol_str = { version = "0.2.0", optional = true }
ron = "0.8.0"
rmp-serde = "1.1"
bincode = "1.3"
serde_json = "1.0"
serde = { version = "1", features = ["derive"] }
[[example]]
name = "reflect_docs"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_reflect_derive"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Derive implementations for bevy_reflect"
homepage = "https://bevyengine.org"
@ -17,7 +17,7 @@ default = []
documentation = []
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" }
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.1" }
syn = { version = "2.0", features = ["full"] }
proc-macro2 = "1.0"

View File

@ -132,9 +132,9 @@ impl WhereClauseOptions {
let custom_bounds = active_bounds(field).map(|bounds| quote!(+ #bounds));
let bounds = if is_from_reflect {
quote!(#bevy_reflect_path::FromReflect #custom_bounds)
quote!(#bevy_reflect_path::FromReflect + #bevy_reflect_path::TypePath #custom_bounds)
} else {
quote!(#bevy_reflect_path::Reflect #custom_bounds)
quote!(#bevy_reflect_path::Reflect + #bevy_reflect_path::TypePath #custom_bounds)
};
(ty, bounds)

View File

@ -317,8 +317,8 @@ impl<'a> Serialize for EnumSerializer<'a> {
match variant_type {
VariantType::Unit => {
if self.enum_value.reflect_module_path() == Some("core::option")
&& self.enum_value.reflect_type_ident() == Some("Option")
if type_info.type_path_table().module_path() == Some("core::option")
&& type_info.type_path_table().ident() == Some("Option")
{
serializer.serialize_none()
} else {
@ -352,10 +352,9 @@ impl<'a> Serialize for EnumSerializer<'a> {
}
VariantType::Tuple if field_len == 1 => {
let field = self.enum_value.field_at(0).unwrap();
if self
.enum_value
.reflect_type_path()
.starts_with("core::option::Option")
if type_info.type_path_table().module_path() == Some("core::option")
&& type_info.type_path_table().ident() == Some("Option")
{
serializer.serialize_some(&TypedReflectSerializer::new(field, self.registry))
} else {
@ -464,8 +463,8 @@ impl<'a> Serialize for ArraySerializer<'a> {
#[cfg(test)]
mod tests {
use crate as bevy_reflect;
use crate::serde::ReflectSerializer;
use crate::{self as bevy_reflect, Struct};
use crate::{Reflect, ReflectSerialize, TypeRegistry};
use bevy_utils::HashMap;
use ron::extensions::Extensions;
@ -881,4 +880,43 @@ mod tests {
assert_eq!(expected, bytes);
}
#[test]
fn should_serialize_dynamic_option() {
#[derive(Default, Reflect)]
struct OtherStruct {
some: Option<SomeStruct>,
none: Option<SomeStruct>,
}
let value = OtherStruct {
some: Some(SomeStruct { foo: 999999999 }),
none: None,
};
let dynamic = value.clone_dynamic();
let reflect = dynamic.as_reflect();
let registry = get_registry();
let serializer = ReflectSerializer::new(reflect, &registry);
let mut buf = Vec::new();
let format = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut ser = serde_json::Serializer::with_formatter(&mut buf, format);
serializer.serialize(&mut ser).unwrap();
let output = std::str::from_utf8(&buf).unwrap();
let expected = r#"{
"bevy_reflect::serde::ser::tests::OtherStruct": {
"some": {
"foo": 999999999
},
"none": null
}
}"#;
assert_eq!(expected, output);
}
}

View File

@ -0,0 +1,16 @@
use bevy_reflect::Reflect;
mod nested_generics {
use super::*;
#[derive(Reflect)]
struct Foo<T>(T);
#[derive(Reflect)]
struct Bar<T>(Foo<T>);
#[derive(Reflect)]
struct Baz<T>(Bar<Foo<T>>);
}
fn main() {}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_render"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides rendering functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -35,23 +35,23 @@ webgl = ["wgpu/webgl"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_core = { path = "../bevy_core", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.12.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_render_macros = { path = "macros", version = "0.12.0" }
bevy_time = { path = "../bevy_time", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_window = { path = "../bevy_window", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_core = { path = "../bevy_core", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.12.1" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_render_macros = { path = "macros", version = "0.12.1" }
bevy_time = { path = "../bevy_time", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_window = { path = "../bevy_window", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
# rendering
image = { version = "0.24", default-features = false }

View File

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

View File

@ -259,30 +259,24 @@ impl AssetLoader for ShaderLoader {
) -> BoxedFuture<'a, Result<Shader, Self::Error>> {
Box::pin(async move {
let ext = load_context.path().extension().unwrap().to_str().unwrap();
let path = load_context.asset_path().to_string();
// On windows, the path will inconsistently use \ or /.
// TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511
let path = path.replace(std::path::MAIN_SEPARATOR, "/");
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut shader = match ext {
"spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()),
"wgsl" => Shader::from_wgsl(
String::from_utf8(bytes)?,
load_context.path().to_string_lossy(),
),
"vert" => Shader::from_glsl(
String::from_utf8(bytes)?,
naga::ShaderStage::Vertex,
load_context.path().to_string_lossy(),
),
"frag" => Shader::from_glsl(
String::from_utf8(bytes)?,
naga::ShaderStage::Fragment,
load_context.path().to_string_lossy(),
),
"comp" => Shader::from_glsl(
String::from_utf8(bytes)?,
naga::ShaderStage::Compute,
load_context.path().to_string_lossy(),
),
"wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path),
"vert" => {
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path)
}
"frag" => {
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path)
}
"comp" => {
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Compute, path)
}
_ => panic!("unhandled extension: {ext}"),
};

View File

@ -269,7 +269,7 @@ pub fn calculate_bounds(
for (entity, mesh_handle) in &without_aabb {
if let Some(mesh) = meshes.get(mesh_handle) {
if let Some(aabb) = mesh.compute_aabb() {
commands.entity(entity).insert(aabb);
commands.entity(entity).try_insert(aabb);
}
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_scene"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides scene functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -14,15 +14,15 @@ serialize = ["dep:serde", "uuid/serde"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_render = { path = "../bevy_render", version = "0.12.0", optional = true }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_render = { path = "../bevy_render", version = "0.12.1", optional = true }
# other
serde = { version = "1.0", features = ["derive"], optional = true }

View File

@ -1,5 +1,5 @@
use crate::{DynamicScene, Scene};
use bevy_asset::{AssetEvent, AssetId, Assets};
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
use bevy_ecs::{
entity::Entity,
event::{Event, Events, ManualEventReader},
@ -63,8 +63,8 @@ pub struct SceneSpawner {
spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, Vec<InstanceId>>,
spawned_instances: HashMap<InstanceId, InstanceInfo>,
scene_asset_event_reader: ManualEventReader<AssetEvent<DynamicScene>>,
dynamic_scenes_to_spawn: Vec<(AssetId<DynamicScene>, InstanceId)>,
scenes_to_spawn: Vec<(AssetId<Scene>, InstanceId)>,
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId)>,
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId)>,
scenes_to_despawn: Vec<AssetId<DynamicScene>>,
instances_to_despawn: Vec<InstanceId>,
scenes_with_parent: Vec<(InstanceId, Entity)>,
@ -127,7 +127,7 @@ pub enum SceneSpawnError {
impl SceneSpawner {
/// Schedule the spawn of a new instance of the provided dynamic scene.
pub fn spawn_dynamic(&mut self, id: impl Into<AssetId<DynamicScene>>) -> InstanceId {
pub fn spawn_dynamic(&mut self, id: impl Into<Handle<DynamicScene>>) -> InstanceId {
let instance_id = InstanceId::new();
self.dynamic_scenes_to_spawn.push((id.into(), instance_id));
instance_id
@ -136,7 +136,7 @@ impl SceneSpawner {
/// Schedule the spawn of a new instance of the provided dynamic scene as a child of `parent`.
pub fn spawn_dynamic_as_child(
&mut self,
id: impl Into<AssetId<DynamicScene>>,
id: impl Into<Handle<DynamicScene>>,
parent: Entity,
) -> InstanceId {
let instance_id = InstanceId::new();
@ -146,14 +146,14 @@ impl SceneSpawner {
}
/// Schedule the spawn of a new instance of the provided scene.
pub fn spawn(&mut self, id: impl Into<AssetId<Scene>>) -> InstanceId {
pub fn spawn(&mut self, id: impl Into<Handle<Scene>>) -> InstanceId {
let instance_id = InstanceId::new();
self.scenes_to_spawn.push((id.into(), instance_id));
instance_id
}
/// Schedule the spawn of a new instance of the provided scene as a child of `parent`.
pub fn spawn_as_child(&mut self, id: impl Into<AssetId<Scene>>, parent: Entity) -> InstanceId {
pub fn spawn_as_child(&mut self, id: impl Into<Handle<Scene>>, parent: Entity) -> InstanceId {
let instance_id = InstanceId::new();
self.scenes_to_spawn.push((id.into(), instance_id));
self.scenes_with_parent.push((instance_id, parent));
@ -296,21 +296,21 @@ impl SceneSpawner {
pub fn spawn_queued_scenes(&mut self, world: &mut World) -> Result<(), SceneSpawnError> {
let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn);
for (id, instance_id) in scenes_to_spawn {
for (handle, instance_id) in scenes_to_spawn {
let mut entity_map = HashMap::default();
match Self::spawn_dynamic_internal(world, id, &mut entity_map) {
match Self::spawn_dynamic_internal(world, handle.id(), &mut entity_map) {
Ok(_) => {
self.spawned_instances
.insert(instance_id, InstanceInfo { entity_map });
let spawned = self
.spawned_dynamic_scenes
.entry(id)
.entry(handle.id())
.or_insert_with(Vec::new);
spawned.push(instance_id);
}
Err(SceneSpawnError::NonExistentScene { .. }) => {
self.dynamic_scenes_to_spawn.push((id, instance_id));
self.dynamic_scenes_to_spawn.push((handle, instance_id));
}
Err(err) => return Err(err),
}
@ -319,10 +319,10 @@ impl SceneSpawner {
let scenes_to_spawn = std::mem::take(&mut self.scenes_to_spawn);
for (scene_handle, instance_id) in scenes_to_spawn {
match self.spawn_sync_internal(world, scene_handle, instance_id) {
match self.spawn_sync_internal(world, scene_handle.id(), instance_id) {
Ok(_) => {}
Err(SceneSpawnError::NonExistentRealScene { id: handle }) => {
self.scenes_to_spawn.push((handle, instance_id));
Err(SceneSpawnError::NonExistentRealScene { .. }) => {
self.scenes_to_spawn.push((scene_handle, instance_id));
}
Err(err) => return Err(err),
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_sprite"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides sprite functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -13,19 +13,19 @@ webgl = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = [
"bevy",
] }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
# other
bytemuck = { version = "1.5", features = ["derive"] }

View File

@ -131,7 +131,7 @@ pub fn calculate_bounds_2d(
for (entity, mesh_handle) in &meshes_without_aabb {
if let Some(mesh) = meshes.get(&mesh_handle.0) {
if let Some(aabb) = mesh.compute_aabb() {
commands.entity(entity).insert(aabb);
commands.entity(entity).try_insert(aabb);
}
}
}
@ -144,7 +144,7 @@ pub fn calculate_bounds_2d(
center: (-sprite.anchor.as_vec() * size).extend(0.0).into(),
half_extents: (0.5 * size).extend(0.0).into(),
};
commands.entity(entity).insert(aabb);
commands.entity(entity).try_insert(aabb);
}
}
for (entity, atlas_sprite, atlas_handle) in &atlases_without_aabb {
@ -158,7 +158,7 @@ pub fn calculate_bounds_2d(
center: (-atlas_sprite.anchor.as_vec() * size).extend(0.0).into(),
half_extents: (0.5 * size).extend(0.0).into(),
};
commands.entity(entity).insert(aabb);
commands.entity(entity).try_insert(aabb);
}
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_tasks"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "A task executor for Bevy Engine"
homepage = "https://bevyengine.org"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_text"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides text functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -14,16 +14,16 @@ default_font = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_sprite = { path = "../bevy_sprite", version = "0.12.0" }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_window = { path = "../bevy_window", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_sprite = { path = "../bevy_sprite", version = "0.12.1" }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_window = { path = "../bevy_window", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# other
ab_glyph = "0.2.6"

View File

@ -20,11 +20,15 @@ impl Font {
pub fn get_outlined_glyph_texture(outlined_glyph: OutlinedGlyph) -> Image {
let bounds = outlined_glyph.px_bounds();
let width = bounds.width() as usize;
let height = bounds.height() as usize;
// Increase the length of the glyph texture by 2-pixels on each axis to make space
// for a pixel wide transparent border along its edges.
let width = bounds.width() as usize + 2;
let height = bounds.height() as usize + 2;
let mut alpha = vec![0.0; width * height];
outlined_glyph.draw(|x, y, v| {
alpha[y as usize * width + x as usize] = v;
// Displace the glyph by 1 pixel on each axis so that it is drawn in the center of the texture.
// This leaves a pixel wide transparent border around the glyph.
alpha[(y + 1) as usize * width + x as usize + 1] = v;
});
// TODO: make this texture grayscale

View File

@ -65,7 +65,7 @@ impl FontAtlas {
Self {
texture_atlas: texture_atlases.add(texture_atlas),
glyph_to_atlas_index: HashMap::default(),
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 1),
dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0),
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_time"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides time functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -15,10 +15,10 @@ bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# other
crossbeam-channel = "0.5.0"

View File

@ -19,11 +19,6 @@ pub use time::*;
pub use timer::*;
pub use virt::*;
use bevy_ecs::system::{Res, ResMut};
use bevy_utils::{tracing::warn, Duration, Instant};
pub use crossbeam_channel::TrySendError;
use crossbeam_channel::{Receiver, Sender};
pub mod prelude {
//! The Bevy Time Prelude.
#[doc(hidden)]
@ -31,7 +26,11 @@ pub mod prelude {
}
use bevy_app::{prelude::*, RunFixedUpdateLoop};
use bevy_ecs::event::{event_queue_update_system, EventUpdateSignal};
use bevy_ecs::prelude::*;
use bevy_utils::{tracing::warn, Duration, Instant};
pub use crossbeam_channel::TrySendError;
use crossbeam_channel::{Receiver, Sender};
/// Adds time functionality to Apps.
#[derive(Default)]
@ -61,6 +60,10 @@ impl Plugin for TimePlugin {
)
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule);
// ensure the events are not dropped until `FixedUpdate` systems can observe them
app.init_resource::<EventUpdateSignal>()
.add_systems(FixedUpdate, event_queue_update_system);
#[cfg(feature = "bevy_ci_testing")]
if let Some(ci_testing_config) = app
.world

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_transform"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides transform functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,16 +10,16 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0", features = ["bevy_reflect"] }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = ["bevy"] }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1", features = ["bevy_reflect"] }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = ["bevy"] }
serde = { version = "1", features = ["derive"], optional = true }
thiserror = "1.0"
[dev-dependencies]
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
approx = "0.5.1"
glam = { version = "0.24", features = ["approx"] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_ui"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "A custom ECS-driven UI framework built specifically for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,25 +10,25 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_asset = { path = "../bevy_asset", version = "0.12.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_input = { path = "../bevy_input", version = "0.12.0" }
bevy_log = { path = "../bevy_log", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_asset = { path = "../bevy_asset", version = "0.12.1" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
bevy_input = { path = "../bevy_input", version = "0.12.1" }
bevy_log = { path = "../bevy_log", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = [
"bevy",
] }
bevy_render = { path = "../bevy_render", version = "0.12.0" }
bevy_sprite = { path = "../bevy_sprite", version = "0.12.0" }
bevy_text = { path = "../bevy_text", version = "0.12.0", optional = true }
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
bevy_window = { path = "../bevy_window", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_render = { path = "../bevy_render", version = "0.12.1" }
bevy_sprite = { path = "../bevy_sprite", version = "0.12.1" }
bevy_text = { path = "../bevy_text", version = "0.12.1", optional = true }
bevy_transform = { path = "../bevy_transform", version = "0.12.1" }
bevy_window = { path = "../bevy_window", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# other
taffy = { version = "0.3.10" }

View File

@ -1,5 +1,4 @@
use crate::{camera_config::UiCameraConfig, CalculatedClip, Node, UiScale, UiStack};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
change_detection::DetectChangesMut,
entity::Entity,
@ -9,7 +8,7 @@ use bevy_ecs::{
system::{Local, Query, Res},
};
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
use bevy_math::Vec2;
use bevy_math::{Rect, Vec2};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ViewVisibility};
use bevy_transform::components::GlobalTransform;
@ -57,25 +56,16 @@ impl Default for Interaction {
/// A component storing the position of the mouse relative to the node, (0., 0.) being the top-left corner and (1., 1.) being the bottom-right
/// If the mouse is not over the node, the value will go beyond the range of (0., 0.) to (1., 1.)
/// A None value means that the cursor position is unknown.
///
/// It can be used alongside interaction to get the position of the press.
#[derive(
Component,
Deref,
DerefMut,
Copy,
Clone,
Default,
PartialEq,
Debug,
Reflect,
Serialize,
Deserialize,
)]
#[derive(Component, Copy, Clone, Default, PartialEq, Debug, Reflect, Serialize, Deserialize)]
#[reflect(Component, Serialize, Deserialize, PartialEq)]
pub struct RelativeCursorPosition {
/// Cursor position relative to size and position of the Node.
/// Visible area of the Node relative to the size of the entire Node.
pub normalized_visible_node_rect: Rect,
/// Cursor position relative to the size and position of the Node.
/// A None value indicates that the cursor position is unknown.
pub normalized: Option<Vec2>,
}
@ -83,7 +73,7 @@ impl RelativeCursorPosition {
/// A helper function to check if the mouse is over the node
pub fn mouse_over(&self) -> bool {
self.normalized
.map(|position| (0.0..1.).contains(&position.x) && (0.0..1.).contains(&position.y))
.map(|position| self.normalized_visible_node_rect.contains(position))
.unwrap_or(false)
}
}
@ -216,22 +206,24 @@ pub fn ui_focus_system(
}
}
let position = node.global_transform.translation();
let ui_position = position.truncate();
let extents = node.node.size() / 2.0;
let mut min = ui_position - extents;
if let Some(clip) = node.calculated_clip {
min = Vec2::max(min, clip.clip.min);
}
let node_rect = node.node.logical_rect(node.global_transform);
// Intersect with the calculated clip rect to find the bounds of the visible region of the node
let visible_rect = node
.calculated_clip
.map(|clip| node_rect.intersect(clip.clip))
.unwrap_or(node_rect);
// The mouse position relative to the node
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
// Coordinates are relative to the entire node, not just the visible region.
let relative_cursor_position = cursor_position
.map(|cursor_position| (cursor_position - min) / node.node.size());
.map(|cursor_position| (cursor_position - node_rect.min) / node_rect.size());
// If the current cursor position is within the bounds of the node, consider it for
// If the current cursor position is within the bounds of the node's visible area, consider it for
// clicking
let relative_cursor_position_component = RelativeCursorPosition {
normalized_visible_node_rect: visible_rect.normalize(node_rect),
normalized: relative_cursor_position,
};

View File

@ -410,7 +410,7 @@ pub fn resolve_outlines_system(
.max(0.);
node.outline_offset = outline
.width
.offset
.resolve(node.size().x, viewport_size)
.unwrap_or(0.)
.max(0.);

View File

@ -344,6 +344,9 @@ impl Default for ButtonBundle {
}
/// A UI node that is rendered using a [`UiMaterial`]
///
/// Adding a `BackgroundColor` component to an entity with this bundle will ignore the custom
/// material and use the background color instead.
#[derive(Bundle, Clone, Debug)]
pub struct MaterialNodeBundle<M: UiMaterial> {
/// Describes the logical size of the node

View File

@ -21,6 +21,7 @@ use crate::{
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, AssetEvent, AssetId, Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_math::Vec3Swizzles;
use bevy_math::{Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec4Swizzles};
use bevy_render::{
camera::Camera,
@ -618,13 +619,14 @@ pub fn extract_text_uinodes(
>,
) {
// TODO: Support window-independent UI scale: https://github.com/bevyengine/bevy/issues/5621
let scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.0)
* ui_scale.0;
let inverse_scale_factor = (scale_factor as f32).recip();
let scale_factor = (windows
.get_single()
.map(|window| window.scale_factor())
.unwrap_or(1.)
* ui_scale.0) as f32;
let inverse_scale_factor = scale_factor.recip();
for (uinode, global_transform, text, text_layout_info, view_visibility, clip) in
uinode_query.iter()
@ -633,8 +635,20 @@ pub fn extract_text_uinodes(
if !view_visibility.get() || uinode.size().x == 0. || uinode.size().y == 0. {
continue;
}
let transform = global_transform.compute_matrix()
* Mat4::from_translation(-0.5 * uinode.size().extend(0.));
let mut affine = global_transform.affine();
// Align the text to the nearest physical pixel:
// * Translate by minus the text node's half-size
// (The transform translates to the center of the node but the text coordinates are relative to the node's top left corner)
// * Multiply the logical coordinates by the scale factor to get its position in physical coordinates
// * Round the physical position to the nearest physical pixel
// * Multiply by the rounded physical position by the inverse scale factor to return to logical coordinates
let logical_top_left = affine.translation.xy() - 0.5 * uinode.size();
let physical_nearest_pixel = (logical_top_left * scale_factor).round();
let logical_top_left_nearest_pixel = physical_nearest_pixel * inverse_scale_factor;
affine.translation = logical_top_left_nearest_pixel.extend(0.).into();
let transform = Mat4::from(affine);
let mut color = Color::WHITE;
let mut current_section = usize::MAX;

View File

@ -70,7 +70,7 @@ where
.init_resource::<ExtractedUiMaterials<M>>()
.init_resource::<ExtractedUiMaterialNodes<M>>()
.init_resource::<RenderUiMaterials<M>>()
.init_resource::<UiMaterialMeta>()
.init_resource::<UiMaterialMeta<M>>()
.init_resource::<SpecializedRenderPipelines<UiMaterialPipeline<M>>>()
.add_systems(
ExtractSchedule,
@ -98,16 +98,18 @@ where
}
#[derive(Resource)]
pub struct UiMaterialMeta {
pub struct UiMaterialMeta<M: UiMaterial> {
vertices: BufferVec<UiMaterialVertex>,
view_bind_group: Option<BindGroup>,
marker: PhantomData<M>,
}
impl Default for UiMaterialMeta {
impl<M: UiMaterial> Default for UiMaterialMeta<M> {
fn default() -> Self {
Self {
vertices: BufferVec::new(BufferUsages::VERTEX),
view_bind_group: Default::default(),
marker: PhantomData,
}
}
}
@ -261,7 +263,7 @@ pub type DrawUiMaterial<M> = (
pub struct SetMatUiViewBindGroup<M: UiMaterial, const I: usize>(PhantomData<M>);
impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P> for SetMatUiViewBindGroup<M, I> {
type Param = SRes<UiMaterialMeta>;
type Param = SRes<UiMaterialMeta<M>>;
type ViewWorldQuery = Read<ViewUniformOffset>;
type ItemWorldQuery = ();
@ -296,10 +298,9 @@ impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P>
materials: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let material = materials
.into_inner()
.get(&material_handle.material)
.unwrap();
let Some(material) = materials.into_inner().get(&material_handle.material) else {
return RenderCommandResult::Failure;
};
pass.set_bind_group(I, &material.bind_group, &[]);
RenderCommandResult::Success
}
@ -307,7 +308,7 @@ impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P>
pub struct DrawUiMaterialNode<M>(PhantomData<M>);
impl<P: PhaseItem, M: UiMaterial> RenderCommand<P> for DrawUiMaterialNode<M> {
type Param = SRes<UiMaterialMeta>;
type Param = SRes<UiMaterialMeta<M>>;
type ViewWorldQuery = ();
type ItemWorldQuery = Read<UiMaterialBatch<M>>;
@ -352,15 +353,18 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
materials: Extract<Res<Assets<M>>>,
ui_stack: Extract<Res<UiStack>>,
uinode_query: Extract<
Query<(
Entity,
&Node,
&Style,
&GlobalTransform,
&Handle<M>,
&ViewVisibility,
Option<&CalculatedClip>,
)>,
Query<
(
Entity,
&Node,
&Style,
&GlobalTransform,
&Handle<M>,
&ViewVisibility,
Option<&CalculatedClip>,
),
Without<BackgroundColor>,
>,
>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
ui_scale: Extract<Res<UiScale>>,
@ -396,7 +400,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
style.border.right,
parent_width,
ui_logical_viewport_size,
) / uinode.size().y;
) / uinode.size().x;
let top =
resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size)
/ uinode.size().y;
@ -429,7 +433,7 @@ pub fn prepare_uimaterial_nodes<M: UiMaterial>(
mut commands: Commands,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut ui_meta: ResMut<UiMaterialMeta>,
mut ui_meta: ResMut<UiMaterialMeta<M>>,
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
view_uniforms: Res<ViewUniforms>,
ui_material_pipeline: Res<UiMaterialPipeline<M>>,
@ -727,7 +731,9 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
let draw_function = draw_functions.read().id::<DrawUiMaterial<M>>();
for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() {
let material = render_materials.get(&extracted_uinode.material).unwrap();
let Some(material) = render_materials.get(&extracted_uinode.material) else {
continue;
};
for (view, mut transparent_phase) in &mut views {
let pipeline = pipelines.specialize(
&pipeline_cache,

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_utils"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "A collection of utils for Bevy Engine"
homepage = "https://bevyengine.org"
@ -17,7 +17,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
instant = { version = "0.1", features = ["wasm-bindgen"] }
uuid = { version = "1.1", features = ["v4", "serde"] }
hashbrown = { version = "0.14", features = ["serde"] }
bevy_utils_proc_macros = { version = "0.12.0", path = "macros" }
bevy_utils_proc_macros = { version = "0.12.1", path = "macros" }
petgraph = "0.6"
thiserror = "1.0"
nonmax = "0.5"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_utils_proc_macros"
version = "0.12.0"
version = "0.12.1"
description = "Bevy Utils Proc Macros"
edition = "2021"
license = "MIT OR Apache-2.0"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_window"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "Provides windowing functionality for Bevy Engine"
homepage = "https://bevyengine.org"
@ -14,16 +14,16 @@ serialize = ["serde"]
[dependencies]
# bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.0", features = [
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_reflect = { path = "../bevy_reflect", version = "0.12.1", features = [
"glam",
] }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
# Used for close_on_esc
bevy_input = { path = "../bevy_input", version = "0.12.0" }
bevy_input = { path = "../bevy_input", version = "0.12.1" }
raw-window-handle = "0.5"
# other

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_winit"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "A winit window and input backend for Bevy Engine"
homepage = "https://bevyengine.org"
@ -16,16 +16,16 @@ accesskit_unix = ["accesskit_winit/accesskit_unix", "accesskit_winit/async-io"]
[dependencies]
# bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.12.0" }
bevy_app = { path = "../bevy_app", version = "0.12.0" }
bevy_derive = { path = "../bevy_derive", version = "0.12.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.0" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.0" }
bevy_input = { path = "../bevy_input", version = "0.12.0" }
bevy_math = { path = "../bevy_math", version = "0.12.0" }
bevy_window = { path = "../bevy_window", version = "0.12.0" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0" }
bevy_a11y = { path = "../bevy_a11y", version = "0.12.1" }
bevy_app = { path = "../bevy_app", version = "0.12.1" }
bevy_derive = { path = "../bevy_derive", version = "0.12.1" }
bevy_ecs = { path = "../bevy_ecs", version = "0.12.1" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.12.1" }
bevy_input = { path = "../bevy_input", version = "0.12.1" }
bevy_math = { path = "../bevy_math", version = "0.12.1" }
bevy_window = { path = "../bevy_window", version = "0.12.1" }
bevy_utils = { path = "../bevy_utils", version = "0.12.1" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.1" }
# other
winit = { version = "0.28.7", default-features = false }

View File

@ -349,6 +349,11 @@ impl Default for WinitAppRunnerState {
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
/// `EventLoop`.
pub fn winit_runner(mut app: App) {
if app.plugins_state() == PluginsState::Ready {
app.finish();
app.cleanup();
}
let mut event_loop = app
.world
.remove_non_send_resource::<EventLoop<()>>()

View File

@ -2,12 +2,11 @@
use bevy::utils::thiserror;
use bevy::{
asset::{io::Reader, AssetLoader, LoadContext},
asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext},
prelude::*,
reflect::TypePath,
utils::BoxedFuture,
};
use futures_lite::AsyncReadExt;
use serde::Deserialize;
use thiserror::Error;
@ -24,7 +23,7 @@ pub struct CustomAssetLoader;
#[derive(Debug, Error)]
pub enum CustomAssetLoaderError {
/// An [IO](std::io) Error
#[error("Could load shader: {0}")]
#[error("Could not load asset: {0}")]
Io(#[from] std::io::Error),
/// A [RON](ron) Error
#[error("Could not parse RON: {0}")]

View File

@ -3,19 +3,16 @@
//! It does not know anything about the asset formats, only how to talk to the underlying storage.
use bevy::{
asset::io::{
file::FileAssetReader, AssetReader, AssetReaderError, AssetSource, AssetSourceId,
PathStream, Reader,
},
asset::io::{AssetReader, AssetReaderError, AssetSource, AssetSourceId, PathStream, Reader},
prelude::*,
utils::BoxedFuture,
};
use std::path::Path;
/// A custom asset reader implementation that wraps a given asset reader implementation
struct CustomAssetReader<T: AssetReader>(T);
struct CustomAssetReader(Box<dyn AssetReader>);
impl<T: AssetReader> AssetReader for CustomAssetReader<T> {
impl AssetReader for CustomAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
@ -52,8 +49,12 @@ impl Plugin for CustomAssetReaderPlugin {
fn build(&self, app: &mut App) {
app.register_asset_source(
AssetSourceId::Default,
AssetSource::build()
.with_reader(|| Box::new(CustomAssetReader(FileAssetReader::new("assets")))),
AssetSource::build().with_reader(|| {
Box::new(CustomAssetReader(
// This is the default reader for the current platform
AssetSource::get_default_reader("assets".to_string())(),
))
}),
);
}
}

View File

@ -1,6 +1,5 @@
//! Demonstrates using a custom extension to the `StandardMaterial` to modify the results of the builtin pbr shader.
use bevy::reflect::TypePath;
use bevy::{
pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod},
prelude::*,
@ -73,7 +72,7 @@ fn rotate_things(mut q: Query<&mut Transform, With<Rotate>>, time: Res<Time>) {
}
}
#[derive(Asset, AsBindGroup, TypePath, Debug, Clone)]
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
struct MyExtension {
// We need to ensure that the bindings of the base material and the extension do not conflict,
// so we start from binding slot 100, leaving slots 0-99 for the base material.

View File

@ -8,6 +8,7 @@ fn main() {
// 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, update_outlines)
.run();
}
@ -86,18 +87,39 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..Default::default()
})
.with_children(|parent| {
parent.spawn(ImageBundle {
image: UiImage::new(image.clone()),
style: Style {
min_width: Val::Px(100.),
min_height: Val::Px(100.),
parent.spawn((
ImageBundle {
image: UiImage::new(image.clone()),
style: Style {
min_width: Val::Px(100.),
min_height: Val::Px(100.),
..Default::default()
},
background_color: Color::WHITE.into(),
..Default::default()
},
background_color: Color::WHITE.into(),
..Default::default()
});
Interaction::default(),
Outline {
width: Val::Px(2.),
offset: Val::Px(2.),
color: Color::NONE,
},
));
});
});
}
});
}
fn update_outlines(mut outlines_query: Query<(&mut Outline, Ref<Interaction>)>) {
for (mut outline, interaction) in outlines_query.iter_mut() {
if interaction.is_changed() {
outline.color = match *interaction {
Interaction::Pressed => Color::RED,
Interaction::Hovered => Color::WHITE,
Interaction::None => Color::NONE,
};
}
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "build-templated-pages"
version = "0.12.0"
version = "0.12.1"
edition = "2021"
description = "handle templated pages in Bevy repository"
publish = false

View File

@ -33,6 +33,10 @@ struct Args {
#[arg(short, long)]
/// Optimize the wasm file for size with wasm-opt
optimize_size: bool,
#[arg(long)]
/// Additional features to enable
features: Vec<String>,
}
fn main() {
@ -41,7 +45,7 @@ fn main() {
assert!(!cli.examples.is_empty(), "must have at least one example");
let mut default_features = true;
let mut features = vec![];
let mut features: Vec<&str> = cli.features.iter().map(|f| f.as_str()).collect();
if let Some(frames) = cli.frames {
let mut file = File::create("ci_testing_config.ron").unwrap();
file.write_fmt(format_args!("(exit_after: Some({frames}))"))

View File

@ -1,6 +1,6 @@
[package]
name = "example-showcase"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
description = "Run examples"
publish = false

View File

@ -0,0 +1,13 @@
diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs
index 004f87a85..3c8656efc 100644
--- a/crates/bevy_asset/src/lib.rs
+++ b/crates/bevy_asset/src/lib.rs
@@ -105,7 +105,7 @@ impl Default for AssetPlugin {
fn default() -> Self {
Self {
mode: AssetMode::Unprocessed,
- file_path: Self::DEFAULT_UNPROCESSED_FILE_PATH.to_string(),
+ file_path: "/assets/examples".to_string(),
processed_file_path: Self::DEFAULT_PROCESSED_FILE_PATH.to_string(),
watch_for_changes_override: None,
}

View File

@ -562,11 +562,20 @@ header_message = \"Examples ({})\"
let sh = Shell::new().unwrap();
// setting a canvas by default to help with integration
cmd!(sh, "sed -i.bak 's/canvas: None,/canvas: Some(\"#bevy\".to_string()),/' crates/bevy_window/src/window.rs").run().unwrap();
cmd!(sh, "sed -i.bak 's/fit_canvas_to_parent: false,/fit_canvas_to_parent: true,/' crates/bevy_window/src/window.rs").run().unwrap();
cmd!(
sh,
"git apply --ignore-whitespace tools/example-showcase/window-settings-wasm.patch"
)
.run()
.unwrap();
// setting the asset folder root to the root url of this domain
cmd!(sh, "sed -i.bak 's/asset_folder: \"assets\"/asset_folder: \"\\/assets\\/examples\\/\"/' crates/bevy_asset/src/lib.rs").run().unwrap();
cmd!(
sh,
"git apply --ignore-whitespace tools/example-showcase/asset-source-website.patch"
)
.run()
.unwrap();
}
let work_to_do = || {
@ -581,17 +590,26 @@ header_message = \"Examples ({})\"
for to_build in work_to_do() {
let sh = Shell::new().unwrap();
let example = &to_build.technical_name;
let required_features = if to_build.required_features.is_empty() {
vec![]
} else {
vec![
"--features".to_string(),
to_build.required_features.join(","),
]
};
if optimize_size {
cmd!(
sh,
"cargo run -p build-wasm-example -- --api {api} {example} --optimize-size"
"cargo run -p build-wasm-example -- --api {api} {example} --optimize-size {required_features...}"
)
.run()
.unwrap();
} else {
cmd!(
sh,
"cargo run -p build-wasm-example -- --api {api} {example}"
"cargo run -p build-wasm-example -- --api {api} {example} {required_features...}"
)
.run()
.unwrap();

View File

@ -0,0 +1,16 @@
diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs
index 7b5c75d38..8e9404b93 100644
--- a/crates/bevy_window/src/window.rs
+++ b/crates/bevy_window/src/window.rs
@@ -245,9 +245,9 @@ impl Default for Window {
transparent: false,
focused: true,
window_level: Default::default(),
- fit_canvas_to_parent: false,
+ fit_canvas_to_parent: true,
prevent_default_event_handling: true,
- canvas: None,
+ canvas: Some("#bevy".to_string()),
window_theme: None,
visible: true,
}