diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index e9e8576352346..249d7c0f6f437 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -14,17 +14,31 @@ use parking_lot::{Mutex, RwLock}; use std::{path::Path, sync::Arc}; use thiserror::Error; -/// Errors that occur while loading assets with an `AssetServer` +/// Errors that occur while loading assets with an `AssetServer`. #[derive(Error, Debug)] pub enum AssetServerError { + /// Asset folder is not a directory. #[error("asset folder path is not a directory: {0}")] AssetFolderNotADirectory(String), + + /// No asset loader was found for the specified extensions. #[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))] - MissingAssetLoader { extensions: Vec }, + 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, + }, + + /// 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")] IncorrectHandleType, + + /// Encountered an error while processing an asset. #[error("encountered an error while loading an asset: {0}")] AssetLoaderError(anyhow::Error), + + /// Encountered an error while reading an asset from disk. #[error("encountered an error while reading an asset: {0}")] AssetIoError(#[from] AssetIoError), } @@ -48,6 +62,9 @@ pub(crate) struct AssetRefCounter { pub(crate) mark_unused_assets: Arc>>, } +/// Internal data for the asset server. +/// +/// [`AssetServer`] is the public API for interacting with the asset server. pub struct AssetServerInternal { pub(crate) asset_io: Box, pub(crate) asset_ref_counter: AssetRefCounter, @@ -58,17 +75,45 @@ pub struct AssetServerInternal { handle_to_path: Arc>>>, } -/// 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) +/// { +/// // Now you can do whatever you want with the asset server, such as loading an asset: +/// let asset_handle: Handle = 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)] pub struct AssetServer { pub(crate) server: Arc, } impl AssetServer { + /// Creates a new asset server with the provided asset I/O. pub fn new(source_io: T) -> Self { 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) -> Self { AssetServer { server: Arc::new(AssetServerInternal { @@ -83,6 +128,7 @@ impl AssetServer { } } + /// Returns the associated asset I/O. pub fn asset_io(&self) -> &dyn AssetIo { &*self.server.asset_io } @@ -104,6 +150,10 @@ impl AssetServer { 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(&self, loader: T) where T: AssetLoader, @@ -126,11 +176,13 @@ impl AssetServer { Ok(()) } + /// Gets a strong handle for an asset with the provided id. pub fn get_handle>(&self, id: I) -> Handle { let sender = self.server.asset_ref_counter.channel.sender.clone(); Handle::strong(id.into(), sender) } + /// Gets an untyped strong handle for an asset with the provided id. pub fn get_handle_untyped>(&self, id: I) -> HandleUntyped { let sender = self.server.asset_ref_counter.channel.sender.clone(); 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>(&self, handle: H) -> Option> { self.server .handle_to_path @@ -187,6 +240,7 @@ impl AssetServer { .cloned() } + /// Gets the load state of an asset from the provided handle. pub fn get_load_state>(&self, handle: H) -> LoadState { match handle.into() { 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) -> LoadState { let mut load_state = LoadState::Loaded; for handle_id in handles { @@ -219,11 +277,14 @@ impl AssetServer { 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 /// (see ) /// 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 /// 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"] pub fn load<'a, T: Asset, P: Into>>(&self, path: P) -> Handle { self.load_untyped(path).typed() @@ -365,6 +429,9 @@ impl AssetServer { 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"] pub fn load_untyped<'a, P: Into>>(&self, path: P) -> HandleUntyped { let handle_id = self.load_untracked(path.into(), false); @@ -400,6 +467,14 @@ impl AssetServer { 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"] pub fn load_folder>( &self, @@ -429,6 +504,7 @@ impl AssetServer { Ok(handles) } + /// Frees unused assets, unloading them from memory. pub fn free_unused_assets(&self) { 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) { let receiver = &self.server.asset_ref_counter.channel.receiver; 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(); } +/// A system for freeing assets that have no active handles. pub fn free_unused_assets_system(asset_server: Res) { free_unused_assets_system_impl(&asset_server); } diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 23ec2cf3dee66..9ba30143dc964 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -12,12 +12,16 @@ use bevy_utils::HashMap; use crossbeam_channel::Sender; 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 { + #[allow(missing_docs)] Created { handle: Handle }, + #[allow(missing_docs)] Modified { handle: Handle }, + #[allow(missing_docs)] Removed { handle: Handle }, } @@ -81,6 +85,7 @@ impl Assets { /// Adds an asset to the collection, returning a Strong handle to that asset. /// /// # Events + /// /// * [`AssetEvent::Created`] pub fn add(&mut self, asset: T) -> Handle { let id = HandleId::random::(); @@ -110,8 +115,9 @@ impl Assets { /// new asset will be inserted. /// /// # 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>(&mut self, handle: H, asset: T) { let id: HandleId = handle.into(); if self.assets.insert(id, asset).is_some() { @@ -125,7 +131,7 @@ impl Assets { } } - /// 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 /// mutable access to the asset, use [`get_mut`](Assets::get_mut). @@ -150,15 +156,16 @@ impl Assets { 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>(&self, handle: H) -> Handle { 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 - /// * [`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>( &mut self, handle: H, @@ -179,12 +186,12 @@ impl Assets { 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 { 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 { self.assets.iter_mut().map(|(k, v)| { self.events.send(AssetEvent::Modified { @@ -194,16 +201,17 @@ impl Assets { }) } - /// 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 + '_ { 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`. /// /// # Events + /// /// * [`AssetEvent::Removed`] pub fn remove>(&mut self, handle: H) -> Option { let id: HandleId = handle.into(); @@ -238,6 +246,8 @@ impl Assets { 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( mut events: EventWriter>, mut assets: ResMut>, @@ -249,40 +259,60 @@ impl Assets { } } - /// Gets the number of assets in the collection + /// Gets the number of assets in the collection. pub fn len(&self) -> usize { 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 { self.assets.is_empty() } } -/// [App] extension methods for adding new asset types +/// [`App`] extension methods for adding new asset types. 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(&mut self) -> &mut Self where 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(&mut self) -> &mut Self where 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(&mut self) -> &mut Self where 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(&mut self) -> &mut Self where T: AssetLoader + FromWorld; + + /// Adds the provided asset loader to the application. fn add_asset_loader(&mut self, loader: T) -> &mut Self where T: AssetLoader; } 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(&mut self) -> &mut Self where 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")] #[macro_export] 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"))] #[macro_export] macro_rules! load_internal_asset { diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index b7e3c71c85c89..cdb4cc0010efc 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -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_ecs::{ event::Events, @@ -16,8 +20,7 @@ use crate::{ HandleUntyped, }; -/// A "debug asset app", whose sole responsibility is hot reloading assets that are -/// "internal" / compiled-in to Bevy Plugins. +/// A helper [`App`] used for hot reloading internal assets, which are compiled-in to Bevy plugins. pub struct DebugAssetApp(App); 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)] pub struct DebugAssetAppRun; /// Facilitates the creation of a "debug asset app", whose sole responsibility is hot reloading /// 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. #[derive(Default)] 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 { + /// The collection of asset handles. pub handles: HashMap, Handle>, } @@ -106,6 +115,7 @@ pub(crate) fn sync_debug_assets( /// 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`. +/// /// If this feels a bit odd ... thats because it is. This was built to improve the UX of the /// `load_internal_asset` macro. pub fn register_handle_with_loader( diff --git a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs index d5f64c8a455e8..470f3503e15cf 100644 --- a/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs +++ b/crates/bevy_asset/src/diagnostic/asset_count_diagnostics_plugin.rs @@ -3,7 +3,7 @@ use bevy_app::prelude::*; use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics, MAX_DIAGNOSTIC_NAME_WIDTH}; 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 { marker: std::marker::PhantomData, } @@ -24,10 +24,14 @@ impl Plugin for AssetCountDiagnosticsPlugin { } impl AssetCountDiagnosticsPlugin { + /// Gets unique id of this diagnostic. + /// + /// The diagnostic id is the type uuid of `T`. pub fn diagnostic_id() -> DiagnosticId { DiagnosticId(T::TYPE_UUID) } + /// Registers the asset count diagnostic for the current application. pub fn setup_system(mut diagnostics: ResMut) { let asset_type_name = std::any::type_name::(); let max_length = MAX_DIAGNOSTIC_NAME_WIDTH - "asset_count ".len(); @@ -47,6 +51,7 @@ impl AssetCountDiagnosticsPlugin { )); } + /// Updates the asset count of `T` assets. pub fn diagnostic_system(mut diagnostics: ResMut, assets: Res>) { diagnostics.add_measurement(Self::diagnostic_id(), || assets.len() as f64); } diff --git a/crates/bevy_asset/src/diagnostic/mod.rs b/crates/bevy_asset/src/diagnostic/mod.rs index f093527f9755a..6bc1ed390c881 100644 --- a/crates/bevy_asset/src/diagnostic/mod.rs +++ b/crates/bevy_asset/src/diagnostic/mod.rs @@ -1,2 +1,4 @@ +//! Diagnostic providers for `bevy_diagnostic`. + mod asset_count_diagnostics_plugin; pub use asset_count_diagnostics_plugin::AssetCountDiagnosticsPlugin; diff --git a/crates/bevy_asset/src/filesystem_watcher.rs b/crates/bevy_asset/src/filesystem_watcher.rs index 19b131780a078..5ee499e80e3ed 100644 --- a/crates/bevy_asset/src/filesystem_watcher.rs +++ b/crates/bevy_asset/src/filesystem_watcher.rs @@ -2,8 +2,10 @@ use crossbeam_channel::Receiver; use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher}; use std::path::Path; -/// Watches for changes to assets on the filesystem. This is used by the `AssetServer` to reload -/// them +/// Watches for changes to files on the local filesystem. +/// +/// When hot-reloading is enabled, the [`AssetServer`](crate::AssetServer) uses this to reload +/// assets when their source files are modified. pub struct FilesystemWatcher { pub watcher: RecommendedWatcher, pub receiver: Receiver>, @@ -21,6 +23,7 @@ impl Default for FilesystemWatcher { } impl FilesystemWatcher { + /// Watch for changes recursively at the provided path. pub fn watch>(&mut self, path: P) -> Result<()> { self.watcher.watch(path.as_ref(), RecursiveMode::Recursive) } diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index d519f1acfff66..d5296b389da02 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -15,7 +15,7 @@ use bevy_utils::Uuid; use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; -/// A unique, stable asset id +/// A unique, stable asset id. #[derive( Debug, Clone, @@ -32,7 +32,10 @@ use serde::{Deserialize, Serialize}; )] #[reflect_value(Serialize, Deserialize, PartialEq, Hash)] pub enum HandleId { + /// A handle id of a loaded asset. Id(Uuid, u64), + + /// A handle id of a pending asset. AssetPathId(AssetPathId), } @@ -49,32 +52,34 @@ impl<'a> From> for HandleId { } impl HandleId { + /// Creates a random id for an asset of type `T`. #[inline] pub fn random() -> Self { HandleId::Id(T::TYPE_UUID, rand::random()) } + /// Creates the default id for an asset of type `T`. #[inline] pub fn default() -> Self { HandleId::Id(T::TYPE_UUID, 0) } + /// Creates an arbitrary asset id without an explicit type bound. #[inline] pub const fn new(type_uuid: Uuid, id: u64) -> Self { 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) -/// collection. +/// Handles contain a unique id that corresponds to a specific asset in the [`Assets`] collection. /// /// # Accessing the Asset /// /// 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 -/// [`Assets::get`](crate::Assets::get) or [`Assets::get_mut`](crate::Assets::get_mut). +/// [`Assets::get`] or [`Assets::get_mut`]. /// /// # Strong and Weak /// @@ -100,7 +105,7 @@ pub struct Handle where 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, #[reflect(ignore)] handle_type: HandleType, @@ -136,6 +141,7 @@ impl Handle { } } + /// Creates a weak handle into an Asset identified by `id`. #[inline] pub fn weak(id: HandleId) -> Self { Self { @@ -145,7 +151,7 @@ impl Handle { } } - /// 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(&self) -> Handle { Handle { id: self.id, @@ -154,17 +160,19 @@ impl Handle { } } + /// Returns `true` if this is a weak handle. pub fn is_weak(&self) -> bool { matches!(self.handle_type, HandleType::Weak) } + /// Returns `true` if this is a strong handle. pub fn is_strong(&self) -> bool { matches!(self.handle_type, HandleType::Strong(_)) } /// 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) { if self.is_strong() { return; @@ -174,12 +182,14 @@ impl Handle { self.handle_type = HandleType::Strong(sender); } + /// Creates a weak copy of this handle. #[inline] #[must_use] pub fn clone_weak(&self) -> Self { Self::weak(self.id) } + /// Creates an untyped copy of this handle. pub fn clone_untyped(&self) -> HandleUntyped { match &self.handle_type { HandleType::Strong(sender) => HandleUntyped::strong(self.id, sender.clone()), @@ -187,6 +197,7 @@ impl Handle { } } + /// Creates a weak, untyped copy of this handle. pub fn clone_weak_untyped(&self) -> HandleUntyped { HandleUntyped::weak(self.id) } @@ -289,7 +300,7 @@ impl Clone for Handle { } } -/// 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` and /// `Handle` in the same `HashSet`. @@ -297,11 +308,13 @@ impl Clone for Handle { /// To convert back to a typed handle, use the [typed](HandleUntyped::typed) method. #[derive(Debug)] pub struct HandleUntyped { + /// An unique identifier to an Asset. pub id: HandleId, handle_type: HandleType, } impl HandleUntyped { + /// Creates a weak untyped handle with an arbitrary id. pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self { Self { 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 { Self { id, @@ -324,15 +338,18 @@ impl HandleUntyped { } } + /// Creates a weak copy of this handle. #[must_use] pub fn clone_weak(&self) -> Self { Self::weak(self.id) } + /// Returns `true` if this is a weak handle. pub fn is_weak(&self) -> bool { matches!(self.handle_type, HandleType::Weak) } + /// Returns `true` if this is a strong handle. pub fn is_strong(&self) -> bool { matches!(self.handle_type, HandleType::Strong(_)) } @@ -345,9 +362,13 @@ impl HandleUntyped { 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. + /// + /// # Panics + /// + /// Will panic if type `T` doesn't match this handle's actual asset type. pub fn typed(mut self) -> Handle { if let HandleId::Id(type_uuid, _) = self.id { assert!( diff --git a/crates/bevy_asset/src/info.rs b/crates/bevy_asset/src/info.rs index 6005e92def189..fc757b70e7eb4 100644 --- a/crates/bevy_asset/src/info.rs +++ b/crates/bevy_asset/src/info.rs @@ -3,53 +3,67 @@ use bevy_utils::{HashMap, HashSet, Uuid}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; +/// Metadata for an asset source. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SourceMeta { + /// A collection of asset metadata. pub assets: Vec, } +/// Metadata for an asset. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AssetMeta { + /// Asset label. pub label: Option, + /// Asset dependencies. pub dependencies: Vec>, + /// An unique identifier for an asset type. 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)] pub struct SourceInfo { + /// Metadata for the source. pub meta: Option, + /// The path of the source. pub path: PathBuf, + /// A map of assets and their type identifiers. pub asset_types: HashMap, + /// The load state of the source. pub load_state: LoadState, + /// A collection to track which assets were sent to their asset storages. pub committed_assets: HashSet, + /// Current version of the source. pub version: usize, } impl SourceInfo { + /// Returns `true` if all assets tracked by the source were loaded into their asset storages. pub fn is_loaded(&self) -> bool { self.meta.as_ref().map_or(false, |meta| { 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 { 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)] pub enum LoadState { - /// The asset has not be loaded. + /// The asset has not been loaded. NotLoaded, - /// The asset in the the process of loading. + /// The asset is in the process of 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, /// The asset failed to load. Failed, - /// The asset was previously loaded, however all handles were dropped and - /// the asset was removed from the [`Assets`](crate::Assets) collection. + /// The asset was previously loaded, however all handles were dropped and the asset was removed + /// from the [`Assets`](crate::Assets) collection. Unloaded, } diff --git a/crates/bevy_asset/src/io/android_asset_io.rs b/crates/bevy_asset/src/io/android_asset_io.rs index fc049b77a91a6..bbdf94d6a9c80 100644 --- a/crates/bevy_asset/src/io/android_asset_io.rs +++ b/crates/bevy_asset/src/io/android_asset_io.rs @@ -7,6 +7,16 @@ use std::{ 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 { root_path: PathBuf, } diff --git a/crates/bevy_asset/src/io/file_asset_io.rs b/crates/bevy_asset/src/io/file_asset_io.rs index 14dcf0a153a92..59447dfac5031 100644 --- a/crates/bevy_asset/src/io/file_asset_io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -21,6 +21,9 @@ use std::{ 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 { root_path: PathBuf, #[cfg(feature = "filesystem_watcher")] @@ -28,11 +31,15 @@ pub struct 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>(path: P, watch_for_changes: bool) -> Self { let file_asset_io = FileAssetIo { #[cfg(feature = "filesystem_watcher")] 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 { #[cfg(any( @@ -50,7 +57,12 @@ impl FileAssetIo { 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") { PathBuf::from(manifest_dir) } 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 { &self.root_path } @@ -144,6 +159,7 @@ impl AssetIo for FileAssetIo { } } +/// Watches for file changes in the local file system. #[cfg(all( feature = "filesystem_watcher", all(not(target_arch = "wasm32"), not(target_os = "android")) diff --git a/crates/bevy_asset/src/io/metadata.rs b/crates/bevy_asset/src/io/metadata.rs index 67158a1ccfd1c..466c30590b702 100644 --- a/crates/bevy_asset/src/io/metadata.rs +++ b/crates/bevy_asset/src/io/metadata.rs @@ -4,17 +4,21 @@ use std::convert::{TryFrom, TryInto}; #[non_exhaustive] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum FileType { + /// A directory. Directory, + /// A file. File, } impl FileType { + /// Returns `true` if the entry is a directory. #[inline] pub const fn is_dir(&self) -> bool { matches!(self, Self::Directory) } #[inline] + /// Returns `true` if the entry is a file. pub const fn is_file(&self) -> bool { matches!(self, Self::File) } @@ -46,20 +50,24 @@ pub struct Metadata { } impl Metadata { + /// Creates new metadata information. pub fn new(file_type: FileType) -> Self { Self { file_type } } + /// Returns the file type. #[inline] pub const fn file_type(&self) -> FileType { self.file_type } + /// Returns `true` if the entry is a directory. #[inline] pub const fn is_dir(&self) -> bool { self.file_type.is_dir() } + /// Returns `true` if the entry is a file. #[inline] pub const fn is_file(&self) -> bool { self.file_type.is_file() diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index e3d3e24047703..7efbd7ca8882f 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -25,28 +25,53 @@ use std::{ }; use thiserror::Error; -/// Errors that occur while loading assets +/// Errors that occur while loading assets. #[derive(Error, Debug)] pub enum AssetIoError { + /// Path not found. #[error("path not found: {0}")] NotFound(PathBuf), + + /// Encountered an I/O error while loading an asset. #[error("encountered an io error while loading asset: {0}")] Io(#[from] io::Error), + + /// Failed to watch path. #[error("failed to watch path: {0}")] 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 { + /// 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, AssetIoError>>; + + /// Returns an iterator of directory entry names at the provided path. fn read_directory( &self, path: &Path, ) -> Result>, AssetIoError>; + + /// Returns metadata about the filesystem entry at the provided path. fn get_metadata(&self, path: &Path) -> Result; + + /// Tells the asset I/O to watch for changes recursively at the provided path. 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>; + /// Returns `true` if the path is a directory. fn is_dir(&self, path: &Path) -> bool { self.get_metadata(path) .as_ref() @@ -54,6 +79,7 @@ pub trait AssetIo: Downcast + Send + Sync + 'static { .unwrap_or(false) } + /// Returns `true` if the path is a file. fn is_file(&self, path: &Path) -> bool { self.get_metadata(path) .as_ref() diff --git a/crates/bevy_asset/src/io/wasm_asset_io.rs b/crates/bevy_asset/src/io/wasm_asset_io.rs index 4e0d812738d6f..91fe067128c68 100644 --- a/crates/bevy_asset/src/io/wasm_asset_io.rs +++ b/crates/bevy_asset/src/io/wasm_asset_io.rs @@ -10,6 +10,16 @@ use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; 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 { root_path: PathBuf, } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index b5ba1a1854d02..f70bc5c1f4fd4 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -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 assets; #[cfg(feature = "debug_asset_server")] @@ -14,6 +26,7 @@ mod io; mod loader; mod path; +/// The `bevy_asset` prelude. pub mod prelude { #[doc(hidden)] pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped}; @@ -31,19 +44,25 @@ pub use path::*; use bevy_app::{prelude::Plugin, App}; 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)] pub enum AssetStage { + /// The stage where asset storages are updated. LoadAssets, + /// The stage where asset events are generated. AssetEvents, } -/// Adds support for Assets to an App. Assets are typed collections with change tracking, which are -/// added as App Resources. Examples of assets: textures, sounds, 3d models, maps, scenes +/// Adds support for Assets to an App. +/// +/// Assets are typed collections with change tracking, which are added as App Resources. Examples of +/// assets: textures, sounds, 3d models, maps, scenes #[derive(Default)] pub struct AssetPlugin; +/// [`AssetServer`] settings. pub struct AssetServerSettings { + /// The base folder where assets are loaded from, relative to the executable. pub asset_folder: String, /// Whether to watch for changes in asset files. Requires the `filesystem_watcher` feature, /// 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 /// delegate to the default `AssetIo` for the platform. diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 5d6b87d8388ba..3d420e8f83429 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -10,18 +10,45 @@ use crossbeam_channel::{Receiver, Sender}; use downcast_rs::{impl_downcast, Downcast}; 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 { + /// Processes the asset in an asynchronous closure. fn load<'a>( &'a self, bytes: &'a [u8], load_context: &'a mut LoadContext, ) -> BoxedFuture<'a, Result<(), anyhow::Error>>; + + /// Returns a list of extensions supported by this asset loader, without the preceding dot. 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 {} +/// An untyped version of the [`Asset`] trait. pub trait AssetDynamic: Downcast + TypeUuidDynamic + Send + Sync + 'static {} impl_downcast!(AssetDynamic); @@ -29,12 +56,14 @@ impl Asset for T where T: TypeUuid + AssetDynamic + TypeUuidDynamic {} impl AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {} +/// A complete asset processed in an [`AssetLoader`]. pub struct LoadedAsset { pub(crate) value: Option, pub(crate) dependencies: Vec>, } impl LoadedAsset { + /// Creates a new loaded asset. pub fn new(value: T) -> Self { Self { value: Some(value), @@ -42,16 +71,19 @@ impl LoadedAsset { } } + /// Adds a dependency on another asset at the provided path. pub fn add_dependency(&mut self, asset_path: AssetPath) { self.dependencies.push(asset_path.to_owned()); } + /// Adds a dependency on another asset at the provided path. #[must_use] pub fn with_dependency(mut self, asset_path: AssetPath) -> Self { self.add_dependency(asset_path); self } + /// Adds dependencies on other assets at the provided paths. #[must_use] pub fn with_dependencies(mut self, mut asset_paths: Vec>) -> Self { for asset_path in asset_paths.drain(..) { @@ -77,6 +109,15 @@ impl From> 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(crate) ref_change_channel: &'a RefChangeChannel, 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 { self.path } + /// Returns `true` if the load context contains an asset with the specified label. pub fn has_labeled_asset(&self, label: &str) -> bool { self.labeled_assets.contains_key(&Some(label.to_string())) } + /// Sets the primary asset loaded from the asset source. pub fn set_default_asset(&mut self, asset: LoadedAsset) { self.labeled_assets.insert(None, asset.into()); } + /// Sets a secondary asset loaded from the asset source. pub fn set_labeled_asset(&mut self, label: &str, asset: LoadedAsset) -> Handle { assert!(!label.is_empty()); self.labeled_assets @@ -120,14 +165,18 @@ impl<'a> LoadContext<'a> { 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, T: Asset>(&self, id: I) -> Handle { 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>(&self, path: P) -> Result, AssetIoError> { 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 { let mut asset_metas = Vec::new(); for (label, asset) in &self.labeled_assets { @@ -140,33 +189,45 @@ impl<'a> LoadContext<'a> { asset_metas } + /// Gets the asset I/O associated with this load context. pub fn asset_io(&self) -> &dyn AssetIo { self.asset_io } } -/// The result of loading an asset of type `T` +/// The result of loading an asset of type `T`. #[derive(Debug)] pub struct AssetResult { + /// The asset itself. pub asset: Box, + /// The unique id of the asset. pub id: HandleId, + /// Change version. 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)] pub struct AssetLifecycleChannel { + /// The sender endpoint of the channel. pub sender: Sender>, + /// The receiver endpoint of the channel. pub receiver: Receiver>, } +/// Events for the [`AssetLifecycleChannel`]. pub enum AssetLifecycleEvent { + /// An asset was created. Create(AssetResult), + /// An asset was freed. Free(HandleId), } +/// A trait for sending lifecycle notifications from assets in the asset server. 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, version: usize); + /// Notifies the asset server that an asset was freed. fn free_asset(&self, id: HandleId); } impl_downcast!(AssetLifecycle); diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index cb39b3f5ca127..d47bca18e7617 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -7,6 +7,7 @@ use std::{ path::{Path, PathBuf}, }; +/// Represents a path to an asset in the file system. #[derive(Debug, Hash, Clone, Serialize, Deserialize)] pub struct AssetPath<'a> { path: Cow<'a, Path>, @@ -14,6 +15,7 @@ pub struct AssetPath<'a> { } impl<'a> AssetPath<'a> { + /// Creates a new asset path using borrowed information. #[inline] pub fn new_ref(path: &'a Path, label: Option<&'a str>) -> AssetPath<'a> { AssetPath { @@ -22,6 +24,7 @@ impl<'a> AssetPath<'a> { } } + /// Creates a new asset path. #[inline] pub fn new(path: PathBuf, label: Option) -> AssetPath<'a> { AssetPath { @@ -30,21 +33,25 @@ impl<'a> AssetPath<'a> { } } + /// Constructs an identifier from this asset path. #[inline] pub fn get_id(&self) -> AssetPathId { AssetPathId::from(self) } + /// Gets the sub-asset label. #[inline] pub fn label(&self) -> Option<&str> { self.label.as_ref().map(|label| label.as_ref()) } + /// Gets the path to the asset in the filesystem. #[inline] pub fn path(&self) -> &Path { &self.path } + /// Converts the borrowed path data to owned. #[inline] pub fn to_owned(&self) -> AssetPath<'static> { AssetPath { @@ -57,18 +64,21 @@ impl<'a> AssetPath<'a> { } } +/// An unique identifier to an asset path. #[derive( Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, )] #[reflect_value(PartialEq, Hash, Serialize, Deserialize)] pub struct AssetPathId(SourcePathId, LabelId); +/// An unique identifier to the source path of an asset. #[derive( Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, )] #[reflect_value(PartialEq, Hash, Serialize, Deserialize)] pub struct SourcePathId(u64); +/// An unique identifier to a sub-asset label. #[derive( Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect, )] @@ -104,10 +114,12 @@ impl<'a> From> for LabelId { } impl AssetPathId { + /// Gets the id of the source path. pub fn source_path_id(&self) -> SourcePathId { self.0 } + /// Gets the id of the sub-asset label. pub fn label_id(&self) -> LabelId { self.1 }