diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d1d0909cac009..fc4f2beeea01e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -145,6 +145,31 @@ jobs:
- name: Check wasm
run: cargo check --target wasm32-unknown-unknown
+ build-wasm-atomics:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ needs: build
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ubuntu-assets-cargo-build-wasm-nightly-${{ hashFiles('**/Cargo.toml') }}
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}
+ targets: wasm32-unknown-unknown
+ components: rust-src
+ - name: Check wasm
+ run: cargo check --target wasm32-unknown-unknown -Z build-std=std,panic_abort
+ env:
+ RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory"
+
markdownlint:
runs-on: ubuntu-latest
timeout-minutes: 30
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index ee3c6d848c5af..5f163f757d5e9 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -49,6 +49,9 @@ jobs:
echo "" > header.html
- name: Build docs
+ env:
+ # needs to be in sync with [package.metadata.docs.rs]
+ RUSTDOCFLAGS: -Zunstable-options --cfg=docsrs
run: cargo doc --all-features --no-deps -p bevy -Zunstable-options -Zrustdoc-scrape-examples
# This adds the following:
diff --git a/.github/workflows/validation-jobs.yml b/.github/workflows/validation-jobs.yml
index 7e7d2029ac440..932ff4041f4fb 100644
--- a/.github/workflows/validation-jobs.yml
+++ b/.github/workflows/validation-jobs.yml
@@ -286,3 +286,31 @@ jobs:
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
- name: Run cargo udeps
run: cargo udeps
+
+ check-example-showcase-patches-still-work:
+ if: ${{ github.event_name == 'merge_group' }}
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-check-showcase-patches-${{ hashFiles('**/Cargo.toml') }}
+ - uses: dtolnay/rust-toolchain@stable
+ - name: Installs cargo-udeps
+ run: cargo install --force cargo-udeps
+ - name: Install alsa and udev
+ run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
+ - name: Apply patches
+ run: |
+ for patch in tools/example-showcase/*.patch; do
+ git apply --ignore-whitespace $patch
+ done
+ - name: Build with patches
+ run: cargo build
diff --git a/CREDITS.md b/CREDITS.md
index 8da7203793f52..cc8b15083ac69 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -28,6 +28,8 @@
* FiraMono by The Mozilla Foundation and Telefonica S.A (SIL Open Font License, Version 1.1: assets/fonts/FiraMono-LICENSE)
* Barycentric from [mk_bary_gltf](https://github.com/komadori/mk_bary_gltf) (MIT OR Apache-2.0)
* `MorphStressTest.gltf`, [MorphStressTest] ([CC-BY 4.0] by Analytical Graphics, Inc, Model and textures by Ed Mackey)
+* Mysterious acoustic guitar music sample from [florianreichelt](https://freesound.org/people/florianreichelt/sounds/412429/) (CC0 license)
+* Epic orchestra music sample, modified to loop, from [Migfus20](https://freesound.org/people/Migfus20/sounds/560449/) ([CC BY 4.0 DEED](https://creativecommons.org/licenses/by/4.0/))
[MorphStressTest]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/MorphStressTest
[fox]: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Fox
diff --git a/Cargo.toml b/Cargo.toml
index 9c43da5a15bd1..019ce0c6e9059 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/bevyengine/bevy"
documentation = "https://docs.rs/bevy"
-rust-version = "1.76.0"
+rust-version = "1.77.0"
[workspace]
exclude = [
@@ -47,6 +47,7 @@ ptr_cast_constness = "warn"
[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "warn"
missing_docs = "warn"
+unsafe_code = "deny"
[lints]
workspace = true
@@ -321,6 +322,12 @@ embedded_watcher = ["bevy_internal/embedded_watcher"]
# Enable stepping-based debugging of Bevy systems
bevy_debug_stepping = ["bevy_internal/bevy_debug_stepping"]
+# Enables the meshlet renderer for dense high-poly scenes (experimental)
+meshlet = ["bevy_internal/meshlet"]
+
+# Enables processing meshes into meshlet meshes for bevy_pbr
+meshlet_processor = ["bevy_internal/meshlet_processor"]
+
# Enable support for the ios_simulator by downgrading some rendering capabilities
ios_simulator = ["bevy_internal/ios_simulator"]
@@ -330,6 +337,7 @@ bevy_internal = { path = "crates/bevy_internal", version = "0.14.0-dev", default
[dev-dependencies]
rand = "0.8.0"
+rand_chacha = "0.3.1"
ron = "0.8.0"
flate2 = "1.0"
serde = { version = "1", features = ["derive"] }
@@ -554,6 +562,17 @@ description = "Showcases bounding volumes and intersection tests"
category = "2D Rendering"
wasm = true
+[[example]]
+name = "wireframe_2d"
+path = "examples/2d/wireframe_2d.rs"
+doc-scrape-examples = true
+
+[package.metadata.example.wireframe_2d]
+name = "2D Wireframe"
+description = "Showcases wireframes for 2d meshes"
+category = "2D Rendering"
+wasm = false
+
# 3D Rendering
[[example]]
name = "3d_scene"
@@ -949,6 +968,18 @@ description = "Demonstrates irradiance volumes"
category = "3D Rendering"
wasm = false
+[[example]]
+name = "meshlet"
+path = "examples/3d/meshlet.rs"
+doc-scrape-examples = true
+required-features = ["meshlet"]
+
+[package.metadata.example.meshlet]
+name = "Meshlet"
+description = "Meshlet rendering for dense high-poly scenes (experimental)"
+category = "3D Rendering"
+wasm = false
+
[[example]]
name = "lightmaps"
path = "examples/3d/lightmaps.rs"
@@ -1013,6 +1044,17 @@ description = "Create and play an animation defined by code that operates on the
category = "Animation"
wasm = true
+[[example]]
+name = "color_animation"
+path = "examples/animation/color_animation.rs"
+doc-scrape-examples = true
+
+[package.metadata.example.color_animation]
+name = "Color animation"
+description = "Demonstrates how to animate colors using mixing and splines in different color spaces"
+category = "Animation"
+wasm = true
+
[[example]]
name = "cubic_curve"
path = "examples/animation/cubic_curve.rs"
@@ -1347,6 +1389,17 @@ description = "Shows how to create and register a custom audio source by impleme
category = "Audio"
wasm = true
+[[example]]
+name = "soundtrack"
+path = "examples/audio/soundtrack.rs"
+doc-scrape-examples = true
+
+[package.metadata.example.soundtrack]
+name = "Soundtrack"
+description = "Shows how to play different soundtracks based on game state"
+category = "Audio"
+wasm = true
+
[[example]]
name = "spatial_audio_2d"
path = "examples/audio/spatial_audio_2d.rs"
@@ -2340,6 +2393,17 @@ description = "Demonstrates how to create a node with a border"
category = "UI (User Interface)"
wasm = true
+[[example]]
+name = "rounded_borders"
+path = "examples/ui/rounded_borders.rs"
+doc-scrape-examples = true
+
+[package.metadata.example.rounded_borders]
+name = "Rounded Borders"
+description = "Demonstrates how to create a node with a rounded border"
+category = "UI (User Interface)"
+wasm = true
+
[[example]]
name = "button"
path = "examples/ui/button.rs"
@@ -2798,5 +2862,6 @@ lto = "fat"
panic = "abort"
[package.metadata.docs.rs]
-cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
+rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
all-features = true
+cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
diff --git a/assets/models/bunny.meshlet_mesh b/assets/models/bunny.meshlet_mesh
new file mode 100644
index 0000000000000..735d002601293
Binary files /dev/null and b/assets/models/bunny.meshlet_mesh differ
diff --git a/assets/sounds/Epic orchestra music.ogg b/assets/sounds/Epic orchestra music.ogg
new file mode 100644
index 0000000000000..c88b99a23762b
Binary files /dev/null and b/assets/sounds/Epic orchestra music.ogg differ
diff --git a/assets/sounds/Mysterious acoustic guitar.ogg b/assets/sounds/Mysterious acoustic guitar.ogg
new file mode 100644
index 0000000000000..5dae3e7080fb7
Binary files /dev/null and b/assets/sounds/Mysterious acoustic guitar.ogg differ
diff --git a/benches/Cargo.toml b/benches/Cargo.toml
index e64a79d2b389a..ee9d45953a6a3 100644
--- a/benches/Cargo.toml
+++ b/benches/Cargo.toml
@@ -17,6 +17,7 @@ bevy_reflect = { path = "../crates/bevy_reflect" }
bevy_tasks = { path = "../crates/bevy_tasks" }
bevy_utils = { path = "../crates/bevy_utils" }
bevy_math = { path = "../crates/bevy_math" }
+bevy_render = { path = "../crates/bevy_render" }
[profile.release]
opt-level = 3
@@ -62,6 +63,11 @@ name = "bezier"
path = "benches/bevy_math/bezier.rs"
harness = false
+[[bench]]
+name = "torus"
+path = "benches/bevy_render/torus.rs"
+harness = false
+
[[bench]]
name = "entity_hash"
path = "benches/bevy_ecs/world/entity_hash.rs"
diff --git a/benches/benches/bevy_render/torus.rs b/benches/benches/bevy_render/torus.rs
new file mode 100644
index 0000000000000..8ec81c80409d8
--- /dev/null
+++ b/benches/benches/bevy_render/torus.rs
@@ -0,0 +1,15 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+
+use bevy_render::mesh::TorusMeshBuilder;
+
+fn torus(c: &mut Criterion) {
+ c.bench_function("build_torus", |b| {
+ b.iter(|| black_box(TorusMeshBuilder::new(black_box(0.5),black_box(1.0))));
+ });
+}
+
+criterion_group!(
+ benches,
+ torus,
+);
+criterion_main!(benches);
diff --git a/crates/bevy_a11y/Cargo.toml b/crates/bevy_a11y/Cargo.toml
index 1f13d6e8a52d8..4ee262fa22974 100644
--- a/crates/bevy_a11y/Cargo.toml
+++ b/crates/bevy_a11y/Cargo.toml
@@ -18,3 +18,7 @@ accesskit = "0.12"
[lints]
workspace = true
+
+[package.metadata.docs.rs]
+rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
+all-features = true
diff --git a/crates/bevy_a11y/src/lib.rs b/crates/bevy_a11y/src/lib.rs
index 53a515c7eeb6d..aab4c23f2c860 100644
--- a/crates/bevy_a11y/src/lib.rs
+++ b/crates/bevy_a11y/src/lib.rs
@@ -1,6 +1,11 @@
-//! Accessibility for Bevy
-
#![forbid(unsafe_code)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![doc(
+ html_logo_url = "https://bevyengine.org/assets/icon.png",
+ html_favicon_url = "https://bevyengine.org/assets/icon.png"
+)]
+
+//! Accessibility for Bevy
use std::sync::{
atomic::{AtomicBool, Ordering},
diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml
index c6cc5275f5255..52c2786b849c7 100644
--- a/crates/bevy_animation/Cargo.toml
+++ b/crates/bevy_animation/Cargo.toml
@@ -12,6 +12,7 @@ keywords = ["bevy"]
# bevy
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
+bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.14.0-dev" }
@@ -28,7 +29,7 @@ bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" }
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" }
# other
-fixedbitset = "0.4"
+fixedbitset = "0.5"
petgraph = { version = "0.6", features = ["serde-1"] }
ron = "0.8"
serde = "1"
@@ -39,3 +40,7 @@ uuid = { version = "1.7", features = ["v4"] }
[lints]
workspace = true
+
+[package.metadata.docs.rs]
+rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
+all-features = true
diff --git a/crates/bevy_animation/src/animatable.rs b/crates/bevy_animation/src/animatable.rs
index 17e11bfe5fd2c..666485a305d30 100644
--- a/crates/bevy_animation/src/animatable.rs
+++ b/crates/bevy_animation/src/animatable.rs
@@ -1,9 +1,9 @@
use crate::util;
+use bevy_color::{ClampColor, Laba, LinearRgba, Oklaba, Srgba, Xyza};
use bevy_ecs::world::World;
use bevy_math::*;
use bevy_reflect::Reflect;
use bevy_transform::prelude::Transform;
-use bevy_utils::FloatOrd;
/// An individual input for [`Animatable::blend`].
pub struct BlendInput {
@@ -57,6 +57,31 @@ macro_rules! impl_float_animatable {
};
}
+macro_rules! impl_color_animatable {
+ ($ty: ident) => {
+ impl Animatable for $ty {
+ #[inline]
+ fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
+ let value = *a * (1. - t) + *b * t;
+ value.clamped()
+ }
+
+ #[inline]
+ fn blend(inputs: impl Iterator- >) -> Self {
+ let mut value = Default::default();
+ for input in inputs {
+ if input.additive {
+ value += input.weight * input.value;
+ } else {
+ value = Self::interpolate(&value, &input.value, input.weight);
+ }
+ }
+ value.clamped()
+ }
+ }
+ };
+}
+
impl_float_animatable!(f32, f32);
impl_float_animatable!(Vec2, f32);
impl_float_animatable!(Vec3A, f32);
@@ -67,6 +92,12 @@ impl_float_animatable!(DVec2, f64);
impl_float_animatable!(DVec3, f64);
impl_float_animatable!(DVec4, f64);
+impl_color_animatable!(LinearRgba);
+impl_color_animatable!(Laba);
+impl_color_animatable!(Oklaba);
+impl_color_animatable!(Srgba);
+impl_color_animatable!(Xyza);
+
// Vec3 is special cased to use Vec3A internally for blending
impl Animatable for Vec3 {
#[inline]
diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs
index aba53f1d7adfa..01a270ed5385d 100644
--- a/crates/bevy_animation/src/graph.rs
+++ b/crates/bevy_animation/src/graph.rs
@@ -6,7 +6,6 @@ use std::ops::{Index, IndexMut};
use bevy_asset::io::Reader;
use bevy_asset::{Asset, AssetId, AssetLoader, AssetPath, AsyncReadExt as _, Handle, LoadContext};
use bevy_reflect::{Reflect, ReflectSerialize};
-use bevy_utils::BoxedFuture;
use petgraph::graph::{DiGraph, NodeIndex};
use ron::de::SpannedError;
use serde::{Deserialize, Serialize};
@@ -336,40 +335,37 @@ impl AssetLoader for AnimationGraphAssetLoader {
type Error = AnimationGraphLoadError;
- fn load<'a>(
+ async fn load<'a>(
&'a self,
- reader: &'a mut Reader,
+ reader: &'a mut Reader<'_>,
_: &'a Self::Settings,
- load_context: &'a mut LoadContext,
- ) -> BoxedFuture<'a, Result> {
- Box::pin(async move {
- let mut bytes = Vec::new();
- reader.read_to_end(&mut bytes).await?;
-
- // Deserialize a `SerializedAnimationGraph` directly, so that we can
- // get the list of the animation clips it refers to and load them.
- let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
- let serialized_animation_graph =
- SerializedAnimationGraph::deserialize(&mut deserializer)
- .map_err(|err| deserializer.span_error(err))?;
-
- // Load all `AssetPath`s to convert from a
- // `SerializedAnimationGraph` to a real `AnimationGraph`.
- Ok(AnimationGraph {
- graph: serialized_animation_graph.graph.map(
- |_, serialized_node| AnimationGraphNode {
- clip: serialized_node.clip.as_ref().map(|clip| match clip {
- SerializedAnimationClip::AssetId(asset_id) => Handle::Weak(*asset_id),
- SerializedAnimationClip::AssetPath(asset_path) => {
- load_context.load(asset_path)
- }
- }),
- weight: serialized_node.weight,
- },
- |_, _| (),
- ),
- root: serialized_animation_graph.root,
- })
+ load_context: &'a mut LoadContext<'_>,
+ ) -> Result {
+ let mut bytes = Vec::new();
+ reader.read_to_end(&mut bytes).await?;
+
+ // Deserialize a `SerializedAnimationGraph` directly, so that we can
+ // get the list of the animation clips it refers to and load them.
+ let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
+ let serialized_animation_graph = SerializedAnimationGraph::deserialize(&mut deserializer)
+ .map_err(|err| deserializer.span_error(err))?;
+
+ // Load all `AssetPath`s to convert from a
+ // `SerializedAnimationGraph` to a real `AnimationGraph`.
+ Ok(AnimationGraph {
+ graph: serialized_animation_graph.graph.map(
+ |_, serialized_node| AnimationGraphNode {
+ clip: serialized_node.clip.as_ref().map(|clip| match clip {
+ SerializedAnimationClip::AssetId(asset_id) => Handle::Weak(*asset_id),
+ SerializedAnimationClip::AssetPath(asset_path) => {
+ load_context.load(asset_path)
+ }
+ }),
+ weight: serialized_node.weight,
+ },
+ |_, _| (),
+ ),
+ root: serialized_animation_graph.root,
})
}
diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs
index d3dbed8406193..c4f8bc9248e6b 100644
--- a/crates/bevy_animation/src/lib.rs
+++ b/crates/bevy_animation/src/lib.rs
@@ -1,3 +1,10 @@
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![forbid(unsafe_code)]
+#![doc(
+ html_logo_url = "https://bevyengine.org/assets/icon.png",
+ html_favicon_url = "https://bevyengine.org/assets/icon.png"
+)]
+
//! Animation for the game engine Bevy
mod animatable;
diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml
index 28b272571edd3..15f511ceb30a2 100644
--- a/crates/bevy_app/Cargo.toml
+++ b/crates/bevy_app/Cargo.toml
@@ -31,9 +31,11 @@ thiserror = "1.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2" }
web-sys = { version = "0.3", features = ["Window"] }
+console_error_panic_hook = "0.1.6"
[lints]
workspace = true
[package.metadata.docs.rs]
+rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
all-features = true
diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs
index 10f567f2f211c..7b57f321e07e1 100644
--- a/crates/bevy_app/src/app.rs
+++ b/crates/bevy_app/src/app.rs
@@ -6,6 +6,7 @@ use bevy_ecs::{
common_conditions::run_once as run_once_condition, run_enter_schedule,
InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel,
},
+ system::SystemId,
};
use bevy_utils::{intern::Interned, tracing::debug, HashMap, HashSet};
use std::{
@@ -453,6 +454,22 @@ impl App {
self
}
+ /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
+ ///
+ /// It's possible to register the same systems more than once, they'll be stored separately.
+ ///
+ /// This is different from adding systems to a [`Schedule`] with [`App::add_systems`],
+ /// because the [`SystemId`] that is returned can be used anywhere in the [`World`] to run the associated system.
+ /// This allows for running systems in a push-based fashion.
+ /// Using a [`Schedule`] is still preferred for most cases
+ /// due to its better performance and ability to run non-conflicting systems simultaneously.
+ pub fn register_system + 'static>(
+ &mut self,
+ system: S,
+ ) -> SystemId {
+ self.world.register_system(system)
+ }
+
/// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
#[track_caller]
pub fn configure_sets(
diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs
index d87bcbde130a6..232c3e801e73a 100644
--- a/crates/bevy_app/src/lib.rs
+++ b/crates/bevy_app/src/lib.rs
@@ -1,8 +1,15 @@
-//! This crate is about everything concerning the highest-level, application layer of a Bevy app.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![forbid(unsafe_code)]
+#![doc(
+ html_logo_url = "https://bevyengine.org/assets/icon.png",
+ html_favicon_url = "https://bevyengine.org/assets/icon.png"
+)]
+
+//! This crate is about everything concerning the highest-level, application layer of a Bevy app.
mod app;
mod main_schedule;
+mod panic_handler;
mod plugin;
mod plugin_group;
mod schedule_runner;
@@ -10,6 +17,7 @@ mod schedule_runner;
pub use app::*;
pub use bevy_derive::DynamicPlugin;
pub use main_schedule::*;
+pub use panic_handler::*;
pub use plugin::*;
pub use plugin_group::*;
pub use schedule_runner::*;
diff --git a/crates/bevy_app/src/panic_handler.rs b/crates/bevy_app/src/panic_handler.rs
new file mode 100644
index 0000000000000..d66062e104f41
--- /dev/null
+++ b/crates/bevy_app/src/panic_handler.rs
@@ -0,0 +1,52 @@
+//! This module provides panic handlers for [Bevy](https://bevyengine.org)
+//! apps, and automatically configures platform specifics (i.e. WASM or Android).
+//!
+//! By default, the [`PanicHandlerPlugin`] from this crate is included in Bevy's `DefaultPlugins`.
+//!
+//! For more fine-tuned control over panic behavior, disable the [`PanicHandlerPlugin`] or
+//! `DefaultPlugins` during app initialization.
+
+use crate::App;
+use crate::Plugin;
+
+/// Adds sensible panic handlers to Apps. This plugin is part of the `DefaultPlugins`. Adding
+/// this plugin will setup a panic hook appropriate to your target platform:
+/// * On WASM, uses [`console_error_panic_hook`](https://crates.io/crates/console_error_panic_hook), logging
+/// to the browser console.
+/// * Other platforms are currently not setup.
+///
+/// ```no_run
+/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, PluginGroup, PanicHandlerPlugin};
+/// fn main() {
+/// App::new()
+/// .add_plugins(MinimalPlugins)
+/// .add_plugins(PanicHandlerPlugin)
+/// .run();
+/// }
+/// ```
+///
+/// If you want to setup your own panic handler, you should disable this
+/// plugin from `DefaultPlugins`:
+/// ```no_run
+/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, PanicHandlerPlugin};
+/// fn main() {
+/// App::new()
+/// .add_plugins(DefaultPlugins.build().disable::())
+/// .run();
+/// }
+/// ```
+#[derive(Default)]
+pub struct PanicHandlerPlugin;
+
+impl Plugin for PanicHandlerPlugin {
+ fn build(&self, _app: &mut App) {
+ #[cfg(target_arch = "wasm32")]
+ {
+ console_error_panic_hook::set_once();
+ }
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ // Use the default target panic hook - Do nothing.
+ }
+ }
+}
diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml
index 62f499857d925..04398df8ec254 100644
--- a/crates/bevy_asset/Cargo.toml
+++ b/crates/bevy_asset/Cargo.toml
@@ -61,4 +61,5 @@ bevy_log = { path = "../bevy_log", version = "0.14.0-dev" }
workspace = true
[package.metadata.docs.rs]
+rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
all-features = true
diff --git a/crates/bevy_asset/macros/Cargo.toml b/crates/bevy_asset/macros/Cargo.toml
index 21d6b1fdcda1a..f178067cc19f3 100644
--- a/crates/bevy_asset/macros/Cargo.toml
+++ b/crates/bevy_asset/macros/Cargo.toml
@@ -20,3 +20,7 @@ quote = "1.0"
[lints]
workspace = true
+
+[package.metadata.docs.rs]
+rustdoc-args = ["-Zunstable-options", "--cfg", "docsrs"]
+all-features = true
diff --git a/crates/bevy_asset/macros/src/lib.rs b/crates/bevy_asset/macros/src/lib.rs
index 8dc8975f2352b..6c290367e64f2 100644
--- a/crates/bevy_asset/macros/src/lib.rs
+++ b/crates/bevy_asset/macros/src/lib.rs
@@ -1,5 +1,6 @@
// FIXME(3492): remove once docs are ready
#![allow(missing_docs)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use bevy_macro_utils::BevyManifest;
use proc_macro::{Span, TokenStream};
diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs
index 9cf1b4e5386b7..b04d8552f50e9 100644
--- a/crates/bevy_asset/src/assets.rs
+++ b/crates/bevy_asset/src/assets.rs
@@ -549,23 +549,24 @@ impl Assets {
while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
let id = drop_event.id.typed();
- assets.queued_events.push(AssetEvent::Unused { id });
-
- let mut remove_asset = true;
-
if drop_event.asset_server_managed {
- let untyped_id = drop_event.id.untyped(TypeId::of::());
+ let untyped_id = id.untyped();
if let Some(info) = infos.get(untyped_id) {
if let LoadState::Loading | LoadState::NotLoaded = info.load_state {
not_ready.push(drop_event);
continue;
}
}
- remove_asset = infos.process_handle_drop(untyped_id);
- }
- if remove_asset {
- assets.remove_dropped(id);
+
+ // the process_handle_drop call checks whether new handles have been created since the drop event was fired, before removing the asset
+ if !infos.process_handle_drop(untyped_id) {
+ // a new handle has been created, or the asset doesn't exist
+ continue;
+ }
}
+
+ assets.queued_events.push(AssetEvent::Unused { id });
+ assets.remove_dropped(id);
}
// TODO: this is _extremely_ inefficient find a better fix
diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs
index d14c325973c77..b2ebea2af9024 100644
--- a/crates/bevy_asset/src/handle.rs
+++ b/crates/bevy_asset/src/handle.rs
@@ -249,30 +249,30 @@ impl PartialEq for Handle {
impl Eq for Handle {}
-impl From> for AssetId {
+impl From<&Handle> for AssetId {
#[inline]
- fn from(value: Handle) -> Self {
+ fn from(value: &Handle) -> Self {
value.id()
}
}
-impl From<&Handle> for AssetId {
+impl From<&Handle> for UntypedAssetId {
#[inline]
fn from(value: &Handle) -> Self {
- value.id()
+ value.id().into()
}
}
-impl From> for UntypedAssetId {
+impl From<&mut Handle> for AssetId {
#[inline]
- fn from(value: Handle) -> Self {
- value.id().into()
+ fn from(value: &mut Handle) -> Self {
+ value.id()
}
}
-impl From<&Handle> for UntypedAssetId {
+impl From<&mut Handle> for UntypedAssetId {
#[inline]
- fn from(value: &Handle) -> Self {
+ fn from(value: &mut Handle) -> Self {
value.id().into()
}
}
@@ -429,13 +429,6 @@ impl PartialOrd for UntypedHandle {
}
}
-impl From for UntypedAssetId {
- #[inline]
- fn from(value: UntypedHandle) -> Self {
- value.id()
- }
-}
-
impl From<&UntypedHandle> for UntypedAssetId {
#[inline]
fn from(value: &UntypedHandle) -> Self {
diff --git a/crates/bevy_asset/src/io/android.rs b/crates/bevy_asset/src/io/android.rs
index 38791b35e0071..18daac5949645 100644
--- a/crates/bevy_asset/src/io/android.rs
+++ b/crates/bevy_asset/src/io/android.rs
@@ -2,7 +2,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader,
};
use bevy_utils::tracing::error;
-use bevy_utils::BoxedFuture;
use std::{ffi::CString, path::Path};
/// [`AssetReader`] implementation for Android devices, built on top of Android's [`AssetManager`].
@@ -17,57 +16,47 @@ use std::{ffi::CString, path::Path};
pub struct AndroidAssetReader;
impl AssetReader for AndroidAssetReader {
- fn read<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
- Box::pin(async move {
- let asset_manager = bevy_winit::ANDROID_APP
- .get()
- .expect("Bevy must be setup with the #[bevy_main] macro on Android")
- .asset_manager();
- let mut opened_asset = asset_manager
- .open(&CString::new(path.to_str().unwrap()).unwrap())
- .ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
- let bytes = opened_asset.buffer()?;
- let reader: Box = Box::new(VecReader::new(bytes.to_vec()));
- Ok(reader)
- })
+ async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
+ let asset_manager = bevy_winit::ANDROID_APP
+ .get()
+ .expect("Bevy must be setup with the #[bevy_main] macro on Android")
+ .asset_manager();
+ let mut opened_asset = asset_manager
+ .open(&CString::new(path.to_str().unwrap()).unwrap())
+ .ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
+ let bytes = opened_asset.buffer()?;
+ let reader: Box = Box::new(VecReader::new(bytes.to_vec()));
+ Ok(reader)
}
- fn read_meta<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
- Box::pin(async move {
- let meta_path = get_meta_path(path);
- let asset_manager = bevy_winit::ANDROID_APP
- .get()
- .expect("Bevy must be setup with the #[bevy_main] macro on Android")
- .asset_manager();
- let mut opened_asset = asset_manager
- .open(&CString::new(meta_path.to_str().unwrap()).unwrap())
- .ok_or(AssetReaderError::NotFound(meta_path))?;
- let bytes = opened_asset.buffer()?;
- let reader: Box = Box::new(VecReader::new(bytes.to_vec()));
- Ok(reader)
- })
+ async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
+ let meta_path = get_meta_path(path);
+ let asset_manager = bevy_winit::ANDROID_APP
+ .get()
+ .expect("Bevy must be setup with the #[bevy_main] macro on Android")
+ .asset_manager();
+ let mut opened_asset = asset_manager
+ .open(&CString::new(meta_path.to_str().unwrap()).unwrap())
+ .ok_or(AssetReaderError::NotFound(meta_path))?;
+ let bytes = opened_asset.buffer()?;
+ let reader: Box = Box::new(VecReader::new(bytes.to_vec()));
+ Ok(reader)
}
- fn read_directory<'a>(
+ async fn read_directory<'a>(
&'a self,
_path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetReaderError>> {
+ ) -> Result, AssetReaderError> {
let stream: Box = Box::new(EmptyPathStream);
error!("Reading directories is not supported with the AndroidAssetReader");
- Box::pin(async move { Ok(stream) })
+ Ok(stream)
}
- fn is_directory<'a>(
+ async fn is_directory<'a>(
&'a self,
_path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result> {
+ ) -> std::result::Result {
error!("Reading directories is not supported with the AndroidAssetReader");
- Box::pin(async move { Ok(false) })
+ Ok(false)
}
}
diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs
index b953e1e669f08..d8f85c315cbe6 100644
--- a/crates/bevy_asset/src/io/embedded/mod.rs
+++ b/crates/bevy_asset/src/io/embedded/mod.rs
@@ -254,7 +254,7 @@ pub fn watched_path(_source_file_path: &'static str, _asset_path: &'static str)
macro_rules! load_internal_asset {
($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
let mut assets = $app.world.resource_mut::<$crate::Assets<_>>();
- assets.insert($handle, ($loader)(
+ assets.insert($handle.id(), ($loader)(
include_str!($path_str),
std::path::Path::new(file!())
.parent()
@@ -266,7 +266,7 @@ macro_rules! load_internal_asset {
// we can't support params without variadic arguments, so internal assets with additional params can't be hot-reloaded
($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)+) => {{
let mut assets = $app.world.resource_mut::<$crate::Assets<_>>();
- assets.insert($handle, ($loader)(
+ assets.insert($handle.id(), ($loader)(
include_str!($path_str),
std::path::Path::new(file!())
.parent()
@@ -284,7 +284,7 @@ macro_rules! load_internal_binary_asset {
($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
let mut assets = $app.world.resource_mut::<$crate::Assets<_>>();
assets.insert(
- $handle,
+ $handle.id(),
($loader)(
include_bytes!($path_str).as_ref(),
std::path::Path::new(file!())
diff --git a/crates/bevy_asset/src/io/file/file_asset.rs b/crates/bevy_asset/src/io/file/file_asset.rs
index aa20913140111..5826fe097ddb3 100644
--- a/crates/bevy_asset/src/io/file/file_asset.rs
+++ b/crates/bevy_asset/src/io/file/file_asset.rs
@@ -3,7 +3,6 @@ use crate::io::{
Reader, Writer,
};
use async_fs::{read_dir, File};
-use bevy_utils::BoxedFuture;
use futures_lite::StreamExt;
use std::path::Path;
@@ -11,215 +10,168 @@ use std::path::Path;
use super::{FileAssetReader, FileAssetWriter};
impl AssetReader for FileAssetReader {
- fn read<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- match File::open(&full_path).await {
- Ok(file) => {
- let reader: Box = Box::new(file);
- Ok(reader)
- }
- Err(e) => {
- if e.kind() == std::io::ErrorKind::NotFound {
- Err(AssetReaderError::NotFound(full_path))
- } else {
- Err(e.into())
- }
+ async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
+ let full_path = self.root_path.join(path);
+ match File::open(&full_path).await {
+ Ok(file) => {
+ let reader: Box = Box::new(file);
+ Ok(reader)
+ }
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::NotFound {
+ Err(AssetReaderError::NotFound(full_path))
+ } else {
+ Err(e.into())
}
}
- })
+ }
}
- fn read_meta<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
+ async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
let meta_path = get_meta_path(path);
- Box::pin(async move {
- let full_path = self.root_path.join(meta_path);
- match File::open(&full_path).await {
- Ok(file) => {
- let reader: Box = Box::new(file);
- Ok(reader)
- }
- Err(e) => {
- if e.kind() == std::io::ErrorKind::NotFound {
- Err(AssetReaderError::NotFound(full_path))
- } else {
- Err(e.into())
- }
+ let full_path = self.root_path.join(meta_path);
+ match File::open(&full_path).await {
+ Ok(file) => {
+ let reader: Box = Box::new(file);
+ Ok(reader)
+ }
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::NotFound {
+ Err(AssetReaderError::NotFound(full_path))
+ } else {
+ Err(e.into())
}
}
- })
+ }
}
- fn read_directory<'a>(
+ async fn read_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetReaderError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- match read_dir(&full_path).await {
- Ok(read_dir) => {
- let root_path = self.root_path.clone();
- let mapped_stream = read_dir.filter_map(move |f| {
- f.ok().and_then(|dir_entry| {
- let path = dir_entry.path();
- // filter out meta files as they are not considered assets
- if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
- if ext.eq_ignore_ascii_case("meta") {
- return None;
- }
+ ) -> Result, AssetReaderError> {
+ let full_path = self.root_path.join(path);
+ match read_dir(&full_path).await {
+ Ok(read_dir) => {
+ let root_path = self.root_path.clone();
+ let mapped_stream = read_dir.filter_map(move |f| {
+ f.ok().and_then(|dir_entry| {
+ let path = dir_entry.path();
+ // filter out meta files as they are not considered assets
+ if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
+ if ext.eq_ignore_ascii_case("meta") {
+ return None;
}
- let relative_path = path.strip_prefix(&root_path).unwrap();
- Some(relative_path.to_owned())
- })
- });
- let read_dir: Box = Box::new(mapped_stream);
- Ok(read_dir)
- }
- Err(e) => {
- if e.kind() == std::io::ErrorKind::NotFound {
- Err(AssetReaderError::NotFound(full_path))
- } else {
- Err(e.into())
- }
+ }
+ let relative_path = path.strip_prefix(&root_path).unwrap();
+ Some(relative_path.to_owned())
+ })
+ });
+ let read_dir: Box = Box::new(mapped_stream);
+ Ok(read_dir)
+ }
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::NotFound {
+ Err(AssetReaderError::NotFound(full_path))
+ } else {
+ Err(e.into())
}
}
- })
+ }
}
- fn is_directory<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- let metadata = full_path
- .metadata()
- .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
- Ok(metadata.file_type().is_dir())
- })
+ async fn is_directory<'a>(&'a self, path: &'a Path) -> Result {
+ let full_path = self.root_path.join(path);
+ let metadata = full_path
+ .metadata()
+ .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
+ Ok(metadata.file_type().is_dir())
}
}
impl AssetWriter for FileAssetWriter {
- fn write<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- if let Some(parent) = full_path.parent() {
- async_fs::create_dir_all(parent).await?;
- }
- let file = File::create(&full_path).await?;
- let writer: Box = Box::new(file);
- Ok(writer)
- })
+ async fn write<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ if let Some(parent) = full_path.parent() {
+ async_fs::create_dir_all(parent).await?;
+ }
+ let file = File::create(&full_path).await?;
+ let writer: Box = Box::new(file);
+ Ok(writer)
}
- fn write_meta<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetWriterError>> {
- Box::pin(async move {
- let meta_path = get_meta_path(path);
- let full_path = self.root_path.join(meta_path);
- if let Some(parent) = full_path.parent() {
- async_fs::create_dir_all(parent).await?;
- }
- let file = File::create(&full_path).await?;
- let writer: Box = Box::new(file);
- Ok(writer)
- })
+ async fn write_meta<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> {
+ let meta_path = get_meta_path(path);
+ let full_path = self.root_path.join(meta_path);
+ if let Some(parent) = full_path.parent() {
+ async_fs::create_dir_all(parent).await?;
+ }
+ let file = File::create(&full_path).await?;
+ let writer: Box = Box::new(file);
+ Ok(writer)
}
- fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- async_fs::remove_file(full_path).await?;
- Ok(())
- })
+ async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ async_fs::remove_file(full_path).await?;
+ Ok(())
}
- fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
- Box::pin(async move {
- let meta_path = get_meta_path(path);
- let full_path = self.root_path.join(meta_path);
- async_fs::remove_file(full_path).await?;
- Ok(())
- })
+ async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
+ let meta_path = get_meta_path(path);
+ let full_path = self.root_path.join(meta_path);
+ async_fs::remove_file(full_path).await?;
+ Ok(())
}
- fn rename<'a>(
+ async fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
- ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_old_path = self.root_path.join(old_path);
- let full_new_path = self.root_path.join(new_path);
- if let Some(parent) = full_new_path.parent() {
- async_fs::create_dir_all(parent).await?;
- }
- async_fs::rename(full_old_path, full_new_path).await?;
- Ok(())
- })
+ ) -> Result<(), AssetWriterError> {
+ let full_old_path = self.root_path.join(old_path);
+ let full_new_path = self.root_path.join(new_path);
+ if let Some(parent) = full_new_path.parent() {
+ async_fs::create_dir_all(parent).await?;
+ }
+ async_fs::rename(full_old_path, full_new_path).await?;
+ Ok(())
}
- fn rename_meta<'a>(
+ async fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
- ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
- Box::pin(async move {
- let old_meta_path = get_meta_path(old_path);
- let new_meta_path = get_meta_path(new_path);
- let full_old_path = self.root_path.join(old_meta_path);
- let full_new_path = self.root_path.join(new_meta_path);
- if let Some(parent) = full_new_path.parent() {
- async_fs::create_dir_all(parent).await?;
- }
- async_fs::rename(full_old_path, full_new_path).await?;
- Ok(())
- })
+ ) -> Result<(), AssetWriterError> {
+ let old_meta_path = get_meta_path(old_path);
+ let new_meta_path = get_meta_path(new_path);
+ let full_old_path = self.root_path.join(old_meta_path);
+ let full_new_path = self.root_path.join(new_meta_path);
+ if let Some(parent) = full_new_path.parent() {
+ async_fs::create_dir_all(parent).await?;
+ }
+ async_fs::rename(full_old_path, full_new_path).await?;
+ Ok(())
}
- fn remove_directory<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- async_fs::remove_dir_all(full_path).await?;
- Ok(())
- })
+ async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ async_fs::remove_dir_all(full_path).await?;
+ Ok(())
}
- fn remove_empty_directory<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- async_fs::remove_dir(full_path).await?;
- Ok(())
- })
+ async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ async_fs::remove_dir(full_path).await?;
+ Ok(())
}
- fn remove_assets_in_directory<'a>(
+ async fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- async_fs::remove_dir_all(&full_path).await?;
- async_fs::create_dir_all(&full_path).await?;
- Ok(())
- })
+ ) -> Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ async_fs::remove_dir_all(&full_path).await?;
+ async_fs::create_dir_all(&full_path).await?;
+ Ok(())
}
}
diff --git a/crates/bevy_asset/src/io/file/sync_file_asset.rs b/crates/bevy_asset/src/io/file/sync_file_asset.rs
index a8bf573a7ab07..426472150167e 100644
--- a/crates/bevy_asset/src/io/file/sync_file_asset.rs
+++ b/crates/bevy_asset/src/io/file/sync_file_asset.rs
@@ -5,7 +5,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,
Reader, Writer,
};
-use bevy_utils::BoxedFuture;
use std::{
fs::{read_dir, File},
@@ -76,221 +75,180 @@ impl Stream for DirReader {
}
impl AssetReader for FileAssetReader {
- fn read<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- match File::open(&full_path) {
- Ok(file) => {
- let reader: Box = Box::new(FileReader(file));
- Ok(reader)
- }
- Err(e) => {
- if e.kind() == std::io::ErrorKind::NotFound {
- Err(AssetReaderError::NotFound(full_path))
- } else {
- Err(e.into())
- }
+ async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
+ let full_path = self.root_path.join(path);
+ match File::open(&full_path) {
+ Ok(file) => {
+ let reader: Box = Box::new(FileReader(file));
+ Ok(reader)
+ }
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::NotFound {
+ Err(AssetReaderError::NotFound(full_path))
+ } else {
+ Err(e.into())
}
}
- })
+ }
}
- fn read_meta<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
+ async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
let meta_path = get_meta_path(path);
- Box::pin(async move {
- let full_path = self.root_path.join(meta_path);
- match File::open(&full_path) {
- Ok(file) => {
- let reader: Box = Box::new(FileReader(file));
- Ok(reader)
- }
- Err(e) => {
- if e.kind() == std::io::ErrorKind::NotFound {
- Err(AssetReaderError::NotFound(full_path))
- } else {
- Err(e.into())
- }
+ let full_path = self.root_path.join(meta_path);
+ match File::open(&full_path) {
+ Ok(file) => {
+ let reader: Box = Box::new(FileReader(file));
+ Ok(reader)
+ }
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::NotFound {
+ Err(AssetReaderError::NotFound(full_path))
+ } else {
+ Err(e.into())
}
}
- })
+ }
}
- fn read_directory<'a>(
+ async fn read_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetReaderError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- match read_dir(&full_path) {
- Ok(read_dir) => {
- let root_path = self.root_path.clone();
- let mapped_stream = read_dir.filter_map(move |f| {
- f.ok().and_then(|dir_entry| {
- let path = dir_entry.path();
- // filter out meta files as they are not considered assets
- if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
- if ext.eq_ignore_ascii_case("meta") {
- return None;
- }
+ ) -> Result, AssetReaderError> {
+ let full_path = self.root_path.join(path);
+ match read_dir(&full_path) {
+ Ok(read_dir) => {
+ let root_path = self.root_path.clone();
+ let mapped_stream = read_dir.filter_map(move |f| {
+ f.ok().and_then(|dir_entry| {
+ let path = dir_entry.path();
+ // filter out meta files as they are not considered assets
+ if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
+ if ext.eq_ignore_ascii_case("meta") {
+ return None;
}
- let relative_path = path.strip_prefix(&root_path).unwrap();
- Some(relative_path.to_owned())
- })
- });
- let read_dir: Box = Box::new(DirReader(mapped_stream.collect()));
- Ok(read_dir)
- }
- Err(e) => {
- if e.kind() == std::io::ErrorKind::NotFound {
- Err(AssetReaderError::NotFound(full_path))
- } else {
- Err(e.into())
- }
+ }
+ let relative_path = path.strip_prefix(&root_path).unwrap();
+ Some(relative_path.to_owned())
+ })
+ });
+ let read_dir: Box = Box::new(DirReader(mapped_stream.collect()));
+ Ok(read_dir)
+ }
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::NotFound {
+ Err(AssetReaderError::NotFound(full_path))
+ } else {
+ Err(e.into())
}
}
- })
+ }
}
- fn is_directory<'a>(
+ async fn is_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- let metadata = full_path
- .metadata()
- .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
- Ok(metadata.file_type().is_dir())
- })
+ ) -> std::result::Result {
+ let full_path = self.root_path.join(path);
+ let metadata = full_path
+ .metadata()
+ .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
+ Ok(metadata.file_type().is_dir())
}
}
impl AssetWriter for FileAssetWriter {
- fn write<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- if let Some(parent) = full_path.parent() {
- std::fs::create_dir_all(parent)?;
- }
- let file = File::create(&full_path)?;
- let writer: Box = Box::new(FileWriter(file));
- Ok(writer)
- })
+ async fn write<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ if let Some(parent) = full_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ let file = File::create(&full_path)?;
+ let writer: Box = Box::new(FileWriter(file));
+ Ok(writer)
}
- fn write_meta<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetWriterError>> {
- Box::pin(async move {
- let meta_path = get_meta_path(path);
- let full_path = self.root_path.join(meta_path);
- if let Some(parent) = full_path.parent() {
- std::fs::create_dir_all(parent)?;
- }
- let file = File::create(&full_path)?;
- let writer: Box = Box::new(FileWriter(file));
- Ok(writer)
- })
+ async fn write_meta<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> {
+ let meta_path = get_meta_path(path);
+ let full_path = self.root_path.join(meta_path);
+ if let Some(parent) = full_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ let file = File::create(&full_path)?;
+ let writer: Box = Box::new(FileWriter(file));
+ Ok(writer)
}
- fn remove<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- std::fs::remove_file(full_path)?;
- Ok(())
- })
+ async fn remove<'a>(&'a self, path: &'a Path) -> std::result::Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ std::fs::remove_file(full_path)?;
+ Ok(())
}
- fn remove_meta<'a>(
+ async fn remove_meta<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
- Box::pin(async move {
- let meta_path = get_meta_path(path);
- let full_path = self.root_path.join(meta_path);
- std::fs::remove_file(full_path)?;
- Ok(())
- })
+ ) -> std::result::Result<(), AssetWriterError> {
+ let meta_path = get_meta_path(path);
+ let full_path = self.root_path.join(meta_path);
+ std::fs::remove_file(full_path)?;
+ Ok(())
}
- fn remove_directory<'a>(
+ async fn remove_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- std::fs::remove_dir_all(full_path)?;
- Ok(())
- })
+ ) -> std::result::Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ std::fs::remove_dir_all(full_path)?;
+ Ok(())
}
- fn remove_empty_directory<'a>(
+ async fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- std::fs::remove_dir(full_path)?;
- Ok(())
- })
+ ) -> std::result::Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ std::fs::remove_dir(full_path)?;
+ Ok(())
}
- fn remove_assets_in_directory<'a>(
+ async fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_path = self.root_path.join(path);
- std::fs::remove_dir_all(&full_path)?;
- std::fs::create_dir_all(&full_path)?;
- Ok(())
- })
+ ) -> std::result::Result<(), AssetWriterError> {
+ let full_path = self.root_path.join(path);
+ std::fs::remove_dir_all(&full_path)?;
+ std::fs::create_dir_all(&full_path)?;
+ Ok(())
}
- fn rename<'a>(
+ async fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
- Box::pin(async move {
- let full_old_path = self.root_path.join(old_path);
- let full_new_path = self.root_path.join(new_path);
- if let Some(parent) = full_new_path.parent() {
- std::fs::create_dir_all(parent)?;
- }
- std::fs::rename(full_old_path, full_new_path)?;
- Ok(())
- })
+ ) -> std::result::Result<(), AssetWriterError> {
+ let full_old_path = self.root_path.join(old_path);
+ let full_new_path = self.root_path.join(new_path);
+ if let Some(parent) = full_new_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ std::fs::rename(full_old_path, full_new_path)?;
+ Ok(())
}
- fn rename_meta<'a>(
+ async fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
- ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
- Box::pin(async move {
- let old_meta_path = get_meta_path(old_path);
- let new_meta_path = get_meta_path(new_path);
- let full_old_path = self.root_path.join(old_meta_path);
- let full_new_path = self.root_path.join(new_meta_path);
- if let Some(parent) = full_new_path.parent() {
- std::fs::create_dir_all(parent)?;
- }
- std::fs::rename(full_old_path, full_new_path)?;
- Ok(())
- })
+ ) -> std::result::Result<(), AssetWriterError> {
+ let old_meta_path = get_meta_path(old_path);
+ let new_meta_path = get_meta_path(new_path);
+ let full_old_path = self.root_path.join(old_meta_path);
+ let full_new_path = self.root_path.join(new_meta_path);
+ if let Some(parent) = full_new_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ std::fs::rename(full_old_path, full_new_path)?;
+ Ok(())
}
}
diff --git a/crates/bevy_asset/src/io/gated.rs b/crates/bevy_asset/src/io/gated.rs
index 76f531a04c88a..d3d2b35f1f066 100644
--- a/crates/bevy_asset/src/io/gated.rs
+++ b/crates/bevy_asset/src/io/gated.rs
@@ -1,5 +1,5 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
-use bevy_utils::{BoxedFuture, HashMap};
+use bevy_utils::HashMap;
use crossbeam_channel::{Receiver, Sender};
use parking_lot::RwLock;
use std::{path::Path, sync::Arc};
@@ -55,10 +55,7 @@ impl GatedReader {
}
impl AssetReader for GatedReader {
- fn read<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
+ async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
let receiver = {
let mut gates = self.gates.write();
let gates = gates
@@ -66,31 +63,23 @@ impl AssetReader for GatedReader {
.or_insert_with(crossbeam_channel::unbounded);
gates.1.clone()
};
- Box::pin(async move {
- receiver.recv().unwrap();
- let result = self.reader.read(path).await?;
- Ok(result)
- })
+ receiver.recv().unwrap();
+ let result = self.reader.read(path).await?;
+ Ok(result)
}
- fn read_meta<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
- self.reader.read_meta(path)
+ async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
+ self.reader.read_meta(path).await
}
- fn read_directory<'a>(
+ async fn read_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetReaderError>> {
- self.reader.read_directory(path)
+ ) -> Result, AssetReaderError> {
+ self.reader.read_directory(path).await
}
- fn is_directory<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result> {
- self.reader.is_directory(path)
+ async fn is_directory<'a>(&'a self, path: &'a Path) -> Result {
+ self.reader.is_directory(path).await
}
}
diff --git a/crates/bevy_asset/src/io/memory.rs b/crates/bevy_asset/src/io/memory.rs
index cc13d0482056a..563086f7b0620 100644
--- a/crates/bevy_asset/src/io/memory.rs
+++ b/crates/bevy_asset/src/io/memory.rs
@@ -1,5 +1,5 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
-use bevy_utils::{BoxedFuture, HashMap};
+use bevy_utils::HashMap;
use futures_io::AsyncRead;
use futures_lite::{ready, Stream};
use parking_lot::RwLock;
@@ -237,62 +237,47 @@ impl AsyncRead for DataReader {
}
impl AssetReader for MemoryAssetReader {
- fn read<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
- Box::pin(async move {
- self.root
- .get_asset(path)
- .map(|data| {
- let reader: Box = Box::new(DataReader {
- data,
- bytes_read: 0,
- });
- reader
- })
- .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
- })
+ async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
+ self.root
+ .get_asset(path)
+ .map(|data| {
+ let reader: Box = Box::new(DataReader {
+ data,
+ bytes_read: 0,
+ });
+ reader
+ })
+ .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
}
- fn read_meta<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>> {
- Box::pin(async move {
- self.root
- .get_metadata(path)
- .map(|data| {
- let reader: Box = Box::new(DataReader {
- data,
- bytes_read: 0,
- });
- reader
- })
- .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
- })
+ async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> {
+ self.root
+ .get_metadata(path)
+ .map(|data| {
+ let reader: Box = Box::new(DataReader {
+ data,
+ bytes_read: 0,
+ });
+ reader
+ })
+ .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
}
- fn read_directory<'a>(
+ async fn read_directory<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, Result, AssetReaderError>> {
- Box::pin(async move {
- self.root
- .get_dir(path)
- .map(|dir| {
- let stream: Box = Box::new(DirStream::new(dir));
- stream
- })
- .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
- })
+ ) -> Result, AssetReaderError> {
+ self.root
+ .get_dir(path)
+ .map(|dir| {
+ let stream: Box = Box::new(DirStream::new(dir));
+ stream
+ })
+ .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
}
- fn is_directory<'a>(
- &'a self,
- path: &'a Path,
- ) -> BoxedFuture<'a, Result> {
- Box::pin(async move { Ok(self.root.get_dir(path).is_some()) })
+ async fn is_directory<'a>(&'a self, path: &'a Path) -> Result {
+ Ok(self.root.get_dir(path).is_some())
}
}
diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs
index 9b8f83b0eea7f..766e536455fa7 100644
--- a/crates/bevy_asset/src/io/mod.rs
+++ b/crates/bevy_asset/src/io/mod.rs
@@ -21,7 +21,7 @@ mod source;
pub use futures_lite::{AsyncReadExt, AsyncWriteExt};
pub use source::*;
-use bevy_utils::BoxedFuture;
+use bevy_utils::{BoxedFuture, ConditionalSendFuture};
use futures_io::{AsyncRead, AsyncWrite};
use futures_lite::{ready, Stream};
use std::{
@@ -59,7 +59,7 @@ pub type Reader<'a> = dyn AsyncRead + Unpin + Send + Sync + 'a;
/// Performs read operations on an asset storage. [`AssetReader`] exposes a "virtual filesystem"
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
-/// `path`.
+/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead.
///
/// Also see [`AssetWriter`].
pub trait AssetReader: Send + Sync + 'static {
@@ -67,35 +67,90 @@ pub trait AssetReader: Send + Sync + 'static {
fn read<'a>(
&'a self,
path: &'a Path,
- ) -> BoxedFuture<'a, Result>, AssetReaderError>>;
+ ) -> impl ConditionalSendFuture