docs: Full documentation for bevy_asset (#3536)

# Objective

This PR aims to document the `bevy_asset` crate to complete coverage, while also trying to improve some bits of UX.

### Progress

- [x] Root items
- [x] `handle` module
- [x] `info` module
- [x] `path` module
- [x] `loader` module
- [x] `io` and `filesystem_watcher` module
- [x] `assets` module
- [x] `asset_server` module
- [x] `diagnostic` module
- [x] `debug_asset_server` module
- [x] Crate level documentation
- [x] Add `#![warn(missing_docs)]` lint

Coverage: 100%

## Migration Guide

- Rename `FileAssetIo::get_root_path` uses to `FileAssetIo::get_base_path`

    `FileAssetIo::root_path()` is a getter for the `root_path` field, while `FileAssetIo::get_root_path` returned the parent directory of the asset root path, which was the executable's directory unless `CARGO_MANIFEST_DIR` was set. This change solves the ambiguity between the two methods.
This commit is contained in:
Mark Nokalt 2022-07-12 15:44:09 +00:00
parent 93a131661d
commit f9c1a8a3d5
16 changed files with 393 additions and 60 deletions

View File

@ -14,17 +14,31 @@ use parking_lot::{Mutex, RwLock};
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
use thiserror::Error; use thiserror::Error;
/// Errors that occur while loading assets with an `AssetServer` /// Errors that occur while loading assets with an `AssetServer`.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AssetServerError { pub enum AssetServerError {
/// Asset folder is not a directory.
#[error("asset folder path is not a directory: {0}")] #[error("asset folder path is not a directory: {0}")]
AssetFolderNotADirectory(String), AssetFolderNotADirectory(String),
/// No asset loader was found for the specified extensions.
#[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))] #[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))]
MissingAssetLoader { extensions: Vec<String> }, MissingAssetLoader {
/// The list of extensions detected on the asset source path that failed to load.
///
/// The list may be empty if the asset path is invalid or doesn't have an extension.
extensions: Vec<String>,
},
/// The handle type does not match the type of the loaded asset.
#[error("the given type does not match the type of the loaded asset")] #[error("the given type does not match the type of the loaded asset")]
IncorrectHandleType, IncorrectHandleType,
/// Encountered an error while processing an asset.
#[error("encountered an error while loading an asset: {0}")] #[error("encountered an error while loading an asset: {0}")]
AssetLoaderError(anyhow::Error), AssetLoaderError(anyhow::Error),
/// Encountered an error while reading an asset from disk.
#[error("encountered an error while reading an asset: {0}")] #[error("encountered an error while reading an asset: {0}")]
AssetIoError(#[from] AssetIoError), AssetIoError(#[from] AssetIoError),
} }
@ -48,6 +62,9 @@ pub(crate) struct AssetRefCounter {
pub(crate) mark_unused_assets: Arc<Mutex<Vec<HandleId>>>, pub(crate) mark_unused_assets: Arc<Mutex<Vec<HandleId>>>,
} }
/// Internal data for the asset server.
///
/// [`AssetServer`] is the public API for interacting with the asset server.
pub struct AssetServerInternal { pub struct AssetServerInternal {
pub(crate) asset_io: Box<dyn AssetIo>, pub(crate) asset_io: Box<dyn AssetIo>,
pub(crate) asset_ref_counter: AssetRefCounter, pub(crate) asset_ref_counter: AssetRefCounter,
@ -58,17 +75,45 @@ pub struct AssetServerInternal {
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>, handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
} }
/// Loads assets from the filesystem on background threads /// Loads assets from the filesystem in the background.
///
/// The asset server is the primary way of loading assets in bevy. It keeps track of the load state
/// of the assets it manages and can even reload them from the filesystem with
/// [`AssetServer::watch_for_changes`]!
///
/// The asset server is a _resource_, so in order to accesss it in a system you need a `Res`
/// accessor, like this:
///
/// ```rust,no_run
/// use bevy_asset::{AssetServer, Handle};
/// use bevy_ecs::prelude::{Commands, Res};
///
/// # #[derive(Debug, bevy_reflect::TypeUuid)]
/// # #[uuid = "00000000-0000-0000-0000-000000000000"]
/// # struct Image;
///
/// fn my_system(mut commands: Commands, asset_server: Res<AssetServer>)
/// {
/// // Now you can do whatever you want with the asset server, such as loading an asset:
/// let asset_handle: Handle<Image> = asset_server.load("cool_picture.png");
/// }
/// ```
///
/// See the [`asset_loading`] example for more information.
///
/// [`asset_loading`]: https://github.com/bevyengine/bevy/tree/latest/examples/asset/asset_loading.rs
#[derive(Clone)] #[derive(Clone)]
pub struct AssetServer { pub struct AssetServer {
pub(crate) server: Arc<AssetServerInternal>, pub(crate) server: Arc<AssetServerInternal>,
} }
impl AssetServer { impl AssetServer {
/// Creates a new asset server with the provided asset I/O.
pub fn new<T: AssetIo>(source_io: T) -> Self { pub fn new<T: AssetIo>(source_io: T) -> Self {
Self::with_boxed_io(Box::new(source_io)) Self::with_boxed_io(Box::new(source_io))
} }
/// Creates a new asset server with a boxed asset I/O.
pub fn with_boxed_io(asset_io: Box<dyn AssetIo>) -> Self { pub fn with_boxed_io(asset_io: Box<dyn AssetIo>) -> Self {
AssetServer { AssetServer {
server: Arc::new(AssetServerInternal { server: Arc::new(AssetServerInternal {
@ -83,6 +128,7 @@ impl AssetServer {
} }
} }
/// Returns the associated asset I/O.
pub fn asset_io(&self) -> &dyn AssetIo { pub fn asset_io(&self) -> &dyn AssetIo {
&*self.server.asset_io &*self.server.asset_io
} }
@ -104,6 +150,10 @@ impl AssetServer {
Assets::new(self.server.asset_ref_counter.channel.sender.clone()) Assets::new(self.server.asset_ref_counter.channel.sender.clone())
} }
/// Adds the provided asset loader to the server.
///
/// If `loader` has one or more supported extensions in conflict with loaders that came before
/// it, it will replace them.
pub fn add_loader<T>(&self, loader: T) pub fn add_loader<T>(&self, loader: T)
where where
T: AssetLoader, T: AssetLoader,
@ -126,11 +176,13 @@ impl AssetServer {
Ok(()) Ok(())
} }
/// Gets a strong handle for an asset with the provided id.
pub fn get_handle<T: Asset, I: Into<HandleId>>(&self, id: I) -> Handle<T> { pub fn get_handle<T: Asset, I: Into<HandleId>>(&self, id: I) -> Handle<T> {
let sender = self.server.asset_ref_counter.channel.sender.clone(); let sender = self.server.asset_ref_counter.channel.sender.clone();
Handle::strong(id.into(), sender) Handle::strong(id.into(), sender)
} }
/// Gets an untyped strong handle for an asset with the provided id.
pub fn get_handle_untyped<I: Into<HandleId>>(&self, id: I) -> HandleUntyped { pub fn get_handle_untyped<I: Into<HandleId>>(&self, id: I) -> HandleUntyped {
let sender = self.server.asset_ref_counter.channel.sender.clone(); let sender = self.server.asset_ref_counter.channel.sender.clone();
HandleUntyped::strong(id.into(), sender) HandleUntyped::strong(id.into(), sender)
@ -179,6 +231,7 @@ impl AssetServer {
}) })
} }
/// Gets the source path of an asset from the provided handle.
pub fn get_handle_path<H: Into<HandleId>>(&self, handle: H) -> Option<AssetPath<'_>> { pub fn get_handle_path<H: Into<HandleId>>(&self, handle: H) -> Option<AssetPath<'_>> {
self.server self.server
.handle_to_path .handle_to_path
@ -187,6 +240,7 @@ impl AssetServer {
.cloned() .cloned()
} }
/// Gets the load state of an asset from the provided handle.
pub fn get_load_state<H: Into<HandleId>>(&self, handle: H) -> LoadState { pub fn get_load_state<H: Into<HandleId>>(&self, handle: H) -> LoadState {
match handle.into() { match handle.into() {
HandleId::AssetPathId(id) => { HandleId::AssetPathId(id) => {
@ -199,6 +253,10 @@ impl AssetServer {
} }
} }
/// Gets the overall load state of a group of assets from the provided handles.
///
/// This method will only return [`LoadState::Loaded`] if all assets in the
/// group were loaded succesfully.
pub fn get_group_load_state(&self, handles: impl IntoIterator<Item = HandleId>) -> LoadState { pub fn get_group_load_state(&self, handles: impl IntoIterator<Item = HandleId>) -> LoadState {
let mut load_state = LoadState::Loaded; let mut load_state = LoadState::Loaded;
for handle_id in handles { for handle_id in handles {
@ -219,11 +277,14 @@ impl AssetServer {
load_state load_state
} }
/// Queue an [`Asset`] at the provided relative path for asynchronous loading. /// Queues an [`Asset`] at the provided relative path for asynchronous loading.
/// ///
/// The absolute Path to the asset is `"ROOT/ASSET_FOLDER_NAME/path"`. /// The absolute path to the asset is `"ROOT/ASSET_FOLDER_NAME/path"`. Its extension is then
/// extracted to search for an [asset loader]. If an asset path contains multiple dots (e.g.
/// `foo.bar.baz`), each level is considered a separate extension and the asset server will try
/// to look for loaders of `bar.baz` and `baz` assets.
/// ///
/// By default the ROOT is the directory of the Application, but this can be overridden by /// By default the `ROOT` is the directory of the Application, but this can be overridden by
/// setting the `"CARGO_MANIFEST_DIR"` environment variable /// setting the `"CARGO_MANIFEST_DIR"` environment variable
/// (see <https://doc.rust-lang.org/cargo/reference/environment-variables.html>) /// (see <https://doc.rust-lang.org/cargo/reference/environment-variables.html>)
/// to another directory. When the application is run through Cargo, then /// to another directory. When the application is run through Cargo, then
@ -235,7 +296,10 @@ impl AssetServer {
/// ///
/// The asset is loaded asynchronously, and will generally not be available by the time /// The asset is loaded asynchronously, and will generally not be available by the time
/// this calls returns. Use [`AssetServer::get_load_state`] to determine when the asset is /// this calls returns. Use [`AssetServer::get_load_state`] to determine when the asset is
/// effectively loaded and available in the [`Assets`] collection. /// effectively loaded and available in the [`Assets`] collection. The asset will always fail to
/// load if the provided path doesn't contain an extension.
///
/// [asset loader]: AssetLoader
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
pub fn load<'a, T: Asset, P: Into<AssetPath<'a>>>(&self, path: P) -> Handle<T> { pub fn load<'a, T: Asset, P: Into<AssetPath<'a>>>(&self, path: P) -> Handle<T> {
self.load_untyped(path).typed() self.load_untyped(path).typed()
@ -365,6 +429,9 @@ impl AssetServer {
Ok(asset_path_id) Ok(asset_path_id)
} }
/// Queues the [`Asset`] at the provided path for loading and returns an untyped handle.
///
/// See [`load`](AssetServer::load).
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
pub fn load_untyped<'a, P: Into<AssetPath<'a>>>(&self, path: P) -> HandleUntyped { pub fn load_untyped<'a, P: Into<AssetPath<'a>>>(&self, path: P) -> HandleUntyped {
let handle_id = self.load_untracked(path.into(), false); let handle_id = self.load_untracked(path.into(), false);
@ -400,6 +467,14 @@ impl AssetServer {
asset_path.into() asset_path.into()
} }
/// Loads assets from the specified folder recursively.
///
/// # Errors
///
/// - If the provided path is not a directory, it will fail with
/// [`AssetServerError::AssetFolderNotADirectory`].
/// - If something unexpected happened while loading an asset, other
/// [`AssetServerError`]s may be returned.
#[must_use = "not using the returned strong handles may result in the unexpected release of the assets"] #[must_use = "not using the returned strong handles may result in the unexpected release of the assets"]
pub fn load_folder<P: AsRef<Path>>( pub fn load_folder<P: AsRef<Path>>(
&self, &self,
@ -429,6 +504,7 @@ impl AssetServer {
Ok(handles) Ok(handles)
} }
/// Frees unused assets, unloading them from memory.
pub fn free_unused_assets(&self) { pub fn free_unused_assets(&self) {
let mut potential_frees = self.server.asset_ref_counter.mark_unused_assets.lock(); let mut potential_frees = self.server.asset_ref_counter.mark_unused_assets.lock();
@ -455,6 +531,7 @@ impl AssetServer {
} }
} }
/// Iterates through asset references and marks assets with no active handles as unused.
pub fn mark_unused_assets(&self) { pub fn mark_unused_assets(&self) {
let receiver = &self.server.asset_ref_counter.channel.receiver; let receiver = &self.server.asset_ref_counter.channel.receiver;
let mut ref_counts = self.server.asset_ref_counter.ref_counts.write(); let mut ref_counts = self.server.asset_ref_counter.ref_counts.write();
@ -559,6 +636,7 @@ fn free_unused_assets_system_impl(asset_server: &AssetServer) {
asset_server.mark_unused_assets(); asset_server.mark_unused_assets();
} }
/// A system for freeing assets that have no active handles.
pub fn free_unused_assets_system(asset_server: Res<AssetServer>) { pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
free_unused_assets_system_impl(&asset_server); free_unused_assets_system_impl(&asset_server);
} }

View File

@ -12,12 +12,16 @@ use bevy_utils::HashMap;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use std::fmt::Debug; use std::fmt::Debug;
/// Events that happen on assets of type `T` /// Events that involve assets of type `T`.
/// ///
/// Events sent via the [Assets] struct will always be sent with a _Weak_ handle /// Events sent via the [`Assets`] struct will always be sent with a _Weak_ handle, because the
/// asset may not exist by the time the event is handled.
pub enum AssetEvent<T: Asset> { pub enum AssetEvent<T: Asset> {
#[allow(missing_docs)]
Created { handle: Handle<T> }, Created { handle: Handle<T> },
#[allow(missing_docs)]
Modified { handle: Handle<T> }, Modified { handle: Handle<T> },
#[allow(missing_docs)]
Removed { handle: Handle<T> }, Removed { handle: Handle<T> },
} }
@ -81,6 +85,7 @@ impl<T: Asset> Assets<T> {
/// Adds an asset to the collection, returning a Strong handle to that asset. /// Adds an asset to the collection, returning a Strong handle to that asset.
/// ///
/// # Events /// # Events
///
/// * [`AssetEvent::Created`] /// * [`AssetEvent::Created`]
pub fn add(&mut self, asset: T) -> Handle<T> { pub fn add(&mut self, asset: T) -> Handle<T> {
let id = HandleId::random::<T>(); let id = HandleId::random::<T>();
@ -110,8 +115,9 @@ impl<T: Asset> Assets<T> {
/// new asset will be inserted. /// new asset will be inserted.
/// ///
/// # Events /// # Events
/// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle ///
/// * [`AssetEvent::Modified`]: Sent if the asset with given handle already existed /// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle.
/// * [`AssetEvent::Modified`]: Sent if the asset with given handle already existed.
pub fn set_untracked<H: Into<HandleId>>(&mut self, handle: H, asset: T) { pub fn set_untracked<H: Into<HandleId>>(&mut self, handle: H, asset: T) {
let id: HandleId = handle.into(); let id: HandleId = handle.into();
if self.assets.insert(id, asset).is_some() { if self.assets.insert(id, asset).is_some() {
@ -125,7 +131,7 @@ impl<T: Asset> Assets<T> {
} }
} }
/// Get the asset for the given handle. /// Gets the asset for the given handle.
/// ///
/// This is the main method for accessing asset data from an [Assets] collection. If you need /// This is the main method for accessing asset data from an [Assets] collection. If you need
/// mutable access to the asset, use [`get_mut`](Assets::get_mut). /// mutable access to the asset, use [`get_mut`](Assets::get_mut).
@ -150,15 +156,16 @@ impl<T: Asset> Assets<T> {
self.assets.get_mut(&id) self.assets.get_mut(&id)
} }
/// Gets a _Strong_ handle pointing to the same asset as the given one /// Gets a _Strong_ handle pointing to the same asset as the given one.
pub fn get_handle<H: Into<HandleId>>(&self, handle: H) -> Handle<T> { pub fn get_handle<H: Into<HandleId>>(&self, handle: H) -> Handle<T> {
Handle::strong(handle.into(), self.ref_change_sender.clone()) Handle::strong(handle.into(), self.ref_change_sender.clone())
} }
/// Get mutable access to an asset for the given handle, inserting a new value if none exists. /// Gets mutable access to an asset for the given handle, inserting a new value if none exists.
/// ///
/// # Events /// # Events
/// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle ///
/// * [`AssetEvent::Created`]: Sent if the asset did not yet exist with the given handle.
pub fn get_or_insert_with<H: Into<HandleId>>( pub fn get_or_insert_with<H: Into<HandleId>>(
&mut self, &mut self,
handle: H, handle: H,
@ -179,12 +186,12 @@ impl<T: Asset> Assets<T> {
borrowed borrowed
} }
/// Get an iterator over all assets in the collection. /// Gets an iterator over all assets in the collection.
pub fn iter(&self) -> impl Iterator<Item = (HandleId, &T)> { pub fn iter(&self) -> impl Iterator<Item = (HandleId, &T)> {
self.assets.iter().map(|(k, v)| (*k, v)) self.assets.iter().map(|(k, v)| (*k, v))
} }
/// Get a mutable iterator over all assets in the collection. /// Gets a mutable iterator over all assets in the collection.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (HandleId, &mut T)> { pub fn iter_mut(&mut self) -> impl Iterator<Item = (HandleId, &mut T)> {
self.assets.iter_mut().map(|(k, v)| { self.assets.iter_mut().map(|(k, v)| {
self.events.send(AssetEvent::Modified { self.events.send(AssetEvent::Modified {
@ -194,16 +201,17 @@ impl<T: Asset> Assets<T> {
}) })
} }
/// Get an iterator over all [`HandleId`]'s in the collection. /// Gets an iterator over all [`HandleId`]'s in the collection.
pub fn ids(&self) -> impl Iterator<Item = HandleId> + '_ { pub fn ids(&self) -> impl Iterator<Item = HandleId> + '_ {
self.assets.keys().cloned() self.assets.keys().cloned()
} }
/// Remove an asset for the given handle. /// Removes an asset for the given handle.
/// ///
/// The asset is returned if it existed in the collection, otherwise `None`. /// The asset is returned if it existed in the collection, otherwise `None`.
/// ///
/// # Events /// # Events
///
/// * [`AssetEvent::Removed`] /// * [`AssetEvent::Removed`]
pub fn remove<H: Into<HandleId>>(&mut self, handle: H) -> Option<T> { pub fn remove<H: Into<HandleId>>(&mut self, handle: H) -> Option<T> {
let id: HandleId = handle.into(); let id: HandleId = handle.into();
@ -238,6 +246,8 @@ impl<T: Asset> Assets<T> {
self.assets.shrink_to_fit(); self.assets.shrink_to_fit();
} }
/// A system that creates [`AssetEvent`]s at the end of the frame based on changes in the
/// asset storage.
pub fn asset_event_system( pub fn asset_event_system(
mut events: EventWriter<AssetEvent<T>>, mut events: EventWriter<AssetEvent<T>>,
mut assets: ResMut<Assets<T>>, mut assets: ResMut<Assets<T>>,
@ -249,40 +259,60 @@ impl<T: Asset> Assets<T> {
} }
} }
/// Gets the number of assets in the collection /// Gets the number of assets in the collection.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.assets.len() self.assets.len()
} }
/// Returns true if there are no stored assets /// Returns `true` if there are no stored assets.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.assets.is_empty() self.assets.is_empty()
} }
} }
/// [App] extension methods for adding new asset types /// [`App`] extension methods for adding new asset types.
pub trait AddAsset { pub trait AddAsset {
/// Registers `T` as a supported asset in the application.
///
/// Adding the same type again after it has been added does nothing.
fn add_asset<T>(&mut self) -> &mut Self fn add_asset<T>(&mut self) -> &mut Self
where where
T: Asset; T: Asset;
/// Registers `T` as a supported internal asset in the application.
///
/// Internal assets (e.g. shaders) are bundled directly into the app and can't be hot reloaded
/// using the conventional API. See `DebugAssetServerPlugin`.
///
/// Adding the same type again after it has been added does nothing.
fn add_debug_asset<T: Clone>(&mut self) -> &mut Self fn add_debug_asset<T: Clone>(&mut self) -> &mut Self
where where
T: Asset; T: Asset;
/// Adds an asset loader `T` using default values.
///
/// The default values may come from the `World` or from `T::default()`.
fn init_asset_loader<T>(&mut self) -> &mut Self fn init_asset_loader<T>(&mut self) -> &mut Self
where where
T: AssetLoader + FromWorld; T: AssetLoader + FromWorld;
/// Adds an asset loader `T` for internal assets using default values.
///
/// Internal assets (e.g. shaders) are bundled directly into the app and can't be hot reloaded
/// using the conventional API. See `DebugAssetServerPlugin`.
///
/// The default values may come from the `World` or from `T::default()`.
fn init_debug_asset_loader<T>(&mut self) -> &mut Self fn init_debug_asset_loader<T>(&mut self) -> &mut Self
where where
T: AssetLoader + FromWorld; T: AssetLoader + FromWorld;
/// Adds the provided asset loader to the application.
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
where where
T: AssetLoader; T: AssetLoader;
} }
impl AddAsset for App { impl AddAsset for App {
/// Add an [`Asset`] to the [`App`].
///
/// Adding the same [`Asset`] again after it has been added does nothing.
fn add_asset<T>(&mut self) -> &mut Self fn add_asset<T>(&mut self) -> &mut Self
where where
T: Asset, T: Asset,
@ -349,6 +379,10 @@ impl AddAsset for App {
} }
} }
/// Loads an internal asset.
///
/// Internal assets (e.g. shaders) are bundled directly into the app and can't be hot reloaded
/// using the conventional API. See `DebugAssetServerPlugin`.
#[cfg(feature = "debug_asset_server")] #[cfg(feature = "debug_asset_server")]
#[macro_export] #[macro_export]
macro_rules! load_internal_asset { macro_rules! load_internal_asset {
@ -370,6 +404,10 @@ macro_rules! load_internal_asset {
}}; }};
} }
/// Loads an internal asset.
///
/// Internal assets (e.g. shaders) are bundled directly into the app and can't be hot reloaded
/// using the conventional API. See `DebugAssetServerPlugin`.
#[cfg(not(feature = "debug_asset_server"))] #[cfg(not(feature = "debug_asset_server"))]
#[macro_export] #[macro_export]
macro_rules! load_internal_asset { macro_rules! load_internal_asset {

View File

@ -1,3 +1,7 @@
//! Support for hot reloading internal assets.
//!
//! Internal assets (e.g. shaders) are bundled directly into an application and can't be hot
//! reloaded using the conventional API.
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_ecs::{ use bevy_ecs::{
event::Events, event::Events,
@ -16,8 +20,7 @@ use crate::{
HandleUntyped, HandleUntyped,
}; };
/// A "debug asset app", whose sole responsibility is hot reloading assets that are /// A helper [`App`] used for hot reloading internal assets, which are compiled-in to Bevy plugins.
/// "internal" / compiled-in to Bevy Plugins.
pub struct DebugAssetApp(App); pub struct DebugAssetApp(App);
impl Deref for DebugAssetApp { impl Deref for DebugAssetApp {
@ -34,17 +37,23 @@ impl DerefMut for DebugAssetApp {
} }
} }
/// A label describing the system that runs [`DebugAssetApp`].
#[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)] #[derive(SystemLabel, Debug, Clone, PartialEq, Eq, Hash)]
pub struct DebugAssetAppRun; pub struct DebugAssetAppRun;
/// Facilitates the creation of a "debug asset app", whose sole responsibility is hot reloading /// Facilitates the creation of a "debug asset app", whose sole responsibility is hot reloading
/// assets that are "internal" / compiled-in to Bevy Plugins. /// assets that are "internal" / compiled-in to Bevy Plugins.
/// Pair with [`load_internal_asset`](crate::load_internal_asset) to load "hot reloadable" assets ///
/// The `debug_asset_server` feature flag must also be enabled for hot reloading to work. /// Pair with the [`load_internal_asset`](crate::load_internal_asset) macro to load hot-reloadable
/// assets. The `debug_asset_server` feature flag must also be enabled for hot reloading to work.
/// Currently only hot reloads assets stored in the `crates` folder. /// Currently only hot reloads assets stored in the `crates` folder.
#[derive(Default)] #[derive(Default)]
pub struct DebugAssetServerPlugin; pub struct DebugAssetServerPlugin;
/// A collection that maps internal assets in a [`DebugAssetApp`]'s asset server to their mirrors in
/// the main [`App`].
pub struct HandleMap<T: Asset> { pub struct HandleMap<T: Asset> {
/// The collection of asset handles.
pub handles: HashMap<Handle<T>, Handle<T>>, pub handles: HashMap<Handle<T>, Handle<T>>,
} }
@ -106,6 +115,7 @@ pub(crate) fn sync_debug_assets<T: Asset + Clone>(
/// Uses the return type of the given loader to register the given handle with the appropriate type /// Uses the return type of the given loader to register the given handle with the appropriate type
/// and load the asset with the given `path` and parent `file_path`. /// and load the asset with the given `path` and parent `file_path`.
///
/// If this feels a bit odd ... thats because it is. This was built to improve the UX of the /// If this feels a bit odd ... thats because it is. This was built to improve the UX of the
/// `load_internal_asset` macro. /// `load_internal_asset` macro.
pub fn register_handle_with_loader<A: Asset>( pub fn register_handle_with_loader<A: Asset>(

View File

@ -3,7 +3,7 @@ use bevy_app::prelude::*;
use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics, MAX_DIAGNOSTIC_NAME_WIDTH}; use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics, MAX_DIAGNOSTIC_NAME_WIDTH};
use bevy_ecs::system::{Res, ResMut}; use bevy_ecs::system::{Res, ResMut};
/// Adds "asset count" diagnostic to an App /// Adds an asset count diagnostic to an [`App`] for assets of type `T`.
pub struct AssetCountDiagnosticsPlugin<T: Asset> { pub struct AssetCountDiagnosticsPlugin<T: Asset> {
marker: std::marker::PhantomData<T>, marker: std::marker::PhantomData<T>,
} }
@ -24,10 +24,14 @@ impl<T: Asset> Plugin for AssetCountDiagnosticsPlugin<T> {
} }
impl<T: Asset> AssetCountDiagnosticsPlugin<T> { impl<T: Asset> AssetCountDiagnosticsPlugin<T> {
/// Gets unique id of this diagnostic.
///
/// The diagnostic id is the type uuid of `T`.
pub fn diagnostic_id() -> DiagnosticId { pub fn diagnostic_id() -> DiagnosticId {
DiagnosticId(T::TYPE_UUID) DiagnosticId(T::TYPE_UUID)
} }
/// Registers the asset count diagnostic for the current application.
pub fn setup_system(mut diagnostics: ResMut<Diagnostics>) { pub fn setup_system(mut diagnostics: ResMut<Diagnostics>) {
let asset_type_name = std::any::type_name::<T>(); let asset_type_name = std::any::type_name::<T>();
let max_length = MAX_DIAGNOSTIC_NAME_WIDTH - "asset_count ".len(); let max_length = MAX_DIAGNOSTIC_NAME_WIDTH - "asset_count ".len();
@ -47,6 +51,7 @@ impl<T: Asset> AssetCountDiagnosticsPlugin<T> {
)); ));
} }
/// Updates the asset count of `T` assets.
pub fn diagnostic_system(mut diagnostics: ResMut<Diagnostics>, assets: Res<Assets<T>>) { pub fn diagnostic_system(mut diagnostics: ResMut<Diagnostics>, assets: Res<Assets<T>>) {
diagnostics.add_measurement(Self::diagnostic_id(), || assets.len() as f64); diagnostics.add_measurement(Self::diagnostic_id(), || assets.len() as f64);
} }

View File

@ -1,2 +1,4 @@
//! Diagnostic providers for `bevy_diagnostic`.
mod asset_count_diagnostics_plugin; mod asset_count_diagnostics_plugin;
pub use asset_count_diagnostics_plugin::AssetCountDiagnosticsPlugin; pub use asset_count_diagnostics_plugin::AssetCountDiagnosticsPlugin;

View File

@ -2,8 +2,10 @@ use crossbeam_channel::Receiver;
use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher}; use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher};
use std::path::Path; use std::path::Path;
/// Watches for changes to assets on the filesystem. This is used by the `AssetServer` to reload /// Watches for changes to files on the local filesystem.
/// them ///
/// When hot-reloading is enabled, the [`AssetServer`](crate::AssetServer) uses this to reload
/// assets when their source files are modified.
pub struct FilesystemWatcher { pub struct FilesystemWatcher {
pub watcher: RecommendedWatcher, pub watcher: RecommendedWatcher,
pub receiver: Receiver<Result<Event>>, pub receiver: Receiver<Result<Event>>,
@ -21,6 +23,7 @@ impl Default for FilesystemWatcher {
} }
impl FilesystemWatcher { impl FilesystemWatcher {
/// Watch for changes recursively at the provided path.
pub fn watch<P: AsRef<Path>>(&mut self, path: P) -> Result<()> { pub fn watch<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
self.watcher.watch(path.as_ref(), RecursiveMode::Recursive) self.watcher.watch(path.as_ref(), RecursiveMode::Recursive)
} }

View File

@ -15,7 +15,7 @@ use bevy_utils::Uuid;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A unique, stable asset id /// A unique, stable asset id.
#[derive( #[derive(
Debug, Debug,
Clone, Clone,
@ -32,7 +32,10 @@ use serde::{Deserialize, Serialize};
)] )]
#[reflect_value(Serialize, Deserialize, PartialEq, Hash)] #[reflect_value(Serialize, Deserialize, PartialEq, Hash)]
pub enum HandleId { pub enum HandleId {
/// A handle id of a loaded asset.
Id(Uuid, u64), Id(Uuid, u64),
/// A handle id of a pending asset.
AssetPathId(AssetPathId), AssetPathId(AssetPathId),
} }
@ -49,32 +52,34 @@ impl<'a> From<AssetPath<'a>> for HandleId {
} }
impl HandleId { impl HandleId {
/// Creates a random id for an asset of type `T`.
#[inline] #[inline]
pub fn random<T: Asset>() -> Self { pub fn random<T: Asset>() -> Self {
HandleId::Id(T::TYPE_UUID, rand::random()) HandleId::Id(T::TYPE_UUID, rand::random())
} }
/// Creates the default id for an asset of type `T`.
#[inline] #[inline]
pub fn default<T: Asset>() -> Self { pub fn default<T: Asset>() -> Self {
HandleId::Id(T::TYPE_UUID, 0) HandleId::Id(T::TYPE_UUID, 0)
} }
/// Creates an arbitrary asset id without an explicit type bound.
#[inline] #[inline]
pub const fn new(type_uuid: Uuid, id: u64) -> Self { pub const fn new(type_uuid: Uuid, id: u64) -> Self {
HandleId::Id(type_uuid, id) HandleId::Id(type_uuid, id)
} }
} }
/// A handle into a specific Asset of type `T` /// A handle into a specific [`Asset`] of type `T`.
/// ///
/// Handles contain a unique id that corresponds to a specific asset in the [Assets](crate::Assets) /// Handles contain a unique id that corresponds to a specific asset in the [`Assets`] collection.
/// collection.
/// ///
/// # Accessing the Asset /// # Accessing the Asset
/// ///
/// A handle is _not_ the asset itself, but should be seen as a pointer to the asset. Modifying a /// A handle is _not_ the asset itself, but should be seen as a pointer to the asset. Modifying a
/// handle's `id` only modifies which asset is being pointed to. To get the actual asset, try using /// handle's `id` only modifies which asset is being pointed to. To get the actual asset, try using
/// [`Assets::get`](crate::Assets::get) or [`Assets::get_mut`](crate::Assets::get_mut). /// [`Assets::get`] or [`Assets::get_mut`].
/// ///
/// # Strong and Weak /// # Strong and Weak
/// ///
@ -100,7 +105,7 @@ pub struct Handle<T>
where where
T: Asset, T: Asset,
{ {
/// The ID of the asset as contained within its respective [Assets](crate::Assets) collection /// The ID of the asset as contained within its respective [`Assets`] collection
pub id: HandleId, pub id: HandleId,
#[reflect(ignore)] #[reflect(ignore)]
handle_type: HandleType, handle_type: HandleType,
@ -136,6 +141,7 @@ impl<T: Asset> Handle<T> {
} }
} }
/// Creates a weak handle into an Asset identified by `id`.
#[inline] #[inline]
pub fn weak(id: HandleId) -> Self { pub fn weak(id: HandleId) -> Self {
Self { Self {
@ -145,7 +151,7 @@ impl<T: Asset> Handle<T> {
} }
} }
/// Get a copy of this handle as a Weak handle /// Recasts this handle as a weak handle of an Asset `U`.
pub fn as_weak<U: Asset>(&self) -> Handle<U> { pub fn as_weak<U: Asset>(&self) -> Handle<U> {
Handle { Handle {
id: self.id, id: self.id,
@ -154,17 +160,19 @@ impl<T: Asset> Handle<T> {
} }
} }
/// Returns `true` if this is a weak handle.
pub fn is_weak(&self) -> bool { pub fn is_weak(&self) -> bool {
matches!(self.handle_type, HandleType::Weak) matches!(self.handle_type, HandleType::Weak)
} }
/// Returns `true` if this is a strong handle.
pub fn is_strong(&self) -> bool { pub fn is_strong(&self) -> bool {
matches!(self.handle_type, HandleType::Strong(_)) matches!(self.handle_type, HandleType::Strong(_))
} }
/// Makes this handle Strong if it wasn't already. /// Makes this handle Strong if it wasn't already.
/// ///
/// This method requires the corresponding [Assets](crate::Assets) collection /// This method requires the corresponding [`Assets`](crate::Assets) collection.
pub fn make_strong(&mut self, assets: &Assets<T>) { pub fn make_strong(&mut self, assets: &Assets<T>) {
if self.is_strong() { if self.is_strong() {
return; return;
@ -174,12 +182,14 @@ impl<T: Asset> Handle<T> {
self.handle_type = HandleType::Strong(sender); self.handle_type = HandleType::Strong(sender);
} }
/// Creates a weak copy of this handle.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn clone_weak(&self) -> Self { pub fn clone_weak(&self) -> Self {
Self::weak(self.id) Self::weak(self.id)
} }
/// Creates an untyped copy of this handle.
pub fn clone_untyped(&self) -> HandleUntyped { pub fn clone_untyped(&self) -> HandleUntyped {
match &self.handle_type { match &self.handle_type {
HandleType::Strong(sender) => HandleUntyped::strong(self.id, sender.clone()), HandleType::Strong(sender) => HandleUntyped::strong(self.id, sender.clone()),
@ -187,6 +197,7 @@ impl<T: Asset> Handle<T> {
} }
} }
/// Creates a weak, untyped copy of this handle.
pub fn clone_weak_untyped(&self) -> HandleUntyped { pub fn clone_weak_untyped(&self) -> HandleUntyped {
HandleUntyped::weak(self.id) HandleUntyped::weak(self.id)
} }
@ -289,7 +300,7 @@ impl<T: Asset> Clone for Handle<T> {
} }
} }
/// A non-generic version of [Handle] /// A non-generic version of [`Handle`].
/// ///
/// This allows handles to be mingled in a cross asset context. For example, storing `Handle<A>` and /// This allows handles to be mingled in a cross asset context. For example, storing `Handle<A>` and
/// `Handle<B>` in the same `HashSet<HandleUntyped>`. /// `Handle<B>` in the same `HashSet<HandleUntyped>`.
@ -297,11 +308,13 @@ impl<T: Asset> Clone for Handle<T> {
/// To convert back to a typed handle, use the [typed](HandleUntyped::typed) method. /// To convert back to a typed handle, use the [typed](HandleUntyped::typed) method.
#[derive(Debug)] #[derive(Debug)]
pub struct HandleUntyped { pub struct HandleUntyped {
/// An unique identifier to an Asset.
pub id: HandleId, pub id: HandleId,
handle_type: HandleType, handle_type: HandleType,
} }
impl HandleUntyped { impl HandleUntyped {
/// Creates a weak untyped handle with an arbitrary id.
pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self { pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self {
Self { Self {
id: HandleId::new(uuid, id), id: HandleId::new(uuid, id),
@ -317,6 +330,7 @@ impl HandleUntyped {
} }
} }
/// Create a weak, untyped handle into an Asset identified by `id`.
pub fn weak(id: HandleId) -> Self { pub fn weak(id: HandleId) -> Self {
Self { Self {
id, id,
@ -324,15 +338,18 @@ impl HandleUntyped {
} }
} }
/// Creates a weak copy of this handle.
#[must_use] #[must_use]
pub fn clone_weak(&self) -> Self { pub fn clone_weak(&self) -> Self {
Self::weak(self.id) Self::weak(self.id)
} }
/// Returns `true` if this is a weak handle.
pub fn is_weak(&self) -> bool { pub fn is_weak(&self) -> bool {
matches!(self.handle_type, HandleType::Weak) matches!(self.handle_type, HandleType::Weak)
} }
/// Returns `true` if this is a strong handle.
pub fn is_strong(&self) -> bool { pub fn is_strong(&self) -> bool {
matches!(self.handle_type, HandleType::Strong(_)) matches!(self.handle_type, HandleType::Strong(_))
} }
@ -345,9 +362,13 @@ impl HandleUntyped {
self.clone_weak().typed() self.clone_weak().typed()
} }
/// Convert this handle into a typed [Handle]. /// Converts this handle into a typed [`Handle`] of an [`Asset`] `T`.
/// ///
/// The new handle will maintain the Strong or Weak status of the current handle. /// The new handle will maintain the Strong or Weak status of the current handle.
///
/// # Panics
///
/// Will panic if type `T` doesn't match this handle's actual asset type.
pub fn typed<T: Asset>(mut self) -> Handle<T> { pub fn typed<T: Asset>(mut self) -> Handle<T> {
if let HandleId::Id(type_uuid, _) = self.id { if let HandleId::Id(type_uuid, _) = self.id {
assert!( assert!(

View File

@ -3,53 +3,67 @@ use bevy_utils::{HashMap, HashSet, Uuid};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
/// Metadata for an asset source.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SourceMeta { pub struct SourceMeta {
/// A collection of asset metadata.
pub assets: Vec<AssetMeta>, pub assets: Vec<AssetMeta>,
} }
/// Metadata for an asset.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AssetMeta { pub struct AssetMeta {
/// Asset label.
pub label: Option<String>, pub label: Option<String>,
/// Asset dependencies.
pub dependencies: Vec<AssetPath<'static>>, pub dependencies: Vec<AssetPath<'static>>,
/// An unique identifier for an asset type.
pub type_uuid: Uuid, pub type_uuid: Uuid,
} }
/// Info about a specific asset, such as its path and its current load state /// Information about an asset source, such as its path, load state and asset metadata.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SourceInfo { pub struct SourceInfo {
/// Metadata for the source.
pub meta: Option<SourceMeta>, pub meta: Option<SourceMeta>,
/// The path of the source.
pub path: PathBuf, pub path: PathBuf,
/// A map of assets and their type identifiers.
pub asset_types: HashMap<LabelId, Uuid>, pub asset_types: HashMap<LabelId, Uuid>,
/// The load state of the source.
pub load_state: LoadState, pub load_state: LoadState,
/// A collection to track which assets were sent to their asset storages.
pub committed_assets: HashSet<LabelId>, pub committed_assets: HashSet<LabelId>,
/// Current version of the source.
pub version: usize, pub version: usize,
} }
impl SourceInfo { impl SourceInfo {
/// Returns `true` if all assets tracked by the source were loaded into their asset storages.
pub fn is_loaded(&self) -> bool { pub fn is_loaded(&self) -> bool {
self.meta.as_ref().map_or(false, |meta| { self.meta.as_ref().map_or(false, |meta| {
self.committed_assets.len() == meta.assets.len() self.committed_assets.len() == meta.assets.len()
}) })
} }
/// Gets the type identifier for an asset identified by `label_id`.
pub fn get_asset_type(&self, label_id: LabelId) -> Option<Uuid> { pub fn get_asset_type(&self, label_id: LabelId) -> Option<Uuid> {
self.asset_types.get(&label_id).cloned() self.asset_types.get(&label_id).cloned()
} }
} }
/// The load state of an asset /// The load state of an asset.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum LoadState { pub enum LoadState {
/// The asset has not be loaded. /// The asset has not been loaded.
NotLoaded, NotLoaded,
/// The asset in the the process of loading. /// The asset is in the process of loading.
Loading, Loading,
/// The asset has loaded and is living inside an [`Assets`](crate::Assets) collection. /// The asset has been loaded and is living inside an [`Assets`](crate::Assets) collection.
Loaded, Loaded,
/// The asset failed to load. /// The asset failed to load.
Failed, Failed,
/// The asset was previously loaded, however all handles were dropped and /// The asset was previously loaded, however all handles were dropped and the asset was removed
/// the asset was removed from the [`Assets`](crate::Assets) collection. /// from the [`Assets`](crate::Assets) collection.
Unloaded, Unloaded,
} }

View File

@ -7,6 +7,16 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
/// I/O implementation for Android devices.
///
/// Implementation details:
///
/// - `load_path` uses the [AssetManager] to load files.
/// - `read_directory` always returns an empty itrator.
/// - `get_metadata` will probably return an error.
/// - Watching for changes is not supported. The watcher methods will do nothing.
///
/// [AssetManager]: https://developer.android.com/reference/android/content/res/AssetManager
pub struct AndroidAssetIo { pub struct AndroidAssetIo {
root_path: PathBuf, root_path: PathBuf,
} }

View File

@ -21,6 +21,9 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
/// I/O implementation for the local filesystem.
///
/// This asset I/O is fully featured but it's not available on `android` and `wasm` targets.
pub struct FileAssetIo { pub struct FileAssetIo {
root_path: PathBuf, root_path: PathBuf,
#[cfg(feature = "filesystem_watcher")] #[cfg(feature = "filesystem_watcher")]
@ -28,11 +31,15 @@ pub struct FileAssetIo {
} }
impl FileAssetIo { impl FileAssetIo {
/// Creates a new `FileAssetIo` at a path relative to the executable's directory, optionally
/// watching for changes.
///
/// See `get_base_path` below.
pub fn new<P: AsRef<Path>>(path: P, watch_for_changes: bool) -> Self { pub fn new<P: AsRef<Path>>(path: P, watch_for_changes: bool) -> Self {
let file_asset_io = FileAssetIo { let file_asset_io = FileAssetIo {
#[cfg(feature = "filesystem_watcher")] #[cfg(feature = "filesystem_watcher")]
filesystem_watcher: Default::default(), filesystem_watcher: Default::default(),
root_path: Self::get_root_path().join(path.as_ref()), root_path: Self::get_base_path().join(path.as_ref()),
}; };
if watch_for_changes { if watch_for_changes {
#[cfg(any( #[cfg(any(
@ -50,7 +57,12 @@ impl FileAssetIo {
file_asset_io file_asset_io
} }
pub fn get_root_path() -> PathBuf { /// Returns the base path of the assets directory, which is normally the executable's parent
/// directory.
///
/// If the `CARGO_MANIFEST_DIR` environment variable is set, then its value will be used
/// instead. It's set by cargo when running with `cargo run`.
pub fn get_base_path() -> PathBuf {
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
PathBuf::from(manifest_dir) PathBuf::from(manifest_dir)
} else { } else {
@ -64,6 +76,9 @@ impl FileAssetIo {
} }
} }
/// Returns the root directory where assets are loaded from.
///
/// See `get_base_path`.
pub fn root_path(&self) -> &PathBuf { pub fn root_path(&self) -> &PathBuf {
&self.root_path &self.root_path
} }
@ -144,6 +159,7 @@ impl AssetIo for FileAssetIo {
} }
} }
/// Watches for file changes in the local file system.
#[cfg(all( #[cfg(all(
feature = "filesystem_watcher", feature = "filesystem_watcher",
all(not(target_arch = "wasm32"), not(target_os = "android")) all(not(target_arch = "wasm32"), not(target_os = "android"))

View File

@ -4,17 +4,21 @@ use std::convert::{TryFrom, TryInto};
#[non_exhaustive] #[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum FileType { pub enum FileType {
/// A directory.
Directory, Directory,
/// A file.
File, File,
} }
impl FileType { impl FileType {
/// Returns `true` if the entry is a directory.
#[inline] #[inline]
pub const fn is_dir(&self) -> bool { pub const fn is_dir(&self) -> bool {
matches!(self, Self::Directory) matches!(self, Self::Directory)
} }
#[inline] #[inline]
/// Returns `true` if the entry is a file.
pub const fn is_file(&self) -> bool { pub const fn is_file(&self) -> bool {
matches!(self, Self::File) matches!(self, Self::File)
} }
@ -46,20 +50,24 @@ pub struct Metadata {
} }
impl Metadata { impl Metadata {
/// Creates new metadata information.
pub fn new(file_type: FileType) -> Self { pub fn new(file_type: FileType) -> Self {
Self { file_type } Self { file_type }
} }
/// Returns the file type.
#[inline] #[inline]
pub const fn file_type(&self) -> FileType { pub const fn file_type(&self) -> FileType {
self.file_type self.file_type
} }
/// Returns `true` if the entry is a directory.
#[inline] #[inline]
pub const fn is_dir(&self) -> bool { pub const fn is_dir(&self) -> bool {
self.file_type.is_dir() self.file_type.is_dir()
} }
/// Returns `true` if the entry is a file.
#[inline] #[inline]
pub const fn is_file(&self) -> bool { pub const fn is_file(&self) -> bool {
self.file_type.is_file() self.file_type.is_file()

View File

@ -25,28 +25,53 @@ use std::{
}; };
use thiserror::Error; use thiserror::Error;
/// Errors that occur while loading assets /// Errors that occur while loading assets.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AssetIoError { pub enum AssetIoError {
/// Path not found.
#[error("path not found: {0}")] #[error("path not found: {0}")]
NotFound(PathBuf), NotFound(PathBuf),
/// Encountered an I/O error while loading an asset.
#[error("encountered an io error while loading asset: {0}")] #[error("encountered an io error while loading asset: {0}")]
Io(#[from] io::Error), Io(#[from] io::Error),
/// Failed to watch path.
#[error("failed to watch path: {0}")] #[error("failed to watch path: {0}")]
PathWatchError(PathBuf), PathWatchError(PathBuf),
} }
/// Handles load requests from an `AssetServer` /// A storage provider for an [`AssetServer`].
///
/// An asset I/O is the backend actually providing data for the asset loaders managed by the asset
/// server. An average user will probably be just fine with the default [`FileAssetIo`], but you
/// can easily use your own custom I/O to, for example, load assets from cloud storage or create a
/// seamless VFS layout using custom containers.
///
/// See the [`custom_asset_io`] example in the repository for more details.
///
/// [`AssetServer`]: struct.AssetServer.html
/// [`custom_asset_io`]: https://github.com/bevyengine/bevy/tree/latest/examples/asset/custom_asset_io.rs
pub trait AssetIo: Downcast + Send + Sync + 'static { pub trait AssetIo: Downcast + Send + Sync + 'static {
/// Returns a future to load the full file data at the provided path.
fn load_path<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<Vec<u8>, AssetIoError>>; fn load_path<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<Vec<u8>, AssetIoError>>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory( fn read_directory(
&self, &self,
path: &Path, path: &Path,
) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError>; ) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError>;
/// Returns metadata about the filesystem entry at the provided path.
fn get_metadata(&self, path: &Path) -> Result<Metadata, AssetIoError>; fn get_metadata(&self, path: &Path) -> Result<Metadata, AssetIoError>;
/// Tells the asset I/O to watch for changes recursively at the provided path.
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>; fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>;
/// Enables change tracking in this asset I/O.
fn watch_for_changes(&self) -> Result<(), AssetIoError>; fn watch_for_changes(&self) -> Result<(), AssetIoError>;
/// Returns `true` if the path is a directory.
fn is_dir(&self, path: &Path) -> bool { fn is_dir(&self, path: &Path) -> bool {
self.get_metadata(path) self.get_metadata(path)
.as_ref() .as_ref()
@ -54,6 +79,7 @@ pub trait AssetIo: Downcast + Send + Sync + 'static {
.unwrap_or(false) .unwrap_or(false)
} }
/// Returns `true` if the path is a file.
fn is_file(&self, path: &Path) -> bool { fn is_file(&self, path: &Path) -> bool {
self.get_metadata(path) self.get_metadata(path)
.as_ref() .as_ref()

View File

@ -10,6 +10,16 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use web_sys::Response; use web_sys::Response;
/// I/O implementation for web builds.
///
/// Implementation details:
///
/// - `load_path` makes [fetch()] requests.
/// - `read_directory` always returns an empty iterator.
/// - `get_metadata` will always return an error.
/// - Watching for changes is not supported. The watcher methods will do nothing.
///
/// [fetch()]: https://developer.mozilla.org/en-US/docs/Web/API/fetch
pub struct WasmAssetIo { pub struct WasmAssetIo {
root_path: PathBuf, root_path: PathBuf,
} }

View File

@ -1,3 +1,15 @@
//! Built-in plugin for asset support.
//!
//! This plugin allows a bevy app to work with assets from the filesystem (or [another source]),
//! providing an [asset server] for loading and processing [`Asset`]s and storing them in an
//! [asset storage] to be accessed by systems.
//!
//! [another source]: trait.AssetIo.html
//! [asset server]: struct.AssetServer.html
//! [asset storage]: struct.Assets.html
#![warn(missing_docs)]
mod asset_server; mod asset_server;
mod assets; mod assets;
#[cfg(feature = "debug_asset_server")] #[cfg(feature = "debug_asset_server")]
@ -14,6 +26,7 @@ mod io;
mod loader; mod loader;
mod path; mod path;
/// The `bevy_asset` prelude.
pub mod prelude { pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped}; pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped};
@ -31,19 +44,25 @@ pub use path::*;
use bevy_app::{prelude::Plugin, App}; use bevy_app::{prelude::Plugin, App};
use bevy_ecs::schedule::{StageLabel, SystemStage}; use bevy_ecs::schedule::{StageLabel, SystemStage};
/// The names of asset stages in an App Schedule /// The names of asset stages in an [`App`] schedule.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum AssetStage { pub enum AssetStage {
/// The stage where asset storages are updated.
LoadAssets, LoadAssets,
/// The stage where asset events are generated.
AssetEvents, AssetEvents,
} }
/// Adds support for Assets to an App. Assets are typed collections with change tracking, which are /// Adds support for Assets to an App.
/// added as App Resources. Examples of assets: textures, sounds, 3d models, maps, scenes ///
/// Assets are typed collections with change tracking, which are added as App Resources. Examples of
/// assets: textures, sounds, 3d models, maps, scenes
#[derive(Default)] #[derive(Default)]
pub struct AssetPlugin; pub struct AssetPlugin;
/// [`AssetServer`] settings.
pub struct AssetServerSettings { pub struct AssetServerSettings {
/// The base folder where assets are loaded from, relative to the executable.
pub asset_folder: String, pub asset_folder: String,
/// Whether to watch for changes in asset files. Requires the `filesystem_watcher` feature, /// Whether to watch for changes in asset files. Requires the `filesystem_watcher` feature,
/// and cannot be supported on the wasm32 arch nor android os. /// and cannot be supported on the wasm32 arch nor android os.
@ -59,7 +78,7 @@ impl Default for AssetServerSettings {
} }
} }
/// Create an instance of the platform default `AssetIo` /// Creates an instance of the platform's default `AssetIo`.
/// ///
/// This is useful when providing a custom `AssetIo` instance that needs to /// This is useful when providing a custom `AssetIo` instance that needs to
/// delegate to the default `AssetIo` for the platform. /// delegate to the default `AssetIo` for the platform.

View File

@ -10,18 +10,45 @@ use crossbeam_channel::{Receiver, Sender};
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use std::path::Path; use std::path::Path;
/// A loader for an asset source /// A loader for an asset source.
///
/// Types implementing this trait are used by the asset server to load assets into their respective
/// asset storages.
pub trait AssetLoader: Send + Sync + 'static { pub trait AssetLoader: Send + Sync + 'static {
/// Processes the asset in an asynchronous closure.
fn load<'a>( fn load<'a>(
&'a self, &'a self,
bytes: &'a [u8], bytes: &'a [u8],
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<(), anyhow::Error>>; ) -> BoxedFuture<'a, Result<(), anyhow::Error>>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str]; fn extensions(&self) -> &[&str];
} }
/// An essential piece of data of an application.
///
/// Assets are the building blocks of games. They can be anything, from images and sounds to scenes
/// and scripts. In Bevy, an asset is any struct that has an unique type id, as shown below:
///
/// ```rust
/// use bevy_reflect::TypeUuid;
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize, TypeUuid)]
/// #[uuid = "39cadc56-aa9c-4543-8640-a018b74b5052"]
/// pub struct CustomAsset {
/// pub value: i32,
/// }
/// ```
///
/// See the `assets/custom_asset.rs` example in the repository for more details.
///
/// In order to load assets into your game you must either add them manually to an asset storage
/// with [`Assets::add`] or load them from the filesystem with [`AssetServer::load`].
pub trait Asset: TypeUuid + AssetDynamic {} pub trait Asset: TypeUuid + AssetDynamic {}
/// An untyped version of the [`Asset`] trait.
pub trait AssetDynamic: Downcast + TypeUuidDynamic + Send + Sync + 'static {} pub trait AssetDynamic: Downcast + TypeUuidDynamic + Send + Sync + 'static {}
impl_downcast!(AssetDynamic); impl_downcast!(AssetDynamic);
@ -29,12 +56,14 @@ impl<T> Asset for T where T: TypeUuid + AssetDynamic + TypeUuidDynamic {}
impl<T> AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {} impl<T> AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {}
/// A complete asset processed in an [`AssetLoader`].
pub struct LoadedAsset<T: Asset> { pub struct LoadedAsset<T: Asset> {
pub(crate) value: Option<T>, pub(crate) value: Option<T>,
pub(crate) dependencies: Vec<AssetPath<'static>>, pub(crate) dependencies: Vec<AssetPath<'static>>,
} }
impl<T: Asset> LoadedAsset<T> { impl<T: Asset> LoadedAsset<T> {
/// Creates a new loaded asset.
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
Self { Self {
value: Some(value), value: Some(value),
@ -42,16 +71,19 @@ impl<T: Asset> LoadedAsset<T> {
} }
} }
/// Adds a dependency on another asset at the provided path.
pub fn add_dependency(&mut self, asset_path: AssetPath) { pub fn add_dependency(&mut self, asset_path: AssetPath) {
self.dependencies.push(asset_path.to_owned()); self.dependencies.push(asset_path.to_owned());
} }
/// Adds a dependency on another asset at the provided path.
#[must_use] #[must_use]
pub fn with_dependency(mut self, asset_path: AssetPath) -> Self { pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
self.add_dependency(asset_path); self.add_dependency(asset_path);
self self
} }
/// Adds dependencies on other assets at the provided paths.
#[must_use] #[must_use]
pub fn with_dependencies(mut self, mut asset_paths: Vec<AssetPath<'static>>) -> Self { pub fn with_dependencies(mut self, mut asset_paths: Vec<AssetPath<'static>>) -> Self {
for asset_path in asset_paths.drain(..) { for asset_path in asset_paths.drain(..) {
@ -77,6 +109,15 @@ impl<T: Asset> From<LoadedAsset<T>> for BoxedLoadedAsset {
} }
} }
/// An asynchronous context where an [`Asset`] is processed.
///
/// The load context is created by the [`AssetServer`] to process an asset source after loading its
/// contents into memory. It is then passed to the appropriate [`AssetLoader`] based on the file
/// extension of the asset's path.
///
/// An asset source can define one or more assets from a single source path. The main asset is set
/// using [`LoadContext::set_default_asset`] and sub-assets are defined with
/// [`LoadContext::set_labeled_asset`].
pub struct LoadContext<'a> { pub struct LoadContext<'a> {
pub(crate) ref_change_channel: &'a RefChangeChannel, pub(crate) ref_change_channel: &'a RefChangeChannel,
pub(crate) asset_io: &'a dyn AssetIo, pub(crate) asset_io: &'a dyn AssetIo,
@ -101,18 +142,22 @@ impl<'a> LoadContext<'a> {
} }
} }
/// Gets the source path for this load context.
pub fn path(&self) -> &Path { pub fn path(&self) -> &Path {
self.path self.path
} }
/// Returns `true` if the load context contains an asset with the specified label.
pub fn has_labeled_asset(&self, label: &str) -> bool { pub fn has_labeled_asset(&self, label: &str) -> bool {
self.labeled_assets.contains_key(&Some(label.to_string())) self.labeled_assets.contains_key(&Some(label.to_string()))
} }
/// Sets the primary asset loaded from the asset source.
pub fn set_default_asset<T: Asset>(&mut self, asset: LoadedAsset<T>) { pub fn set_default_asset<T: Asset>(&mut self, asset: LoadedAsset<T>) {
self.labeled_assets.insert(None, asset.into()); self.labeled_assets.insert(None, asset.into());
} }
/// Sets a secondary asset loaded from the asset source.
pub fn set_labeled_asset<T: Asset>(&mut self, label: &str, asset: LoadedAsset<T>) -> Handle<T> { pub fn set_labeled_asset<T: Asset>(&mut self, label: &str, asset: LoadedAsset<T>) -> Handle<T> {
assert!(!label.is_empty()); assert!(!label.is_empty());
self.labeled_assets self.labeled_assets
@ -120,14 +165,18 @@ impl<'a> LoadContext<'a> {
self.get_handle(AssetPath::new_ref(self.path(), Some(label))) self.get_handle(AssetPath::new_ref(self.path(), Some(label)))
} }
/// Gets a handle to an asset of type `T` from its id.
pub fn get_handle<I: Into<HandleId>, T: Asset>(&self, id: I) -> Handle<T> { pub fn get_handle<I: Into<HandleId>, T: Asset>(&self, id: I) -> Handle<T> {
Handle::strong(id.into(), self.ref_change_channel.sender.clone()) Handle::strong(id.into(), self.ref_change_channel.sender.clone())
} }
/// Reads the contents of the file at the specified path through the [`AssetIo`] associated
/// with this context.
pub async fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> { pub async fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
self.asset_io.load_path(path.as_ref()).await self.asset_io.load_path(path.as_ref()).await
} }
/// Generates metadata for the assets managed by this load context.
pub fn get_asset_metas(&self) -> Vec<AssetMeta> { pub fn get_asset_metas(&self) -> Vec<AssetMeta> {
let mut asset_metas = Vec::new(); let mut asset_metas = Vec::new();
for (label, asset) in &self.labeled_assets { for (label, asset) in &self.labeled_assets {
@ -140,33 +189,45 @@ impl<'a> LoadContext<'a> {
asset_metas asset_metas
} }
/// Gets the asset I/O associated with this load context.
pub fn asset_io(&self) -> &dyn AssetIo { pub fn asset_io(&self) -> &dyn AssetIo {
self.asset_io self.asset_io
} }
} }
/// The result of loading an asset of type `T` /// The result of loading an asset of type `T`.
#[derive(Debug)] #[derive(Debug)]
pub struct AssetResult<T> { pub struct AssetResult<T> {
/// The asset itself.
pub asset: Box<T>, pub asset: Box<T>,
/// The unique id of the asset.
pub id: HandleId, pub id: HandleId,
/// Change version.
pub version: usize, pub version: usize,
} }
/// A channel to send and receive [`AssetResult`]s /// An event channel used by asset server to update the asset storage of a `T` asset.
#[derive(Debug)] #[derive(Debug)]
pub struct AssetLifecycleChannel<T> { pub struct AssetLifecycleChannel<T> {
/// The sender endpoint of the channel.
pub sender: Sender<AssetLifecycleEvent<T>>, pub sender: Sender<AssetLifecycleEvent<T>>,
/// The receiver endpoint of the channel.
pub receiver: Receiver<AssetLifecycleEvent<T>>, pub receiver: Receiver<AssetLifecycleEvent<T>>,
} }
/// Events for the [`AssetLifecycleChannel`].
pub enum AssetLifecycleEvent<T> { pub enum AssetLifecycleEvent<T> {
/// An asset was created.
Create(AssetResult<T>), Create(AssetResult<T>),
/// An asset was freed.
Free(HandleId), Free(HandleId),
} }
/// A trait for sending lifecycle notifications from assets in the asset server.
pub trait AssetLifecycle: Downcast + Send + Sync + 'static { pub trait AssetLifecycle: Downcast + Send + Sync + 'static {
/// Notifies the asset server that a new asset was created.
fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize); fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize);
/// Notifies the asset server that an asset was freed.
fn free_asset(&self, id: HandleId); fn free_asset(&self, id: HandleId);
} }
impl_downcast!(AssetLifecycle); impl_downcast!(AssetLifecycle);

View File

@ -7,6 +7,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
/// Represents a path to an asset in the file system.
#[derive(Debug, Hash, Clone, Serialize, Deserialize)] #[derive(Debug, Hash, Clone, Serialize, Deserialize)]
pub struct AssetPath<'a> { pub struct AssetPath<'a> {
path: Cow<'a, Path>, path: Cow<'a, Path>,
@ -14,6 +15,7 @@ pub struct AssetPath<'a> {
} }
impl<'a> AssetPath<'a> { impl<'a> AssetPath<'a> {
/// Creates a new asset path using borrowed information.
#[inline] #[inline]
pub fn new_ref(path: &'a Path, label: Option<&'a str>) -> AssetPath<'a> { pub fn new_ref(path: &'a Path, label: Option<&'a str>) -> AssetPath<'a> {
AssetPath { AssetPath {
@ -22,6 +24,7 @@ impl<'a> AssetPath<'a> {
} }
} }
/// Creates a new asset path.
#[inline] #[inline]
pub fn new(path: PathBuf, label: Option<String>) -> AssetPath<'a> { pub fn new(path: PathBuf, label: Option<String>) -> AssetPath<'a> {
AssetPath { AssetPath {
@ -30,21 +33,25 @@ impl<'a> AssetPath<'a> {
} }
} }
/// Constructs an identifier from this asset path.
#[inline] #[inline]
pub fn get_id(&self) -> AssetPathId { pub fn get_id(&self) -> AssetPathId {
AssetPathId::from(self) AssetPathId::from(self)
} }
/// Gets the sub-asset label.
#[inline] #[inline]
pub fn label(&self) -> Option<&str> { pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(|label| label.as_ref()) self.label.as_ref().map(|label| label.as_ref())
} }
/// Gets the path to the asset in the filesystem.
#[inline] #[inline]
pub fn path(&self) -> &Path { pub fn path(&self) -> &Path {
&self.path &self.path
} }
/// Converts the borrowed path data to owned.
#[inline] #[inline]
pub fn to_owned(&self) -> AssetPath<'static> { pub fn to_owned(&self) -> AssetPath<'static> {
AssetPath { AssetPath {
@ -57,18 +64,21 @@ impl<'a> AssetPath<'a> {
} }
} }
/// An unique identifier to an asset path.
#[derive( #[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect,
)] )]
#[reflect_value(PartialEq, Hash, Serialize, Deserialize)] #[reflect_value(PartialEq, Hash, Serialize, Deserialize)]
pub struct AssetPathId(SourcePathId, LabelId); pub struct AssetPathId(SourcePathId, LabelId);
/// An unique identifier to the source path of an asset.
#[derive( #[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect,
)] )]
#[reflect_value(PartialEq, Hash, Serialize, Deserialize)] #[reflect_value(PartialEq, Hash, Serialize, Deserialize)]
pub struct SourcePathId(u64); pub struct SourcePathId(u64);
/// An unique identifier to a sub-asset label.
#[derive( #[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect,
)] )]
@ -104,10 +114,12 @@ impl<'a> From<Option<&'a str>> for LabelId {
} }
impl AssetPathId { impl AssetPathId {
/// Gets the id of the source path.
pub fn source_path_id(&self) -> SourcePathId { pub fn source_path_id(&self) -> SourcePathId {
self.0 self.0
} }
/// Gets the id of the sub-asset label.
pub fn label_id(&self) -> LabelId { pub fn label_id(&self) -> LabelId {
self.1 self.1
} }