mirror of
https://github.com/bevyengine/bevy
synced 2025-01-09 19:58:58 +00:00
ac49dce4ca
# Objective Simplify implementing some asset traits without Box::pin(async move{}) shenanigans. Fixes (in part) https://github.com/bevyengine/bevy/issues/11308 ## Solution Use async-fn in traits when possible in all traits. Traits with return position impl trait are not object safe however, and as AssetReader and AssetWriter are both used with dynamic dispatch, you need a Boxed version of these futures anyway. In the future, Rust is [adding ](https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html)proc macros to generate these traits automatically, and at some point in the future dyn traits should 'just work'. Until then.... this seemed liked the right approach given more ErasedXXX already exist, but, no clue if there's plans here! Especially since these are public now, it's a bit of an unfortunate API, and means this is a breaking change. In theory this saves some performance when these traits are used with static dispatch, but, seems like most code paths go through dynamic dispatch, which boxes anyway. I also suspect a bunch of the lifetime annotations on these function could be simplified now as the BoxedFuture was often the only thing returned which needed a lifetime annotation, but I'm not touching that for now as traits + lifetimes can be so tricky. This is a revival of [pull/11362](https://github.com/bevyengine/bevy/pull/11362) after a spectacular merge f*ckup, with updates to the latest Bevy. Just to recap some discussion: - Overall this seems like a win for code quality, especially when implementing these traits, but a loss for having to deal with ErasedXXX variants. - `ConditionalSend` was the preferred name for the trait that might be Send, to deal with wasm platforms. - When reviewing be sure to disable whitespace difference, as that's 95% of the PR. ## Changelog - AssetReader, AssetWriter, AssetLoader, AssetSaver and Process now use async-fn in traits rather than boxed futures. ## Migration Guide - Custom implementations of AssetReader, AssetWriter, AssetLoader, AssetSaver and Process should switch to async fn rather than returning a bevy_utils::BoxedFuture. - Simultaniously, to use dynamic dispatch on these traits you should instead use dyn ErasedXXX.
118 lines
4.3 KiB
Rust
118 lines
4.3 KiB
Rust
use bevy_asset::{
|
|
io::{AsyncReadExt, Reader},
|
|
Asset, AssetLoader, LoadContext,
|
|
};
|
|
use bevy_reflect::TypePath;
|
|
use std::{io::Cursor, sync::Arc};
|
|
|
|
/// A source of audio data
|
|
#[derive(Asset, Debug, Clone, TypePath)]
|
|
pub struct AudioSource {
|
|
/// Raw data of the audio source.
|
|
///
|
|
/// The data must be one of the file formats supported by Bevy (`wav`, `ogg`, `flac`, or `mp3`).
|
|
/// It is decoded using [`rodio::decoder::Decoder`](https://docs.rs/rodio/latest/rodio/decoder/struct.Decoder.html).
|
|
///
|
|
/// The decoder has conditionally compiled methods
|
|
/// depending on the features enabled.
|
|
/// If the format used is not enabled,
|
|
/// then this will panic with an `UnrecognizedFormat` error.
|
|
pub bytes: Arc<[u8]>,
|
|
}
|
|
|
|
impl AsRef<[u8]> for AudioSource {
|
|
fn as_ref(&self) -> &[u8] {
|
|
&self.bytes
|
|
}
|
|
}
|
|
|
|
/// Loads files as [`AudioSource`] [`Assets`](bevy_asset::Assets)
|
|
///
|
|
/// This asset loader supports different audio formats based on the enable Bevy features.
|
|
/// The feature `bevy/vorbis` enables loading from `.ogg` files and is enabled by default.
|
|
/// Other file endings can be loaded from with additional features:
|
|
/// `.mp3` with `bevy/mp3`
|
|
/// `.flac` with `bevy/flac`
|
|
/// `.wav` with `bevy/wav`
|
|
#[derive(Default)]
|
|
pub struct AudioLoader;
|
|
|
|
impl AssetLoader for AudioLoader {
|
|
type Asset = AudioSource;
|
|
type Settings = ();
|
|
type Error = std::io::Error;
|
|
|
|
async fn load<'a>(
|
|
&'a self,
|
|
reader: &'a mut Reader<'_>,
|
|
_settings: &'a Self::Settings,
|
|
_load_context: &'a mut LoadContext<'_>,
|
|
) -> Result<AudioSource, Self::Error> {
|
|
let mut bytes = Vec::new();
|
|
reader.read_to_end(&mut bytes).await?;
|
|
Ok(AudioSource {
|
|
bytes: bytes.into(),
|
|
})
|
|
}
|
|
|
|
fn extensions(&self) -> &[&str] {
|
|
&[
|
|
#[cfg(feature = "mp3")]
|
|
"mp3",
|
|
#[cfg(feature = "flac")]
|
|
"flac",
|
|
#[cfg(feature = "wav")]
|
|
"wav",
|
|
#[cfg(feature = "vorbis")]
|
|
"oga",
|
|
#[cfg(feature = "vorbis")]
|
|
"ogg",
|
|
#[cfg(feature = "vorbis")]
|
|
"spx",
|
|
]
|
|
}
|
|
}
|
|
|
|
/// A type implementing this trait can be converted to a [`rodio::Source`] type.
|
|
/// It must be [`Send`] and [`Sync`] in order to be registered.
|
|
/// Types that implement this trait usually contain raw sound data that can be converted into an iterator of samples.
|
|
/// This trait is implemented for [`AudioSource`].
|
|
/// Check the example [`decodable`](https://github.com/bevyengine/bevy/blob/latest/examples/audio/decodable.rs) for how to implement this trait on a custom type.
|
|
pub trait Decodable: Send + Sync + 'static {
|
|
/// The type of the audio samples.
|
|
/// Usually a [`u16`], [`i16`] or [`f32`], as those implement [`rodio::Sample`].
|
|
/// Other types can implement the [`rodio::Sample`] trait as well.
|
|
type DecoderItem: rodio::Sample + Send + Sync;
|
|
|
|
/// The type of the iterator of the audio samples,
|
|
/// which iterates over samples of type [`Self::DecoderItem`].
|
|
/// Must be a [`rodio::Source`] so that it can provide information on the audio it is iterating over.
|
|
type Decoder: rodio::Source + Send + Iterator<Item = Self::DecoderItem>;
|
|
|
|
/// Build and return a [`Self::Decoder`] of the implementing type
|
|
fn decoder(&self) -> Self::Decoder;
|
|
}
|
|
|
|
impl Decodable for AudioSource {
|
|
type DecoderItem = <rodio::Decoder<Cursor<AudioSource>> as Iterator>::Item;
|
|
type Decoder = rodio::Decoder<Cursor<AudioSource>>;
|
|
|
|
fn decoder(&self) -> Self::Decoder {
|
|
rodio::Decoder::new(Cursor::new(self.clone())).unwrap()
|
|
}
|
|
}
|
|
|
|
/// A trait that allows adding a custom audio source to the object.
|
|
/// This is implemented for [`App`][bevy_app::App] to allow registering custom [`Decodable`] types.
|
|
pub trait AddAudioSource {
|
|
/// Registers an audio source.
|
|
/// The type must implement [`Decodable`],
|
|
/// so that it can be converted to a [`rodio::Source`] type,
|
|
/// and [`Asset`], so that it can be registered as an asset.
|
|
/// To use this method on [`App`][bevy_app::App],
|
|
/// the [audio][super::AudioPlugin] and [asset][bevy_asset::AssetPlugin] plugins must be added first.
|
|
fn add_audio_source<T>(&mut self) -> &mut Self
|
|
where
|
|
T: Decodable + Asset,
|
|
f32: rodio::cpal::FromSample<T::DecoderItem>;
|
|
}
|